Sheet (Drawer)
- Component overview: Slide-out panel for forms, detail views, or secondary content. Supports four directions: top, right (default), bottom, and left. Based on Radix Dialog. Also exported as
Drawer. - Current status: Supports declarative (trigger as children) and config (title/content/footer) modes, with imperative
Sheet.open()API. - Implementation note:
ui/sheetwraps Radix Dialog primitive; design layer provides config-style API, direction/width control, and imperative overlay support.
Basic Usage
Pass title, content, footer as props. The trigger is provided as children.
Result
Loading...
Live Editor
render( <div style={{ padding: '40px 0' }}> <Sheet title="Profile" content={ <div style={{ padding: '0 16px', flex: 1 }}> <p style={{ fontSize: 14, color: '#6b7280', marginBottom: 8 }}>View and edit your profile information.</p> <p>Name: John Doe</p> <p>Email: john@example.com</p> </div> } footer={ <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}> <Button variant="secondary">Cancel</Button> <Button>Save</Button> </div> } > <Button>Open Sheet</Button> </Sheet> </div>, )
Direction
Use the side prop to control which edge the sheet slides from. Default is right.
Result
Loading...
Live Editor
render( <div style={{ display: 'flex', gap: 12, padding: '40px 0', flexWrap: 'wrap' }}> <Sheet side="right" title="Right Sheet" content={<div style={{ padding: '0 16px', flex: 1 }}>Slides from the right (default).</div>}> <Button variant="secondary">Right</Button> </Sheet> <Sheet side="left" title="Left Sheet" content={<div style={{ padding: '0 16px', flex: 1 }}>Slides from the left.</div>}> <Button variant="secondary">Left</Button> </Sheet> <Sheet side="top" title="Top Sheet" content={<div style={{ padding: '0 16px' }}>Slides from the top.</div>}> <Button variant="secondary">Top</Button> </Sheet> <Sheet side="bottom" title="Bottom Sheet" content={<div style={{ padding: '0 16px' }}>Slides from the bottom.</div>}> <Button variant="secondary">Bottom</Button> </Sheet> </div>, )
Custom Width
For left/right sheets, use the width prop. Accepts number (px) or string.
Result
Loading...
Live Editor
render( <div style={{ display: 'flex', gap: 12, padding: '40px 0' }}> <Sheet width={400} title="Narrow Sheet" content={<div style={{ padding: '0 16px', flex: 1 }}>Width: 400px</div>}> <Button variant="secondary">400px</Button> </Sheet> <Sheet width="80vw" title="Wide Sheet" content={<div style={{ padding: '0 16px', flex: 1 }}>Width: 80vw</div>}> <Button variant="secondary">80vw</Button> </Sheet> </div>, )
Controlled Mode
Result
Loading...
Live Editor
const Demo = () => { const [open, setOpen] = useState(false) return ( <div style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '40px 0' }}> <Button onClick={() => setOpen(true)}>Open Controlled</Button> <span style={{ fontSize: 14, color: '#999' }}>open: {String(open)}</span> <Sheet open={open} onOpenChange={setOpen} title="Controlled Sheet" content={ <div style={{ padding: '0 16px', flex: 1 }}> <p>This sheet is controlled externally.</p> <Button variant="secondary" onClick={() => setOpen(false)}>Close from inside</Button> </div> } /> </div> ) } render(<Demo />)
Imperative API
Sheet.open() provides imperative access. Suitable for async callbacks, keyboard shortcuts, or table actions where a JSX trigger is not available. Requires DesignProvider or OverlayHost mounted at the application root.
import { Sheet } from '@plaud/design'
Sheet.open({
title: 'Edit Profile',
content: ({ close }) => (
<div>
<p>Content here.</p>
<Button onClick={close}>Close</Button>
</div>
),
})
Update an Open Sheet
open() returns a controller to update configuration after opening:
const controller = Sheet.open({
title: 'Loading...',
content: <div>Please wait.</div>,
})
setTimeout(() => {
controller.update({
title: 'Done',
content: <div>Data loaded.</div>,
})
}, 1000)
Controller
interface ImperativeOverlayController<TOptions> {
id: string
close: () => void
update: (updater: Partial<TOptions> | ((prev: TOptions) => TOptions)) => void
afterClosed: Promise<void>
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Header title |
content | ReactNode | — | Body content |
footer | ReactNode | — | Footer area |
children | ReactNode | — | Trigger element (non-controlled mode) |
open | boolean | — | Controlled open state |
onOpenChange | (open: boolean) => void | — | Open state change callback |
side | 'top' | 'right' | 'bottom' | 'left' | 'right' | Slide direction |
width | number | string | 560 | Content width for left/right direction (px or CSS value) |
showClose | boolean | true | Show close button |
destroyOnClose | boolean | true | Unmount content when closed |
contentClassName | string | — | Custom content area class |
Usage Constraints
- Default width is 560px for left/right directions.
- Imperative API requires
DesignProviderorOverlayHostat the application root.