Forms & input
Slash menu
Type / in a field and an inline command menu appears at the caret — insert a heading, a list, a mention, a date, or set status without leaving the keyboard. The Notion-style shortcut that makes a text field do far more than text.
npx shadcn@latest add @orbit/slash-menuOverview
Live — click into the field and press /. Keep typing to filter (try /men), ↑↓ to move, Enter to insert.
last inserted: —
Capabilities
One key turns a plain field into a command surface — formatting and data insertion without reaching for a toolbar.
The menu anchors exactly where you’re typing, so the command list never covers what you’re writing.
Keep typing after the slash — /men narrows to Mention. The query lives in the text and clears on insert.
Mentions, dates and statuses drop in as styled chips, not plain text — so the field carries real structured data.
Arrows move the highlight, Enter inserts, Escape dismisses without touching your text. Hands stay home.
Custom commands
Pass your own groups through commands — each item declares its icon, description, extra filter keywords, and whether it inserts a styled chip or plain text. Here “/urgent” finds Priority via keywords.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| commands | SlashCommandGroup[] | DEFAULT_SLASH_COMMANDS | Grouped commands: { label, items }. Groups keep their order while the filter drops non-matching items. |
| label | string | "Description" | Uppercase field label; also the editable’s accessible name. |
| placeholder | string | "Write something, or press / to insert…" | Empty-state placeholder inside the editable. |
| onSelect | (command: SlashCommand) => void | — | Called after a command inserts (chip or text) and the menu closes. |
| showFooter | boolean | true | The “Try /h1 /mention /status” hint row with the Clear button. |
| showToast | boolean | true | Flash a “… inserted” toast under the card after each insert. |
| …rest | HTMLAttributes<HTMLDivElement> | — | Everything else is forwarded to the root wrapper, including ref. |
| Prop | Type | Default | Description |
|---|---|---|---|
| SlashCommand.id | string | — | Unique id — also matched by the filter, so /men hits “mention”. |
| SlashCommand.title | string | — | Row title; the filter matches it. |
| SlashCommand.description | string | — | Secondary line under the title. |
| SlashCommand.icon | ReactNode | — | 17px glyph shown in a 30px tile, drawn with currentColor. |
| SlashCommand.kbd | string | — | Trailing markdown-shorthand hint — e.g. “#”, “-”, “```”. |
| SlashCommand.keywords | string[] | — | Extra filter terms that aren’t visible on the row. |
| SlashCommand.chip | string | — | Insert this label as a styled, non-editable chip token. |
| SlashCommand.text | string | — | Insert this plain text at the caret (when chip isn’t set). |
| Prop | Type | Default | Description |
|---|---|---|---|
| useSlashMenu(options) | hook | — | The behavior layer for custom surfaces: pass { editorRef, commands, onSelect } and wire the returned onInput / onKeyDown to your own editable; it hands back open state, the filtered list, the highlight, the caret position, and pick(). |
| <SlashMenuPopup menu={…} /> | component | — | The caret-anchored listbox, purely presentational — render it inside a relative wrapper around your editable and feed it the hook’s return value. Optional heading and emptyMessage props relabel the header and no-match text (the Markdown Editor reuses it this way for @ mentions). |
Usage
A slash menu is an accelerator inside a field. For app-wide actions use the Command Palette; for fixed formatting, the Markdown Editor’s toolbar is the discoverable path.
Group and label commands
Basic blocks, then inserts. A short labelled list is faster to scan than one long alphabetical run.
Hide it as the only path
Pair the slash menu with a visible toolbar. New users won’t guess that “/” does anything.
Echo markdown shorthands
Show that /h1 equals “# ”. It teaches the even-faster inline syntax for the commands people use most.
Leave the “/” behind
On insert, remove the slash and its query from the text. A stray “/men” left in the field is a bug people notice.
Keyboard & accessibility
The editable keeps focus the whole time — the popup is a role=listbox the field controls: aria-haspopup and aria-expanded on the textbox, aria-activedescendant tracking the highlighted option, and mouse picks on mousedown so the caret never moves. Escape closes without changing the text.