Drawer
A slide-out panel component that provides controlled open state, directional placement, and optional header/footer content. Built as a composed component with a root container and header/footer sub-components.
import { Box, Button, Drawer, Flex } from '@editora/ui-react';
function FilterDrawer() {
const [open, setOpen] = React.useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open drawer</Button>
<Drawer open={open} side="right" dismissible onChange={setOpen}>
<Drawer.Header style={{ fontWeight: 700 }}>Filters</Drawer.Header>
<Box>Drawer content</Box>
<Drawer.Footer style={{ display: 'flex', gap: 8 }}>
<Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Apply</Button>
</Drawer.Footer>
</Drawer>
</>
);
}
Sub-components
Drawer
Root container for the slide-out panel with placement, sizing, and styling controls.
Props:
open?: boolean— Controlled open stateside?: 'left' | 'right' | 'top' | 'bottom' | 'start' | 'end'— Drawer placement (default: 'right')variant?: 'default' | 'solid' | 'flat' | 'line' | 'glass' | 'contrast'— Surface variantdensity?: 'default' | 'compact' | 'comfortable'— Internal spacing densityshape?: 'default' | 'square' | 'soft'— Corner styleelevation?: 'default' | 'none' | 'low' | 'high'— Shadow depthtone?: 'default' | 'brand' | 'danger' | 'success' | 'warning'— Tone overridesize?: 'default' | 'sm' | 'lg'— Size tokenstate?: 'idle' | 'loading' | 'error' | 'success'— Visual stateinset?: boolean— Inset presentation modedismissible?: boolean— Allow user dismissalcloseOnOverlay?: boolean— Close when clicking overlaycloseOnEsc?: boolean— Close on Escape keylockWhileLoading?: boolean— Prevent closing during loading stateshowClose?: boolean— Render built-in close buttoninitialFocus?: string— Selector to focus on opentitle?: string— Set title textdescription?: string— Set description textariaLabel?: string— Set aria-labelariaLabelledBy?: string— Set aria-labelledbyariaDescribedBy?: string— Set aria-describedbyonOpen?: () => void— Fires when the drawer opensonClose?: () => void— Fires when the drawer closesonChange?: (open: boolean) => void— Change event bridge
Drawer.Header
Header content area for the drawer, typically containing title and close affordances.
Props:
- All standard HTML div attributes
Example:
<Drawer.Header style={{ borderBottom: '1px solid #e2e8f0' }}>
<div style={{ fontWeight: 700, fontSize: 18 }}>Edit Profile</div>
</Drawer.Header>
Drawer.Footer
Footer content area for the drawer, typically containing action buttons.
Props:
- All standard HTML div attributes
Example:
<Drawer.Footer style={{ borderTop: '1px solid #e2e8f0', display: 'flex', gap: 8 }}>
<Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Save</Button>
</Drawer.Footer>
Examples
Basic Side Drawer
export function DrawerExample() {
const [open, setOpen] = React.useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open Drawer</Button>
<Drawer
open={open}
side="right"
dismissible
onChange={setOpen}
title="Settings"
>
<Drawer.Header>
<div style={{ fontWeight: 700 }}>Settings</div>
</Drawer.Header>
<div style={{ padding: 16, display: 'grid', gap: 16 }}>
<div>
<label style={{ display: 'block', marginBottom: 8, fontWeight: 500 }}>
Theme
</label>
<select style={{ width: '100%', padding: 8 }}>
<option>Light</option>
<option>Dark</option>
<option>Auto</option>
</select>
</div>
<div>
<label style={{ display: 'block', marginBottom: 8, fontWeight: 500 }}>
Notifications
</label>
<input type="checkbox" /> Enable notifications
</div>
</div>
<Drawer.Footer style={{ display: 'flex', gap: 8, padding: 16, borderTop: '1px solid #e2e8f0' }}>
<Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Save Settings</Button>
</Drawer.Footer>
</Drawer>
</>
);
}
Multi-Position Variant
const positions = ['left', 'right', 'top', 'bottom'] as const;
export function MultiPositionDrawer() {
const [open, setOpen] = React.useState(false);
const [side, setSide] = React.useState<typeof positions[number]>('right');
return (
<>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
{positions.map((pos) => (
<Button
key={pos}
size="sm"
onClick={() => {
setSide(pos);
setOpen(true);
}}
>
{pos}
</Button>
))}
</div>
<Drawer
open={open}
side={side}
dismissible
onChange={setOpen}
>
<Drawer.Header>Drawer on {side}</Drawer.Header>
<div style={{ padding: 16 }}>Content for {side} drawer</div>
<Drawer.Footer style={{ display: 'flex', gap: 8, padding: 16 }}>
<Button onClick={() => setOpen(false)}>Close</Button>
</Drawer.Footer>
</Drawer>
</>
);
}
With Loading State
export function AsyncDrawer() {
const [open, setOpen] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const handleSave = async () => {
setLoading(true);
await new Promise(resolve => setTimeout(resolve, 1500));
setLoading(false);
setOpen(false);
};
return (
<>
<Button onClick={() => setOpen(true)}>Open Drawer</Button>
<Drawer
open={open}
side="right"
dismissible={!loading}
closeOnOverlay={!loading}
lockWhileLoading={loading}
state={loading ? 'loading' : 'idle'}
onChange={setOpen}
>
<Drawer.Header>
<div style={{ fontWeight: 700 }}>Create Item</div>
</Drawer.Header>
<div style={{ padding: 16, display: 'grid', gap: 12 }}>
<input placeholder="Item name" disabled={loading} />
<textarea placeholder="Description" disabled={loading} />
</div>
<Drawer.Footer style={{ display: 'flex', gap: 8, padding: 16 }}>
<Button
variant="secondary"
onClick={() => setOpen(false)}
disabled={loading}
>
Cancel
</Button>
<Button onClick={handleSave} disabled={loading}>
{loading ? 'Saving...' : 'Create'}
</Button>
</Drawer.Footer>
</Drawer>
</>
);
}
CSS Variables
The underlying ui-drawer web component supports these CSS custom properties for theme customization.
| Variable | Default | Description |
|---|---|---|
--ui-drawer-bg | Theme surface | Background color |
--ui-drawer-text | Theme text | Text foreground |
--ui-drawer-border | Theme border | Border color |
--ui-drawer-shadow | Elevation-based | Shadow depth |
--ui-drawer-width | 360px / 480px | Panel width (default/lg) |
--ui-drawer-height | 50vh / 60vh | Panel height when top/bottom |