# Property Card

Real-estate and rental listing card with swipeable photos, price, beds, baths and area. Saving fills the heart and can leave a small marker, so saved homes stand out in the list.

> Composed · Pro · Data Display — React Native, Expo Go compatible.

## Install

```bash
npx @lumioui/cli add pro/property-card
```

## Import

```tsx
import { PropertyCard } from "@/components/ui/property-card/property-card";
```

## Variants

- `grid` — Search results: `npx @lumioui/cli add pro/property-card-grid`
- `map-peek` — Map peek: `npx @lumioui/cli add pro/property-card-map-peek`
- `rooms` — Custom photos: `npx @lumioui/cli add pro/property-card-rooms`
- `row` — Row list: `npx @lumioui/cli add pro/property-card-row`

## Props

| Prop | Type | Notes |
| --- | --- | --- |
| `photos` | `PropertyPhoto[]` | Listing photos — https URLs or `{ uri, alt }`. The first is the cover. |
| `price` | `number | string` | Price — a number is formatted with `formatPrice`, a string prints as-is. |
| `priceSuffix?` | `string` | Unit after the price ("/mo", "/night"). |
| `formatPrice?` | `(price: number) => string` | Formatter for numeric prices. Default `$689,000`. |
| `priceNote?` | `string` | Short line beside the price ("Price cut $15k", "Est. $3,940/mo"). |
| `beds?` | `number` | Bedrooms — 0 prints "Studio". |
| `baths?` | `number` | — |
| `area?` | `number` | Interior area. |
| `areaUnit?` | `string` | Area unit. Default "sqft". |
| `facts?` | `string[]` | Replace the beds/baths/area row with your own facts ("Garden", "EPC B"). |
| `address` | `string` | Street address — one line, truncates. |
| `subtitle?` | `string` | Secondary line under the address (neighbourhood, listing agent, "Listed 3 days ago"). |
| `tags?` | `string[]` | Pills over the photo — keep to one or two ("Open house Sat", "New"). |
| `saved?` | `boolean` | Controlled saved state. |
| `defaultSaved?` | `boolean` | Uncontrolled initial saved state. |
| `onSavedChange?` | `(saved: boolean) => void` | Called when the heart toggles. Omit both saved props and this to hide the heart. |
| `savedLabel?` | `string` | Where a save lands ("Tour list"). Stack layout shows a "Saved · …" chip  over the photo on save, which condenses into an accent dot that stays. |
| `layout?` | `"stack" | "row"` | `stack` (photo on top with pager) or `row` (thumbnail left). Default "stack". |
| `aspectRatio?` | `number` | Photo aspect ratio in stack layout. Default 4 / 3. |
| `photoIndex?` | `number` | Controlled photo index. |
| `defaultPhotoIndex?` | `number` | Uncontrolled initial photo index. |
| `onPhotoIndexChange?` | `(index: number) => void` | Fires when the pager settles on a new photo. |
| `onPhotoPress?` | `(index: number) => void` | Tap on a photo — open a lightbox at that index. Falls back to `onPress`. |
| `renderPhoto?` | `(source: { uri: string }, index: number, photo: PropertyPhoto) => React.ReactNode` | Render each photo yourself — e.g. a LightboxImage so a tap zooms out of the card. Receives the image source, the index and the original photo entry; fill the page (`h-full w-full`). The returned node owns its own taps (`onPhotoPress` is not wired around it). Default: a cover Image. |
| `onPress?` | `() => void` | Tap anywhere else on the card — open the listing. |
| `footer?` | `React.ReactNode` | Extra content at the bottom of the card body (agent row, CTA). |
| `className?` | `string` | — |

## Icons rendered

- `heart` — /icons.html?i=heart

Source: https://lumioui.com/components/property-card