Sheet
A dialog docked to the right edge at full height, for longer forms you fill in without leaving the list.
Examples
Add server
Same props and behaviour as Dialog. The footer is pinned; the body scrolls.
<DialogTrigger target="add-server" icon="plus">Add server</DialogTrigger>
<Sheet
id="add-server"
title="Add server"
description="Connect an MCP server. Agents see its tools once it answers."
footer={<>
<DialogClose />
<Button type="submit">Add server</Button>
</>}
>
<Field id="add-server-name" label="Name">
<Input name="name" placeholder="Sentry" required />
</Field>
<Field id="add-server-url" label="Server URL" hint="The streamable HTTP endpoint.">
<Input name="url" type="url" mono placeholder="https://mcp.sentry.dev/mcp" required />
</Field>
</Sheet>Usage
import { Sheet, DialogTrigger, DialogClose } from '@switchboard/ui'
<DialogTrigger target="add-server" icon="plus">Add server</DialogTrigger>
<Sheet id="add-server" title="Add server" action={routes.servers.create.href()} footer={…}>
…
</Sheet>Props
Every component also takes mix, for css() and mixins applied after its own style.
Sheet
| Prop | Type | Default | Description |
|---|---|---|---|
| id* | string | – | Unique on the page; the DialogTrigger target. |
| title* | RemixNode | – | Heading and accessible name. |
| size | 'md' | 'lg' | 'md' | Width 440 or 560px. Full width on phones. |
| footer | RemixNode | – | Pinned under the scrolling body. |
| action | string | – | Where the form posts. |
Takes every Dialog prop except size and variant.
Accessibility
- A modal <dialog>, like Dialog: page inert, Escape closes, focus returns to the trigger.
- Named by its title and described by its description.
- The slide-in is skipped under prefers-reduced-motion.
Guidance
Do
- Use it for forms longer than a dialog: adding a server, uploading a skill.
- Keep the list behind it visible so people keep their place.
Don’t
- Use a sheet for navigation or as a permanent side panel.
- Stack a dialog on top of a sheet.