Place Card

Pro

A place an assistant found for you, in its reply. The photo sits inset in the card, the score and the open hours are one glance apart, and directions, call and website are a tap away. Number it to match a pin on the map.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add place-card

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

Two blocks. The media frame, and the content column: title, a meta line, rating, hours, address, a line on why it fits and the actions. Drop any part you do not have; the card closes up around the rest.

import {  PlaceCard,  PlaceCardMedia,  PlaceCardContent,  PlaceCardTitle,  PlaceCardMeta,  PlaceCardRating,  PlaceCardHours,  PlaceCardAddress,  PlaceCardDescription,  PlaceCardActions,  PlaceCardAction,} from "@/components/ui/place-card"<PlaceCard>  <PlaceCardMedia />  <PlaceCardContent>    <PlaceCardTitle />    <PlaceCardMeta />    <PlaceCardRating />    <PlaceCardHours />    <PlaceCardAddress />    <PlaceCardDescription />    <PlaceCardActions>      <PlaceCardAction />    </PlaceCardActions>  </PlaceCardContent></PlaceCard>

Orientation#

vertical puts a 3:2 photo over the details, for a row of places. orientation="horizontal" puts a square thumbnail beside them, for a short list where a column of tall cards would push the answer off screen.

Open status#

PlaceCardHours says whether you can go now. Open reads in the success tone, closing within the hour in warning, closed in the quiet meta ink. The next change in hours follows it: pass it pre-formatted in the place's own time zone, not the reader's.

Two plates of tomato and cucumber salad on a linen tablecloth

Lupa Verde

ItalianModerately priced0.3 mi

Rated 4.6 out of 5, 2,140 reviews on Google

OpenCloses 11 PM

Hands holding an iced latte in a glass next to an orange lily

Marlow Coffee

CafeInexpensive0.2 mi

Rated 4.8 out of 5, 868 reviews on Google

Closes soon6 PM

A wooden counter with stools under woven pendant lamps

Daybreak Bakery

BakeryInexpensive0.5 mi

Rated 4.7 out of 5, 503 reviews on Google

ClosedOpens 7 AM

With a map#

The answer most assistants give for "where should I go": a map with numbered pins over a row of cards. Give each card its index and each MapPin the same number. Pointing at a card selects its pin; clicking a pin lifts its card and slides it into view. It is a composition of Map, ProductCarousel and Place Card, with one piece of state between them.

In a conversation#

Inside a Conversation the cards take the column's width, with no bubble around them. For one or two picks, horizontal cards with a line on why each fits read like a recommendation, not a search result. A labelled Button after the icon actions sits at the end of the row.

Somewhere for dinner near Flatiron tonight, not too loud, around $30 a head?
Two fit. Lupa Verde is the easier table to get; Field & Fable stays open later if you want to linger.
Two plates of tomato and cucumber salad on a linen tablecloth

Lupa Verde

ItalianModerately priced0.3 mi

Rated 4.6 out of 5, 2,140 reviews on Google

OpenCloses 11 PM

Seasonal plates and a short list of natural wines. Book ahead for Friday.

A long wooden table with chairs in a sunlit room

Field & Fable

Wine barModerately priced0.6 mi

Rated 4.4 out of 5, 1,904 reviews on Google

OpenCloses 12 AM

Small plates until midnight and a bottle list priced like a neighborhood bar.

Loading#

While a place streams in, PlaceCardSkeleton holds its spot in the same frame, with the photo and the lines where they will land.

API reference#

PlaceCard

The card and the context every part reads. With href the whole card links out through one overlay link, named by the title; the actions sit above it. Every part accepts className, merged last, and every part is droppable. Forwards native <article> props.

PropTypeDefaultDescription
hrefstring-The place's page: its website, a listing or a booking page. Makes the whole card a link and turns on the hover lift.
targetReact.HTMLAttributeAnchorTarget"_blank"Where the link opens. A new tab by default, announced in the link's name, with rel="noopener noreferrer".
linkLabelstring-Overrides the link's accessible name, which is otherwise the title and "opens in a new tab".
orientation"vertical" | "horizontal""vertical"vertical puts the photo over the details, for a row of places. horizontal puts a square thumbnail beside them, for a short list or a single pick.
indexnumber-The place's number in the answer. Shows as a chip before the title, the same ink as a numbered MapPin, so card 2 reads as pin 2.
selectedbooleanfalseThe place the map is pointing at. The card lifts 4px and its shadow grows, a step past the hover hint.
Data attributeDescription
data-orientationThe card's orientation.
data-selectedPresent while selected.

PlaceCardMedia

