import { PromptInputSlash } from "@elabs-ai/components-ai";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-aiPaste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui PromptInputSlash component (from @elabs-ai/components-ai) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look PromptInputSlash up before writing code: `npx -y @elabs-ai/components-cli docs PromptInputSlash` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-ai is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-ai` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-ai`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Avoid
- Moving DOM focus into the list — the textarea keeps focus and points at the active option with aria-activedescendant; a palette that steals focus loses the caret.
- Passing aria-label or id to the command list — cmdk overwrites both; use its own `label` prop and read the rendered id back off the node.
- Leaving aria-activedescendant set after the palette closes — a stale id points at a node that no longer exists.
- Hand-rolling the trigger scan — the caret/trigger machinery is shared with MentionInput (findTriggerQuery in @elabs-ai/components-ui), not re-derived here.
- Submitting the composer on Enter while the palette is open — Enter selects the command; the textarea's own onKeyDown-before-submit order is what makes that possible.
PromptInputSlash — a /-triggered slash-command palette for the composer. Typing / at the START of a line (never mid-word) opens a filtered listbox of app-defined commands; arrow keys move the highlight, Enter/Tab inserts /name , Escape dismisses — and, like MentionInput, focus never leaves the textarea. Ships no command vocabulary of its own; commands is entirely prop-driven.
Works with
Examples
Palette Open
Typing `/` at the start of the line opens the palette; arrow keys move the highlight without ever moving focus off the textarea.
Not Mid Word
Typing `/` mid-word (not at the start of a line) never opens the palette — the acceptance test for the line-start boundary.
Narrowing Clamps The Highlight
Narrowing the query clamps the highlighted option into the filtered list — it can never point at an option that has scrolled out of the results.
Select With Enter
Enter inserts the highlighted command and closes the palette.
Close With Escape
Escape closes the palette and returns focus to the textarea.
No Matches
No commands match the typed query — the empty state renders in the listbox. Ends by dismissing the palette: `cmdk`'s empty state is a bare `role="presentation"` node with no `option`/`group` children, which is a pre-existing `aria-required-children` shape shared by every cmdk-based combobox in this repo (already baselined for `overlays-command--groups`) — closing the palette before the story's axe pass avoids re-asserting that same, already-accepted primitive limitation here too.
API
PromptInputSlash
| Prop | Type | Description |
|---|---|---|
commandsrequired | SlashCommand[] | The registered commands the palette offers. |
valuerequired | string | The composer's current text — must match what the wrapped textarea renders. |
textareaRefrequired | RefObject<HTMLTextAreaElement | null> | Ref to the composer's `<textarea>` (usually the one `PromptInputSlashTextarea` renders). |
lineStartOnly | boolean | Only trigger at the start of a line (index 0, or right after `"\n"`). `false` falls back to `MentionInput`'s word-boundary rule. @default true |
filter | (command: SlashCommand, query: string) => boolean | Which commands survive the current query. @default defaultSlashCommandFilter |
onValueChangerequired | (next: { text: string; caret: number }) => void | A command was chosen — apply the spliced `{ text, caret }` to your own controlled value (the same shape `replaceTriggerRun` returns). The splice inserts `"/" + command.name + " "` in place of the typed query. |
onSelect | (command: SlashCommand) => void | A command was chosen (fires alongside, not instead of, `onValueChange`). |
onOpenChange | (open: boolean) => void | The popup opened or closed. |
childrenrequired | ReactNode |
PromptInputSlashTextarea
PromptInputCommand
PromptInputCommandList
PromptInputCommandItem
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| active | the highlighted command is reported to the textarea via aria-activedescendant — focus never leaves the input |
| empty | a real empty state with a sentence, never a blank popover |