Token Input

A prompt field where @ adds files and people and / runs commands, each an atomic token, with suggestion menus that open at the caret.

Installation

You can add this token input component using our CLI or manually:

Dark mode ready - this component follows BoardUI semantic tokens automatically. Set up theme switching.

The CLI copies the component source (with its internal dependencies) into your project and installs any required npm packages.

npx boardui@latest add token-input

Using a shared UI package or custom import aliases? Configure your installation paths before running the CLI. Copy-paste examples use the default imports.

Triggers

Each trigger is a character and the suggestions it opens. A match starts at a word start, or only at the very start of the prompt with boundary: "start", and the menu filters as you type, ignoring case and accents. items can be data or a loader that gets the query and an AbortSignal once typing pauses. tone: "accent" draws its chips in the accent colour, and emptyState keeps the menu open when nothing matches.

import { RiFileCodeLine, RiSearchEyeLine } from "@remixicon/react";import type { TokenSuggestion, TokenTrigger } from "@/components/base/token-input/token-input"; const files: TokenSuggestion[] = [ { id: "app/page.tsx", label: "page.tsx", description: "app", icon: RiFileCodeLine }, { id: "components/sidebar.tsx", label: "sidebar.tsx", description: "components", icon: RiFileCodeLine },]; const triggers: TokenTrigger[] = [ // "@" at any word start, filtered as you type. { char: "@", type: "file", title: "Files", items: files }, // "/" only at the very start, as accent chips. { char: "/", type: "command", title: "Commands", boundary: "start", tone: "accent", items: commands }, // Items from a server: called with the query once typing pauses; a newer query aborts the older one. { char: "#", type: "issue", title: "Issues", emptyState: "No issues match", items: (query, signal) => fetch(`/api/issues?q=${query}`, { signal }).then((r) => r.json()), },];

Tokens and text

The value is text and tokens in order. toText writes it as text, each token as [@label](type:value), for a request, a form or a saved draft, and fromText reads that text back into tokens. Copy writes the same text, so a paste into any token input brings the chips back. Pass codec for your own format.

import { TokenInput, fromText, toText, type TokenInputValue } from "@/components/base/token-input/token-input"; const [value, setValue] = useState<TokenInputValue>([]);// ["Review ", { type: "file", value: "components/sidebar.tsx", label: "sidebar.tsx", id: "t1" }, " please"] toText(value); // "Review [@sidebar.tsx](file:components/sidebar.tsx) please"fromText("Review [@sidebar.tsx](file:components/sidebar.tsx) please"); // the value again <TokenInput aria-label="Message" value={value} onValueChange={setValue} triggers={triggers} />

Keyboard

A chip is one character to the caret. Typing, input methods, spelling and autocorrect stay the browser's; everything that touches a chip goes through the field's own history.

@, /, …Opens that trigger's menu at a word start; typing filters it
↑ ↓Moves the highlight while focus and caret stay in the field
Enter, TabInserts the highlighted item as a chip, with a space after it
EscapeCloses the menu and keeps it closed for that match
Ctrl+SpaceReopens a dismissed match
← →Crosses a chip in one press
BackspaceRemoves the chip before the caret
⌘Z, ⇧⌘ZUndo and redo, chips included
EnterSends when no menu is open; Shift+Enter breaks the line

Removing tokens

tokenDeletion decides what Backspace does next to a chip, and the ref handle edits the field from code: insert a token at the caret, focus, clear. Each is one undo step.

<TokenInput tokenDeletion="whole" /> {/* Backspace removes the chip (default) */}<TokenInput tokenDeletion="select" /> {/* first press selects it, the second removes it */}<TokenInput tokenDeletion="revert" /> {/* the chip turns back into "@label" and its menu reopens */}

In the Composer Panel

The Pro Composer Panel takes the same triggers: its prompt becomes a Token Input and still sends a string.

Credits

The editing and matching rules are ported from UI SDK's Token Input and Trigger Menu under the Apache License 2.0, with their unit tests. The menu is React Aria's Autocomplete, Menu and Popover in BoardUI's compact dropdown style.