- Accordion
- Avatar
- Badge
- Breadcrumb
- Button
- Calendar
- Checkbox
- Combobox
- Container
- CurrencyInput
- DistributionSlider
- Drawer
- Dropdown
- Grid
- Heading
- Image
- Input
- InputGroup
- Label
- Logo
- MapPin
- Modal
- NativeSelect
- NumberInput
- OtpInput
- PhoneInput
- Popover
- Progress
- PropertyCalendar
- RadioGroup
- RadioGroupCards
- ResponsiveModal
- ScrollArea
- SearchBar
- SearchBarFallback
- SearchInput
- Select
- Separator
- Spinner
- Switch
- Tabs
- Text
- Textarea
- Toast
- Toggle
- ToggleGroup
- 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)}
/>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)}
/>Set isSwipeable={false} to disable touch swiping on the image carousel. The progress bar is hidden on mobile viewports but remains visible on desktop 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:
price?:
currency?:
locale?:
nights?:
rating?:
features?:
location?:
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
content?: string; // Content container
progress?: string; // Progress bar container
nav?: string; // Navigation arrow buttons
};