- Accordion
- Avatar
- Badge
- Breadcrumb
- Button
- Calendar
- ChatContainer
- ChatInput
- ChatMessage
- ChatMultiChoiceQuestion
- ChatMultiOptionQuestion
- ChatThinking
- Checkbox
- Combobox
- Container
- CurrencyInput
- DistributionSlider
- Drawer
- Dropdown
- FilePicker
- Grid
- Heading
- Image
- Input
- InputGroup
- Label
- Logo
- MapPin
- Markdown
- Modal
- NativeSelect
- NumberInput
- OptionSlider
- OtpInput
- PhoneInput
- Popover
- Progress
- PropertyCalendar
- RadioGroup
- RadioGroupCards
- ResponsiveModal
- ScrollArea
- SearchBar
- SearchBarFallback
- SearchInput
- Select
- Separator
- Spinner
- Switch
- Table
- Tabs
- Text
- Textarea
- TimePicker
- Toast
- Toggle
- ToggleCard
- ToggleGroup
- Toolbar
- Tooltip
PropertyCard
A card component for displaying property listings with image carousel, details, and wishlist functionality.
pnpm add @wandercom/design-system-web
import { useState } from 'react';
import { PropertyCard } from '@wandercom/design-system-web/blocks/property-card';
export function Example() {
const [isWishlisted, setIsWishlisted] = useState(false);
return (
<PropertyCard
name="Home in Newport beach"
images={[
{ src: "/img1.jpg", alt: "Front view" },
{ src: "/img2.jpg", alt: "Interior" },
]}
price={1060}
nights={2}
rating={4.8}
features={{ bedrooms: 4, beds: 6, baths: 4 }}
href="/property/newport-beach"
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>
);
}Basic property card with rating, price, and features.
<PropertyCard
name="Home in Newport beach"
images={images}
price={1060}
nights={2}
rating={4.8}
features={{ bedrooms: 4, beds: 6, baths: 4 }}
href="https://wander.com"
target="_blank"
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>When a location is provided, it appears above the property name.
<PropertyCard
name="Oceanfront Villa"
location="Malibu, California"
images={images}
price={2450}
nights={2}
rating={4.9}
features={{ bedrooms: 5, beds: 7, baths: 5 }}
href="#"
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>Provide a description to show a short tagline below the property name and above the features.
<PropertyCard
name="Wander Forest Retreat"
description="Forest views with a hot tub and deck"
images={images}
price={1060}
nights={2}
rating={4.8}
features={{ bedrooms: 4, beds: 6, baths: 4 }}
href="#"
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>When no rating is provided, the card displays "New" with a star icon.
<PropertyCard
name="Mountain Retreat"
location="Aspen, Colorado"
images={images}
price={850}
nights={1}
features={{ bedrooms: 3, beds: 4, baths: 2 }}
href="#"
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>Use the badge prop to overlay any node in the image's top-left corner. Today this slot is used to render a tier badge, but it accepts any ReactNode so consumers can render their own component. Style the wrapper via slots.badge.
import { Badge } from '@wandercom/design-system-web/ui/badge';
<PropertyCard
name="Home in Newport beach"
badge={<Badge variant="info">Tier 1</Badge>}
images={images}
price={1060}
nights={2}
rating={4.8}
features={{ bedrooms: 4, beds: 6, baths: 4 }}
href="#"
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>Set isSwipeable={false} to disable touch swiping on the image carousel. The progress bar is hidden when the card container is narrower than 65rem (1040px), but remains visible in wider cards where arrow navigation is available.
<PropertyCard
name="Home in Newport beach"
images={images}
price={1060}
nights={2}
rating={4.8}
features={{ bedrooms: 4, beds: 6, baths: 4 }}
href="#"
isSwipeable={false}
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>Set showWishlist={false} to hide the wishlist button entirely. Useful for contexts where wishlisting isn't available.
<PropertyCard
name="Beachfront Bungalow"
images={images}
price={1200}
href="#"
showWishlist={false}
/>Set showRating={false} to hide the rating and star icon entirely, including the "New" fallback state. Useful when rating data is unavailable or not relevant.
<PropertyCard
name="Home in Newport beach"
images={images}
price={1060}
nights={2}
href="#"
showRating={false}
isWishlisted={isWishlisted}
onWishlistClick={() => setIsWishlisted(!isWishlisted)}
/>Use the asChild prop to compose with routing libraries like Next.js Link.
import Link from 'next/link';
<PropertyCard
name="Home in Newport beach"
images={[{ src: "/img.jpg", alt: "Property" }]}
price={1060}
nights={2}
asChild
>
<Link href="/property/newport-beach" />
</PropertyCard>The image carousel follows the WAI-ARIA Carousel Pattern (basic, non-auto-rotating variant).
Carousel ARIA structure
- Carousel container uses
role="region"witharia-roledescription="carousel"and an accessible label derived from the property name - Each slide uses
role="group"witharia-roledescription="slide"and anaria-labelindicating position (e.g., "Front view (1 of 3)") - Non-visible slides are hidden from the accessibility tree via
aria-hidden - The slides container uses
aria-live="polite"so screen readers announce slide changes
Keyboard navigation
ArrowLeft/ArrowRightnavigate between slides when the carousel is focusedTabmoves focus through the previous/next buttons and wishlist button- Previous/next buttons become visible on focus for keyboard discoverability
Rendering semantics
- Renders as
<a>whenhrefis provided - Renders as
<button>whenonClickis provided (withouthref) - Renders as
<article>witharia-labelset to the property name when neither is provided - Wishlist button has
aria-pressedstate and dynamicaria-labelreflecting wishlisted state - Decorative elements (star icon, progress indicator) are hidden from the accessibility tree
name:
images:
badge?:
price?:
currency?:
locale?:
nights?:
rating?:
features?:
location?:
description?:
href?:
target?:
onClick?:
asChild?:
isSwipeable?:
showRating?:
showWishlist?:
isWishlisted?:
onWishlistClick?:
slots?:
className?:
type PropertyCardImage = {
src: string;
alt: string;
thumbhash?: string; // Optional blur placeholder hash
};type PropertyCardFeatures = {
bedrooms: number;
beds: number;
baths: number;
};type PropertyCardSlots = {
root?: string; // Root element
image?: string; // Image container
badge?: string; // Badge container (top-left overlay)
content?: string; // Content container
progress?: string; // Progress bar container
nav?: string; // Navigation arrow buttons
};