Pre-alphaThe registry and the CLI are not published yet.Roadmap
Compositions

Listing Card

A home or a stay: photo, price, address and specs, stacked under the photo or over it.

Stays

Guest favorite
Sunny loft$3,679/mo
Quiet, bright, near the park
2 Beds2 Baths1,150 sqft
Pool villa$6,200/mo
Garden, pool, 10 min from the beach
4 Beds3 Baths2,400 sqft

For sale

Prime pick
$250,000List price
Guillaume Briard · 18 Harbor Rd, Seaview
29 m² Living2 Rooms
By Waleed Sabir2 days ago
View details
$410,000List price
Maison Lumière · 7 Park Ln, Riverside
64 m² Living3 Rooms
By Ana Ruiz5 days ago
View details

New listings

Newly listed
Highland hut$200k
254 Highland Ave, Lochside
2 Beds1 Bath1,150 sqft
White villa$1.2M
3 Cliff Rd, Seaview
5 Beds4 Baths3,800 sqft

For sale

Prime pick
$250,000List price
Guillaume Briard3 Cliff Rd, Seaview
29 m²Living
2Rooms
By Waleed Sabir2 days ago
$185,000List price
Iona Campbell254 Highland Ave, Lochside
48 m²Living
2Rooms
By Ana Ruiz5 days ago

Weekend escapes

The Hill Guest House58 Hullbrook Rd, Alpendorf
2 Beds1 Bath1,160 sqft
$620Reserve now
Lochside Cabin254 Highland Ave, Lochside
1 Bed1 Bath640 sqft
$240Reserve now

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-card
pnpm dlx axiom add listing-card
yarn dlx axiom add listing-card
bun x axiom add listing-card

The file lands in components/compositions/listing-card.tsx.

npx axiom add listing-card-detailed
pnpm dlx axiom add listing-card-detailed
yarn dlx axiom add listing-card-detailed
bun x axiom add listing-card-detailed

The file lands in components/compositions/listing-card-detailed.tsx. Also copies avatar and button.

npx axiom add listing-card-overlay
pnpm dlx axiom add listing-card-overlay
yarn dlx axiom add listing-card-overlay
bun x axiom add listing-card-overlay

The file lands in components/compositions/listing-card-overlay.tsx.

npx axiom add listing-card-overlay-detailed
pnpm dlx axiom add listing-card-overlay-detailed
yarn dlx axiom add listing-card-overlay-detailed
bun x axiom add listing-card-overlay-detailed

The file lands in components/compositions/listing-card-overlay-detailed.tsx. Also copies avatar.

npx axiom add listing-card-booking
pnpm dlx axiom add listing-card-booking
yarn dlx axiom add listing-card-booking
bun x axiom add listing-card-booking

The 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:

components/ui/icons.tsx
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

PropTypeDefaultDescription
titlestring—Name of the place, on one line next to the price.
pricestring—Already formatted, currency included: "$3,679".
priceUnitstring—Shown small after the price: "/mo", "/night".
subtitlestring—One line under the title: a tagline, an area.
imageImageSourcePropType—The photo.
aspectRationumber4 / 3Of the photo.
badgestring—Short label in a highlight Badge, on the photo.
specsListingSpec[]—{ 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”.
styleStyleProp<ViewStyle>—Extra styles for the card container, for example a width in a rail.
PropTypeDefaultDescription
pricestring—Already formatted, currency included. Shown first, as a headingSm Title.
priceLabelstring—Shown small after the price: "List price".
titlestring—The seller or the building, in semibold.
subtitlestring—Usually the address, after the title on the same line.
imageImageSourcePropType—The photo.
aspectRationumber4 / 3Of the photo.
badgestring—Short label in a highlight Badge, on the photo.
specsListingSpec[]—{ icon?, value, label? }, between two lines.
agent{ name, avatar? }—Who posted the listing: an Avatar and “By name”.
metastring—At the end of the agent line: "2 days ago".
actionLabelstring"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.
styleStyleProp<ViewStyle>—Extra styles for the card container.
PropTypeDefaultDescription
titlestring—Name of the place, on one line next to the price.
pricestring—Already formatted, currency included: "$200k".
subtitlestring—One line under the title: the address.
imageImageSourcePropType—Covers the whole card.
aspectRationumber3 / 4Of the whole card. Portrait by default.
badgestring—Short label in a highlight Badge, top left.
specsListingSpec[]—{ icon?, value, label? }, under a line, in white.
onPress() => void—Makes the whole card pressable.
styleStyleProp<ViewStyle>—Extra styles for the card container, for example a width in a rail.
PropTypeDefaultDescription
pricestring—Already formatted, currency included. Shown first, as a heading.
priceLabelstring—Shown small after the price: "List price".
titlestring—The seller or the building, in semibold.
subtitlestring—Usually the address, under the title, on up to two lines.
imageImageSourcePropType—Covers the whole card.
aspectRationumber3 / 4Of the whole card.
badgestring—Short label in a highlight Badge, top left.
specsListingSpec[]—{ icon?, value, label? }, stacked next to the title: value above, label below. Two fit.
agent{ name, avatar? }—Who posted the listing, under a line.
metastring—After the agent: "2 days ago".
onPress() => void—Makes the whole card pressable.
styleStyleProp<ViewStyle>—Extra styles for the card container.
PropTypeDefaultDescription
titlestring—Name of the place.
subtitlestring—One line under the title: the address.
pricestring—Already formatted, currency included. Shown in a translucent pill next to the button.
priceUnitstring—Shown small after the price: "/night".
imageImageSourcePropType—Covers the whole card.
aspectRationumber3 / 4Of the whole card.
badgestring—Short label in a highlight Badge, top left.
specsListingSpec[]—{ icon?, value, label? }, in white.
actionLabelstring"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.
styleStyleProp<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 highlight label 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.