A button that widens into a command panel: type to find an action, a small form slides into the same panel where one's needed, and the button says what happened.
Installation
When to use
- The New or Quick add button of an app: the handful of things people create or jump to, in one place they can type into.
- Actions that need a little input (a name, an email) without a dialog: the step opens inside the same panel.
When not to use
- A single action: a plain button does it in one click.
- Searching the whole app's content: a command palette over the page (Command) fits better.
Usage
Command Button with your own data: a whole component you can paste as a page.
"use client"
import { CommandButton } from "@/components/dashral/pro/command-button"
import { FolderPlus, Settings } from "lucide-react"
const actions = [
{
id: "project",
label: "New project",
icon: <FolderPlus />,
group: "Create",
// A step inside the panel: call done() with what the button should say after.
panel: ({ done }: { done: (message?: string) => void }) => (
<button type="button" onClick={() => done("Project created")} className="w-full rounded-md bg-foreground px-3 py-2 text-sm text-background">
Create
</button>
),
},
{ id: "settings", label: "Settings", icon: <Settings />, group: "Go to", onSelect: () => "Opening Settings" },
]
export default function Toolbar() {
return <CommandButton actions={actions} />
}API reference
CommandButton
A button that widens into a command panel: type to find an action, arrows and Enter to run it, a small step (form) sliding into the same panel where an action needs one. When it's done the panel shrinks back into the button, which says what happened for a moment.
| Prop | Type | Default |
|---|---|---|
actions | CommandAction[]Required. | — |
label | stringThe button's words; "New" by default. | "New" |
icon | React.ReactNodeThe button's icon; a plus by default. | <Plus /> |
shortcut | string[] | nullKeys that open it from anywhere on the page; ⌘J / Ctrl J by default, null for none. | ["mod", "J"] |
placeholder | string | "Type a command…" |
side | "bottom" | "top"Where it opens: down from the button (bottom, the default) or up from it (top, for a button at a screen's foot). | "bottom" |
align | "start" | "end"Which edge it keeps: the button's end (default, it grows toward the start) or its start. | "end" |
width | numberThe panel's width, in px (never wider than the screen). | 380 |
className | string | — |
classNames | CommandButtonClassNames | — |
type CommandAction
| Prop | Type | Default |
|---|---|---|
id | string | — |
label | string | — |
description | string | — |
icon | React.ReactNode | — |
group | stringIts heading in the list ("Create", "Go to"…). | — |
shortcut | string[]Its keys, shown beside it (["mod", "P"]). Shown only: wire them in your app if they should work outside. | — |
keywords | stringWords it's also found by. | — |
onSelect | () => void | string | Promise<void | string>Run it. Return a short message for the button to show after ("Project created"), as is or from a promise. | — |
panel | (step: { done: (message?: string) => void; back: () => void }) => React.ReactNodeOr a step inside the panel (a small form): | — |
type CommandButtonClassNames
| Prop | Type | Default |
|---|---|---|
root | string | — |
button | string | — |
panel | string | — |
Keyboard
| Keys | Action |
|---|---|
| ⌘JorCtrl J | Open or close it from anywhere (shortcut, null for none) |
| ↑or↓ | Move through the actions |
| Enter | Run the action, or open its step |
| ← | In a step: back to the actions (not while typing in a field) |
| Esc | Back from a step, or close; the focus returns to the button |
Accessibility
- The button says it opens a dialog and whether it's open, with its shortcut in aria-keyshortcuts.
- The panel is a dialog (aria-modal) named Commands; its field is a combobox pointing at the highlighted action in the listbox (aria-activedescendant).
- What an action says after ("Project created") is announced in a live region.
Motion
- The button widens into the panel (0.42 s) and, closing, shrinks back onto it while the two crossfade there.
- The highlight glides from action to action; a step slides in from the side as the panel reshapes to it. The button's words swap for the message, then back.
- Smooth tweens on [0.4, 0, 0.2, 1], never springs, except the small press. With reduced motion it only fades.