- 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
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 adapts between Modal and Drawer based on viewport size by default. Pass containerRef when an embedding container, such as a component preview, should determine the mode instead.
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>
);
}Control the open state when a parent needs to react to close events. ResponsiveModal closes when its measured width crosses the active modal/drawer breakpoint, including while controlled:
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 uses viewport size by default, or the supplied containerRef width when one is provided:
- Viewport-driven desktop (≥744px): Renders as a centered modal dialog
- Viewport-driven mobile (<744px): Renders as a bottom drawer
- Container-driven desktop (≥1040px): Renders as a centered modal dialog
- Container-driven mobile (<1040px): Renders as a bottom drawer
This provides an optimal experience across devices without requiring manual detection or separate implementations.
When the viewport or observed container is resized while ResponsiveModal is open, the component closes before switching modes. Container-driven instances switch mode when their observed container crosses 1040px.
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?
containerRef?:
className?
Without containerRef, the viewport breakpoint remains 744px:
- Desktop:
min-width: 744px→ Modal (centered dialog) - Mobile:
max-width: 743px→ Drawer (bottom sheet)
With containerRef, the container-query breakpoint is 65rem (1040px):
- Desktop:
min-width: 65rem→ Modal (centered dialog) - Mobile: below
65rem→ Drawer (bottom sheet)
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