- 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
Shared utils
Cross-platform utilities, hooks, formatters, and dictionaries
Shared utils install automatically with components. To install separately:
pnpm add @wandercom/design-system-sharedClass name merging with Tailwind CSS conflict resolution. Built on clsx and tailwind-merge, pre-configured with design system class groups (text sizes, shadows, etc.).
import { cn } from '@wandercom/design-system-shared/classes';
function Card({ className, elevated }) {
return (
<div
className={cn(
'rounded-lg border p-4',
elevated && 'shadow-modal',
className
)}
/>
);
}Factory function to create a custom cn with extended Tailwind class groups. Use this when your project has custom Tailwind classes beyond the design system defaults.
import { createCn } from '@wandercom/design-system-shared/classes';
export const cn = createCn({
extend: {
classGroups: {
'font-size': ['text-marketing-hero', 'text-marketing-subtitle'],
},
},
});createCn executes once at module level and returns a function identical to cn but aware of your additional classes.
Import hooks individually:
import { useMediaQuery } from '@wandercom/design-system-shared/hooks/use-media-query';Subscribes to a CSS media query. Returns boolean | undefined (undefined during SSR).
const isWide = useMediaQuery('(min-width: 1024px)');Convenience wrappers around useMediaQuery with a 744px breakpoint.
import { useIsDesktop, useIsMobile } from '@wandercom/design-system-shared/hooks/use-media-query';
const isDesktop = useIsDesktop(); // min-width: 744px
const isMobile = useIsMobile(); // max-width: 743pxDetects the user's reduced motion preference. Returns boolean | undefined during SSR.
import { usePrefersReducedMotion } from '@wandercom/design-system-shared/hooks/use-reduced-motion';
const prefersReduced = usePrefersReducedMotion();usePrefersReducedMotionSafe is also exported and defaults to false during SSR instead of undefined.
Returns per-item CSS styles for staggered entrance animations, respecting reduced motion.
import { useStaggeredAnimation } from '@wandercom/design-system-shared/hooks/use-staggered-animation';
const { getItemStyle } = useStaggeredAnimation({
isActive: true,
baseDelayMs: 80,
stepMs: 60,
});
{items.map((item, i) => (
<div key={item.id} style={getItemStyle(i)}>{item.name}</div>
))}Returns true while the window is actively being resized. Debounced (default 150ms).
import { useResizeObserver } from '@wandercom/design-system-shared/hooks/use-resize-observer';
const isResizing = useResizeObserver(200); // custom debounce msReturns true when the window scroll position exceeds a threshold (default 0).
import { useScrolled } from '@wandercom/design-system-shared/hooks/use-scrolled';
const isScrolled = useScrolled(50); // 50px thresholdDetects when a header element overlaps dark theme sections and returns the appropriate theme. Uses intersection detection with mutation, scroll, and resize observers.
import { useHeaderThemeSync } from '@wandercom/design-system-shared/hooks/use-header-theme-sync';
const headerRef = useRef<HTMLElement>(null);
const theme = useHeaderThemeSync(headerRef, {
enabled: true,
selector: '[data-theme="dark"]:not([data-theme-scope="local"])',
});
// Returns "dark" | "light" | undefinedImport formatters individually:
import { formatDateRange } from '@wandercom/design-system-shared/formatters/dates';import {
formatDate,
formatDateRange,
getShortMonthName,
getMonthName,
DATE_FORMATS,
} from '@wandercom/design-system-shared/formatters/dates';
formatDate(new Date(), 'MMM d, yyyy'); // "Mar 9, 2026"
formatDateRange(checkIn, checkOut); // "Mar 15 - 20" or "Mar 15 - Apr 2"
getShortMonthName(2); // "Mar"
getMonthName(new Date()); // "March"DATE_FORMATS provides standard format strings: short, medium, long, dayOfWeek, dayOfWeekLong, monthYear, monthYearShort.
import { formatCount, formatCounts } from '@wandercom/design-system-shared/formatters/counts';
formatCount(1, { singular: 'guest' }); // "1 guest"
formatCount(3, { singular: 'guest' }); // "3 guests"
formatCount(0, { singular: 'guest', fallback: 'No guests' }); // "No guests"
formatCounts([
{ count: 2, singular: 'guest' },
{ count: 1, singular: 'pet' },
]);
// "2 guests, 1 pet"import { formatMoney } from '@wandercom/design-system-shared/formatters/money';
formatMoney({ currency: 'USD', fractional: 15000 }); // "$150.00"The fractional value is in cents (divided by 100 for display). Uses Intl.NumberFormat for locale-aware formatting.
Formatters for search bar UI labels:
import {
formatSearchBarDatesLabel,
formatSearchBarGuestsLabel,
formatFlexibleDatesLabel,
} from '@wandercom/design-system-shared/formatters/search-bar';
formatSearchBarGuestsLabel({ adults: 2, children: 1, pets: 1 });
// "3 guests, 1 pet"
formatFlexibleDatesLabel('weekend', ['jan', 'feb']);
// "Weekend in Jan, Feb"Shared calendar range utilities and vacation rental domain logic for consistent date selection, availability validation, and stay constraint computation across Calendar and PropertyCalendar implementations.
import {
buildAvailabilityMap,
getMaxCheckoutDate,
validateCheckinDate,
validateCheckoutDate,
} from '@wandercom/design-system-shared/calendar';AvailabilityDay — a single day's availability data from the property management system.
interface AvailabilityDay {
date: string; // ISO date string (YYYY-MM-DD)
canCheckIn: boolean;
canCheckOut: boolean;
isBlocked?: boolean;
minNights?: number;
maxNights?: number;
nightPrice?: number;
status: 'available' | 'booked' | 'maintenance' | 'blocked';
}DateRange — a check-in / check-out date pair, where either or both may be unset.
interface DateRange {
start: Date | null;
end: Date | null;
}StayRequirements — constraints on minimum/maximum nights and allowed check-in/check-out days.
interface StayRequirements {
minNights?: number;
maxNights?: number;
checkInDays?: string[]; // e.g. ["Saturday"]
checkOutDays?: string[];
}BookingRule — a booking rule that constrains date selection behavior.
interface BookingRule {
type:
| 'min-nights'
| 'max-nights'
| 'check-in-day'
| 'check-out-day'
| 'advance-booking'
| 'gap-nights'
| 'same-day-turnover'
| 'quota-exhausted';
message: string;
}parseLocalDate(dateStr) — parses an ISO date string (YYYY-MM-DD) into a local Date with no timezone shift.
import { parseLocalDate } from '@wandercom/design-system-shared/calendar';
parseLocalDate('2026-06-15'); // Date for June 15, 2026, local timegetWeekdayName(date) — returns the full weekday name in US English.
getWeekdayName(new Date('2026-06-20')); // "Saturday"buildAvailabilityMap(availability) — builds a fast-lookup Map from an availability array, keyed by ISO date string. Pass this map into validation functions instead of re-iterating the array.
import { buildAvailabilityMap } from '@wandercom/design-system-shared/calendar';
const availabilityMap = buildAvailabilityMap(availabilityDays);
const day = availabilityMap.get('2026-06-15');isDateInRange(date, start, end) — returns true when date falls strictly between start and end (exclusive of both endpoints).
import { isDateInRange } from '@wandercom/design-system-shared/calendar';
isDateInRange(midDate, checkIn, checkOut); // true if betweengetDateRangeState(date, selected, minDate?) — derives all range state flags for a given date against the current selection. Useful for driving calendar day cell styles.
import { getDateRangeState } from '@wandercom/design-system-shared/calendar';
const {
isRangeStart,
isRangeEnd,
isInRange,
isSingleSelected,
isRangeEndpoint,
isDisabled,
} = getDateRangeState(date, { from: checkIn, to: checkOut }, minDate);validateCheckinDate(date, availabilityMap, stayRequirements?) — validates a proposed check-in date. Returns an error string, or null when valid.
import { validateCheckinDate } from '@wandercom/design-system-shared/calendar';
const error = validateCheckinDate(date, availabilityMap, {
checkInDays: ['Saturday'],
});
// null | "Unavailable" | "Check-in unavailable"validateCheckoutDate(date, checkinDate, availabilityMap, stayRequirements?, rangeHasBlocked?) — validates a proposed check-out date against availability, stay requirements, and an optional range-blocked check. Returns an error string, or null when valid.
import { validateCheckoutDate } from '@wandercom/design-system-shared/calendar';
const error = validateCheckoutDate(
checkoutDate,
checkinDate,
availabilityMap,
{ minNights: 3, maxNights: 14 },
);
// null | "Unavailable" | "Min 3 nights" | "Max 14 nights" | ...getMaxCheckoutDate(checkinDate, availabilityMap, stayRequirements?) — computes the latest date a guest may check out. Returns the minimum of checkinDate + maxNights and the first blocked/booked day after check-in. Returns null when there is no computable upper bound.
import { getMaxCheckoutDate } from '@wandercom/design-system-shared/calendar';
const maxCheckout = getMaxCheckoutDate(checkinDate, availabilityMap, {
maxNights: 14,
});pathHasBlockedDay(availabilityMap, from, to) — returns true when the path between from and to (exclusive of both endpoints) contains a blocked, booked, or maintenance day. Use this to prevent range selections that span unavailable nights.
import { pathHasBlockedDay } from '@wandercom/design-system-shared/calendar';
if (pathHasBlockedDay(availabilityMap, checkIn, checkOut)) {
// range crosses an unavailable night
}Type-safe utilities for building components with responsive CVA variants. Solves Tailwind's tree-shaking limitation by requiring all responsive variants defined upfront.
import {
expandResponsiveVariants,
type VariantPropsResponsive,
type BreakpointValue,
} from '@wandercom/design-system-shared/responsive';
const variants = cva('', {
variants: {
size: { sm: '...', md: '...', lg: '...' },
sizeMd: { sm: 'md:...', md: 'md:...', lg: 'md:...' },
},
});
const RESPONSIVE_KEYS = ['size'] as const;
type Props = VariantPropsResponsive<typeof variants, typeof RESPONSIVE_KEYS>;
function Component(props: Props) {
return (
<div className={expandResponsiveVariants(variants, RESPONSIVE_KEYS, props)} />
);
}Enables responsive variant syntax:
<Component size={{ base: 'sm', md: 'lg' }} />BreakpointValue<T> supports breakpoints: base, sm, md, lg, xl, 2xl, 3xl, 4xl.
Progressive image loading with Thumbhash placeholders. Tiny (~20-30 byte) image previews that display while full images load.
import {
thumbHashToDataURL,
getThumbHashDimensions,
} from '@wandercom/design-system-shared/thumbhash';
const dataUrl = thumbHashToDataURL(thumbHashString);
const { width, height } = getThumbHashDimensions(thumbHashString);Both functions return null on error and work in browser and SSR contexts.
Pre-defined toast message templates with support for dynamic values. Provides consistent toast copy across applications.
import { createToastDictionary } from '@wandercom/design-system-shared/dictionaries/toasts';
const toasts = createToastDictionary({
BookingSaved: {
label: 'Booking saved',
description: 'Your booking has been saved.',
},
});createToastDictionary merges your custom toasts with the shared defaults (CopiedToClipboard, ChangedTheme, ErrorGeneral, ErrorActionFailed, ErrorInvalidInput, ErrorInputRequired, RequestReceived). Labels and descriptions can be strings or functions receiving { count?, date?, value? }.
Curated country lists (ISO 3166-1 alpha-2 codes) for use with PhoneInput, CountrySelect, and other country-aware components.
import { ALL_COUNTRIES } from '@wandercom/design-system-shared/countries';
import { STRIPE_CONNECT_COUNTRIES } from '@wandercom/design-system-shared/countries';| Export | Count | Description |
|---|---|---|
ALL_COUNTRIES | 235 | All ISO-2 country codes |
STRIPE_PAYMENT_COUNTRIES | 50 | Stripe Tax-supported countries |
STRIPE_SUPPORTED_COUNTRIES | 49 | Countries where Stripe is available for businesses |
STRIPE_CONNECT_COUNTRIES | 46 | Stripe Connect countries (used by WanderOS) |
Each list is a const array of lowercase ISO-2 strings. The CountryCode type extracts the union of all valid codes.
To use with PhoneInput, convert codes to CountryData[] via filterCountries:
import { STRIPE_CONNECT_COUNTRIES } from '@wandercom/design-system-shared/countries';
import { filterCountries } from '@wandercom/design-system-web/ui/phone-input';
<PhoneInput countries={filterCountries(STRIPE_CONNECT_COUNTRIES)} />Composable appearance config for Stripe Elements that mirrors DS token values — input sizing, border radii, typography, and color for both light and dark modes.
import {
createStripeAppearance,
createStripeGoogleFonts,
stripeLayout,
} from '@wandercom/design-system-shared/stripe-theme';
<Elements
stripe={stripePromise}
options={{
mode: 'payment',
currency: 'usd',
amount: 10_000,
fonts: createStripeGoogleFonts('Instrument Sans'),
appearance: createStripeAppearance(isDark),
}}
>
<PaymentElement options={{ layout: stripeLayout }} />
</Elements>Returns a complete Stripe Appearance object. Accepts either a boolean for the default Wander palette, or a StripeColors object for custom theming.
// Default palette
createStripeAppearance(isDark)
// Custom palette — override individual color values
const colors = { ...resolveStripeColors(false), colorDanger: '#c53030' };
createStripeAppearance(colors)
// Appearance-level overrides merged on top
createStripeAppearance(isDark, { labels: 'floating' })Returns a StripeColors object for a given mode. Use this to read resolved color values or to build a custom palette.
import {
resolveStripeColors,
createStripeAppearance,
type StripeColors,
} from '@wandercom/design-system-shared/stripe-theme';
const colors: StripeColors = resolveStripeColors(isDark);StripeColors fields:
| Field | Description |
|---|---|
colorBackground | Page/card background |
colorPrimary | Primary text and interactive color |
colorSurface | Secondary surface (Stripe colorSuccess) |
inputBackground | Input field background |
colorPlaceholder | Placeholder text |
borderSecondary | Default border |
borderHover | Hover border |
borderSelected | Focus/selected border |
colorDanger | Error color |
Composable primitives for building partial appearance objects. Useful when you need to merge variables or rules selectively.
import {
resolveStripeColors,
createStripeVariables,
createStripeRules,
} from '@wandercom/design-system-shared/stripe-theme';
const colors = resolveStripeColors(isDark);
const appearance = {
theme: isDark ? 'night' : 'flat',
variables: {
...createStripeVariables(colors),
fontSizeBase: '14px',
},
rules: {
...createStripeRules(colors),
'.Label': { fontSize: '14px', marginBottom: '8px' },
},
};Radius tokens used across the Stripe appearance.
import { stripeRadius } from '@wandercom/design-system-shared/stripe-theme';
stripeRadius.input // "8px"
stripeRadius.global // "16px"
stripeRadius.tab // "4rem"
stripeRadius.button // "9999px"Pre-configured PaymentElement layout using the accordion style.
import { stripeLayout } from '@wandercom/design-system-shared/stripe-theme';
<PaymentElement options={{ layout: stripeLayout }} />import {
createStripeGoogleFonts,
createStripeLocalFonts,
} from '@wandercom/design-system-shared/stripe-theme';
// Load from Google Fonts
createStripeGoogleFonts('Instrument Sans')
// Load a self-hosted font
createStripeLocalFonts('/fonts/InstrumentSans.woff2', 'Instrument Sans')- Emails - Email templates package
- Design tokens - Token-based styling
- Installation - Set up the design system