The photo, inset 8px into the card with a concentric radius and a hairline edge. 3:2 when vertical, a square thumbnail when horizontal; the frame owns the ratio, so pass an aspect-* class to change it. Forwards native <div> props.

PropTypeDefaultDescription
srcstring-The photo, cropped to fill the frame. The card renders a plain <img>, so any host works.
altstring""Describe what is in the shot: the room, the dish, the storefront.
srcSetstring-Responsive sources, so the browser picks one the width of the card.
sizesstring"(min-width: 768px) 280px, 70vw"Used with srcSet.
loading"eager" | "lazy""eager"Eager by default: a card slid into a carousel moves by a transform, which the browser does not count as scrolling it into view.
childrenReact.ReactNode-Your own <img> or overlays instead of src.

PlaceCardContent

The details column under the photo, or beside it when horizontal. Forwards native <div> props.

PlaceCardTitle

The place's name, clamped to two lines, with the index chip in front when the card has one. Labels the card and its link. Forwards native heading props.

PropTypeDefaultDescription
as"h2" | "h3" | "h4" | "p""h3"The element, to fit the heading outline of the page.

PlaceCardMeta

One quiet line: what it is, how much it costs and how far it is, joined by middle dots. Pass only what you have. Forwards native <p> props.

PropTypeDefaultDescription
categoryReact.ReactNode-Italian, Cafe, Bookshop, Museum.
price1 | 2 | 3 | 4 | React.ReactNode-A price level from 1 to 4, drawn as that many symbols and read out as "Moderately priced" and the like. Or your own text ("$20 to $30").
priceSymbolstring"$"The symbol a numeric price repeats: €, £.
distanceReact.ReactNode-How far, pre-formatted ("0.3 mi", "450 m"). Set in tabular figures.
childrenReact.ReactNode-More segments, each one dotted off from the last.

PlaceCardRating

One star, the score, the review count in compact form and where the rating comes from: "4.6 (2.1K) · Google". Screen readers hear one phrase with the full count. Forwards native <p> props.

PropTypeDefaultDescription
value*number-The average score, shown with one decimal.
countnumber-The number of reviews. Shown compact (2.1K), announced in full.
sourceReact.ReactNode-Where the score is from: Google, Yelp, Tripadvisor.
maxnumber5The top of the scale, for the announcement.
localestring"en-US"The locale the score and count are formatted in.

PlaceCardHours

Whether it is open right now, in its tone, then the next change in hours: "Open · Closes 11 PM". Forwards native <p> props.

PropTypeDefaultDescription
status"open" | "closing" | "closed""open"open reads in the success tone, closing in warning ("Closes soon"), closed in the quiet meta ink.
labelReact.ReactNode-Replaces the status word, for another language or "Open 24 hours".
childrenReact.ReactNode-The next change, pre-formatted in the place's time zone: "Closes 11 PM", "Opens 7 AM".
Data attributeDescription
data-statusThe status.

PlaceCardAddress

The street address on one line, truncated, in an <address>. Forwards native <address> props.

PlaceCardDescription

A line or two from the assistant on why this place fits, clamped to three lines in the reading ink. Forwards native <p> props.

PlaceCardActions

The row of actions at the foot of the card, above the overlay link. Icon actions line their glyph up with the text; a labelled Button after them is pushed to the end. Forwards native <div> props.

PlaceCardAction

An icon Button with a tooltip and a 40px hit area. Its accessible name is the action and the place ("Directions, Lupa Verde"), so a row of cards does not read as five identical buttons. With href it renders a link. Forwards Button props.

PropTypeDefaultDescription
kind"directions" | "call" | "website"-A preset: the glyph, the name and whether the link opens a new tab. call stays in the tab, for tel: links.
labelstring-The action's name and tooltip. Required when there is no kind.
hrefstring-Makes the action a link: a maps URL, tel:, the website.
targetReact.HTMLAttributeAnchorTarget-Overrides where the link opens.
variantButtonProps["variant"]"subtle"The Button variant. Subtle keeps the row quiet until the pointer reaches it.
childrenReact.ReactNode-Your own glyph instead of the preset's.

PlaceCardSkeleton

The card's shape while a place is still on its way: the same frame, inset photo and lines, shimmering. aria-hidden; announce the wait on the list around it. Forwards native <div> props.

PropTypeDefaultDescription
orientation"vertical" | "horizontal""vertical"Match the cards it stands in for.

FAQ#

Because the map belongs to the answer, not to one place. Map already draws numbered pins and Product Carousel already lays out a row; the card only needs to carry the same number and a selected state. Wiring them with one piece of state keeps every part replaceable: use your own map, or a list instead of a row.