- 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
ResponsiveModal
Adaptive component that renders as a modal on desktop and drawer on mobile
pnpm add @wandercom/design-system-web
ResponsiveModal combines Modal (Radix Dialog) and Drawer (Base UI) to provide responsive behavior.
The ResponsiveModal component automatically adapts between Modal and Drawer based on viewport size, providing an optimal experience across devices.
import { useState } from 'react';
import { ResponsiveModal } from '@wandercom/design-system-web/ui/responsive-modal';
import { Button } from '@wandercom/design-system-web/ui/button';
export function Example() {
const [open, setOpen] = useState(false);
return (
<ResponsiveModal
open={open}
onOpenChange={setOpen}
trigger={<Button>Open</Button>}
title="Edit profile"
description="Make changes to your profile here. Click save when you're done."
footer={
<>
<Button variant="outline">Cancel</Button>
<Button variant="primary">Save changes</Button>
</>
}
>
<div>Form content here</div>
</ResponsiveModal>
);
}The recommended approach is to control the open state programmatically. This ensures the state persists correctly when switching between modal and drawer as the viewport changes:
import { useState } from 'react';
import { ResponsiveModal } from '@wandercom/design-system-web/ui/responsive-modal';
export function ControlledExample() {
const [open, setOpen] = useState(false);
return (
<ResponsiveModal
open={open}
onOpenChange={setOpen}
trigger={<button>Open</button>}
title="Responsive content"
>
<p>This appears as a modal on desktop and drawer on mobile</p>
<button onClick={() => setOpen(false)}>Close</button>
</ResponsiveModal>
);
}The ResponsiveModal automatically adapts to the viewport size:
- Desktop (≥744px): Renders as a centered modal dialog
- Mobile (<744px): Renders as a bottom drawer
This provides an optimal experience across devices without requiring manual detection or separate implementations.
When the viewport is resized while the ResponsiveModal is open, the component intelligently handles the transition between modal and drawer modes by temporarily disabling animations. This prevents jarring visual glitches and ensures a smooth user experience during window resizing. Once resizing completes, normal animations are restored.
Use the slots prop to customize styles for specific parts:
<ResponsiveModal
trigger={<button>Open</button>}
title="Custom styles"
slots={{
content: "max-w-2xl",
title: "text-2xl",
description: "text-muted"
}}
>
Content here
</ResponsiveModal>Use ResponsiveModal when you need a single implementation that works well across all devices:
- Form submissions and data entry
- Confirmation dialogs
- Settings and preferences
- Content that requires user interaction
- Any modal experience that should adapt to device size
If you need explicit control over desktop or mobile behavior, use the Modal or Drawer components directly.
trigger?
title?
description?
footer?
secondaryAction?
variant?
size?
formId?
children?
open?
onOpenChange?
defaultOpen?
onOpenAutoFocus?
onCloseAutoFocus?
modal?
slots?
className?
The responsive breakpoint is set at 744px:
- Desktop:
min-width: 744px→ Modal (centered dialog) - Mobile:
max-width: 743px→ Drawer (bottom sheet)
This breakpoint is defined using the useMediaQuery hook and cannot be customized per instance. For custom breakpoint behavior, use the Modal and Drawer components directly with your own media query logic.
The ResponsiveModal component includes proper accessibility features across both modal and drawer modes:
- Automatically manages focus when opened and closed
- Traps focus within the content when open
- Closes on escape key press
- Supports click outside to close
- Uses
aria-labelledbyto link title to content - Uses
aria-describedbyto link description to content - Returns focus to trigger element when closed
- Provides appropriate animations for visual feedback