Listing Card
A home or a stay: photo, price, address and specs, stacked under the photo or over it.
Each variant is its own component with its own code: install the one you need. Default and Detailed put the photo above the card’s surface. Overlay, Overlay detailed and Booking cover the whole card with the photo and draw the text over a dark gradient, so it reads the same in light and dark mode.
Each variant shows one photo: the whole card is pressable, so it never swipes. Put the gallery on the listing’s screen.
Installation
npx axiom add listing-cardpnpm dlx axiom add listing-cardyarn dlx axiom add listing-cardbun x axiom add listing-cardThe file lands in components/compositions/listing-card.tsx.
npx axiom add listing-card-detailedpnpm dlx axiom add listing-card-detailedyarn dlx axiom add listing-card-detailedbun x axiom add listing-card-detailedThe file lands in components/compositions/listing-card-detailed.tsx. Also copies avatar and button.
npx axiom add listing-card-overlaypnpm dlx axiom add listing-card-overlayyarn dlx axiom add listing-card-overlaybun x axiom add listing-card-overlayThe file lands in components/compositions/listing-card-overlay.tsx.
npx axiom add listing-card-overlay-detailedpnpm dlx axiom add listing-card-overlay-detailedyarn dlx axiom add listing-card-overlay-detailedbun x axiom add listing-card-overlay-detailedThe file lands in components/compositions/listing-card-overlay-detailed.tsx. Also copies avatar.
npx axiom add listing-card-bookingpnpm dlx axiom add listing-card-bookingyarn dlx axiom add listing-card-bookingbun x axiom add listing-card-bookingThe file lands in components/compositions/listing-card-booking.tsx. Also copies button.
Every variant also copies card, badge, separator, icon, text, title and their dependencies. None brings tokens of its own.
The spec icons come from your icon registry. Add the ones you use to icons.tsx, for example with expo-symbols:
bed: symbol("bed.double", "bed"),
bath: symbol("bathtub", "bathtub"),
area: symbol("square.dashed", "square_foot"),
rooms: symbol("door.left.hand.closed", "door_front"),Usage
import { ListingCard } from "@/components/compositions/listing-card";
<ListingCard
title="Sunny loft"
subtitle="Quiet, bright, near the park"
price="$3,679"
priceUnit="/mo"
badge="Guest favorite"
image={listing.photo}
specs={[
{ icon: "bed", value: "2", label: "Beds" },
{ icon: "bath", value: "2", label: "Baths" },
{ icon: "area", value: "1,150", label: "sqft" },
]}
onPress={() => router.push(`/stays/${listing.id}`)}
/>;import { ListingCardDetailed } from "@/components/compositions/listing-card-detailed";
<ListingCardDetailed
price="$250,000"
priceLabel="List price"
title="Guillaume Briard"
subtitle="18 Harbor Rd, Seaview"
badge="Prime pick"
image={listing.photo}
specs={[
{ icon: "area", value: "29 m²", label: "Living" },
{ icon: "rooms", value: "2", label: "Rooms" },
]}
agent={{ name: "Waleed Sabir", avatar: agent.photo }}
meta="2 days ago"
onAction={() => router.push(`/homes/${listing.id}`)}
/>;import { ListingCardOverlay } from "@/components/compositions/listing-card-overlay";
<ListingCardOverlay
title="Highland hut"
subtitle="254 Highland Ave, Lochside"
price="$200k"
badge="Newly listed"
image={listing.photo}
specs={[
{ icon: "bed", value: "2", label: "Beds" },
{ icon: "bath", value: "1", label: "Bath" },
{ icon: "area", value: "1,150", label: "sqft" },
]}
onPress={() => router.push(`/homes/${listing.id}`)}
style={{ width: 260 }}
/>;import { ListingCardOverlayDetailed } from "@/components/compositions/listing-card-overlay-detailed";
<ListingCardOverlayDetailed
price="$250,000"
priceLabel="List price"
title="Guillaume Briard"
subtitle="3 Cliff Rd, Seaview"
badge="Prime pick"
image={listing.photo}
specs={[
{ icon: "area", value: "29 m²", label: "Living" },
{ icon: "rooms", value: "2", label: "Rooms" },
]}
agent={{ name: "Waleed Sabir", avatar: agent.photo }}
meta="2 days ago"
onPress={() => router.push(`/homes/${listing.id}`)}
/>;import { ListingCardBooking } from "@/components/compositions/listing-card-booking";
<ListingCardBooking
title="The Hill Guest House"
subtitle="58 Hullbrook Rd, Alpendorf"
price="$620"
priceUnit="/night"
image={stay.photo}
specs={[
{ icon: "bed", value: "2", label: "Beds" },
{ icon: "bath", value: "1", label: "Bath" },
{ icon: "area", value: "1,160", label: "sqft" },
]}
onAction={() => router.push(`/stays/${stay.id}/book`)}
onPress={() => router.push(`/stays/${stay.id}`)}
/>;price is a string: format it yourself, currency included, so the card doesn’t decide how your app shows money.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Name of the place, on one line next to the price. |
price | string | — | Already formatted, currency included: "$3,679". |
priceUnit | string | — | Shown small after the price: "/mo", "/night". |
subtitle | string | — | One line under the title: a tagline, an area. |
image | ImageSourcePropType | — | The photo. |
aspectRatio | number | 4 / 3 | Of the photo. |
badge | string | — | Short label in a highlight Badge, on the photo. |
specs | ListingSpec[] | — | { icon?, value, label? }: beds, baths, area. Shown as “2 Beds”, separated by lines. Three fit. |
onPress | () => void | — | Makes the whole card pressable. The card is read as “title, price”. |
style | StyleProp<ViewStyle> | — | Extra styles for the card container, for example a width in a rail. |
| Prop | Type | Default | Description |
|---|---|---|---|
price | string | — | Already formatted, currency included. Shown first, as a headingSm Title. |
priceLabel | string | — | Shown small after the price: "List price". |
title | string | — | The seller or the building, in semibold. |
subtitle | string | — | Usually the address, after the title on the same line. |
image | ImageSourcePropType | — | The photo. |
aspectRatio | number | 4 / 3 | Of the photo. |
badge | string | — | Short label in a highlight Badge, on the photo. |
specs | ListingSpec[] | — | { icon?, value, label? }, between two lines. |
agent | { name, avatar? } | — | Who posted the listing: an Avatar and “By name”. |
meta | string | — | At the end of the agent line: "2 days ago". |
actionLabel | string | "View details" | Label of the action button. |
onAction | () => void | — | Shows the full-width button. Without it, there is no button. |
onPress | () => void | — | Makes the whole card pressable. The button keeps its own press. |
style | StyleProp<ViewStyle> | — | Extra styles for the card container. |
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Name of the place, on one line next to the price. |
price | string | — | Already formatted, currency included: "$200k". |
subtitle | string | — | One line under the title: the address. |
image | ImageSourcePropType | — | Covers the whole card. |
aspectRatio | number | 3 / 4 | Of the whole card. Portrait by default. |
badge | string | — | Short label in a highlight Badge, top left. |
specs | ListingSpec[] | — | { icon?, value, label? }, under a line, in white. |
onPress | () => void | — | Makes the whole card pressable. |
style | StyleProp<ViewStyle> | — | Extra styles for the card container, for example a width in a rail. |
| Prop | Type | Default | Description |
|---|---|---|---|
price | string | — | Already formatted, currency included. Shown first, as a heading. |
priceLabel | string | — | Shown small after the price: "List price". |
title | string | — | The seller or the building, in semibold. |
subtitle | string | — | Usually the address, under the title, on up to two lines. |
image | ImageSourcePropType | — | Covers the whole card. |
aspectRatio | number | 3 / 4 | Of the whole card. |
badge | string | — | Short label in a highlight Badge, top left. |
specs | ListingSpec[] | — | { icon?, value, label? }, stacked next to the title: value above, label below. Two fit. |
agent | { name, avatar? } | — | Who posted the listing, under a line. |
meta | string | — | After the agent: "2 days ago". |
onPress | () => void | — | Makes the whole card pressable. |
style | StyleProp<ViewStyle> | — | Extra styles for the card container. |
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Name of the place. |
subtitle | string | — | One line under the title: the address. |
price | string | — | Already formatted, currency included. Shown in a translucent pill next to the button. |
priceUnit | string | — | Shown small after the price: "/night". |
image | ImageSourcePropType | — | Covers the whole card. |
aspectRatio | number | 3 / 4 | Of the whole card. |
badge | string | — | Short label in a highlight Badge, top left. |
specs | ListingSpec[] | — | { icon?, value, label? }, in white. |
actionLabel | string | "Reserve now" | Label of the white button. It is read as “label, title”. |
onAction | () => void | — | Required: the booking action. |
onPress | () => void | — | Makes the whole card pressable, to open the place. The button keeps its own press. |
style | StyleProp<ViewStyle> | — | Extra styles for the card container. |
Accessibility
- A pressable card is read as one element: “title, price” (the Detailed variants: “price, title”).
- The overlay variants draw white text on a dark gradient in both color schemes: the photo stays the same in dark mode, so the text does too. Pick photos without large white areas at the bottom.
Built with
- Card: the elevated surface and, for the stacked variants, its header.
- Badge: the
highlightlabel on the photo. - Separator: the lines around and between the specs.
- Icon: the spec icons, from your registry.
- Title and Text: name, price, address.
- Avatar: the agent, in the Detailed variants.
- Button: the action, in Detailed and Booking.
Used in
No block uses Listing Card yet.




