Built on Vercel’s UI SDK 0.1.0-beta.1. Its API may change.
Token input
A text field on Vercel's UI SDK that mixes typed text with tokens for people, files and commands, with @ and / suggestions.
- Form components
- 15 parts
- 10 examples
@tabler/icons-react@ui-sdk/core@ui-sdk/react
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-token-input.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @ui-sdk/react@0.1.0-beta.1 @ui-sdk/core@0.1.0-beta.1 @tabler/icons-react cnCopy and paste the following code into your project.
components/ui/ui-sdk-token-input.tsx Update the import paths to match your project setup.
UI SDK adds a little CSS of its own in a cascade layer named ui-sdk. Declare that layer at the top of your global stylesheet, above the Tailwind import, so your classes win over it.
A value is an array of text and tokens. Each token has a type, a value for your app and a label people read. The editor owns its content, selection and undo, so let it render and pass renderToken to change how a token looks. Without one, each token shows its label and a remove button.
A TriggerMenu inside the input suggests tokens when someone types a trigger like @ or /. Leave it out when your code supplies the tokens.
Type @ for files or / at the start for skills. A file is pending while it loads, and sending marks each skill as running, then done, with updateToken(). Enter sends and Shift+Enter adds a line.
tokenDeletion sets what Backspace does at a token. whole removes it, select selects it first, and revert turns it back into text.
A controller from createTokenInputController() inserts, updates and removes tokens from code. Pass a promise as resolve to keep a token pending until it settles.
With a name, the input submits its text with each token written as [@label](type:value). required blocks an empty submit, and a reset goes back to defaultValue.
With submitShortcut="mod+enter", Enter adds a line and Command or Control+Enter sends, which many Japanese and Chinese writers prefer.
A codec turns tokens into text and back, here as <@id>. With copy="label", copying writes what people read, and pasting back still restores the tokens.
A disabled input can’t be focused or changed, hides the remove buttons and submits nothing.
With readOnly, the text stays focusable, selectable and submitted, but typing, pastes and drops leave it alone.
The editor follows the page’s direction, and a token stays one piece inside right-to-left text.
| Key | Action |
|---|---|
| BackspaceDelete | Removes the token before or after the caret, as tokenDeletion says. |
| ←→ | Moves the caret past a token in one press. The caret never stops inside one. |
| ↑↓ | Moves through suggestions while the menu is open. |
| Enter | Sends. While the menu is open, chooses the highlighted suggestion. |
| Shift + Enter | Starts a new line. |
| ⌘ + EnterCtrl + Enter | Sends when submitShortcut is mod+enter. Enter then starts a new line. |
| ⌘ + ZCtrl + Z | Undoes the last step. Typing is grouped, and pastes and token changes are steps of their own. |
| Esc | Closes the suggestions. |
The editor is a multiline text box. A trigger menu inside it adds aria-autocomplete and aria-haspopup, and focus stays in the text while the arrow keys move through suggestions. Each token reads as its label and its kind, like “routes.ts file”, and inserting or removing one is announced. Name the editor with aria-label or a label around it.
Remove buttons are hidden from screen readers, since Backspace removes a token from the keyboard. Their hit area grows on touch screens, a pending token only pulses when motion is allowed, and a selected token gets a system highlight border in forced colors mode.
Each part wraps the UI SDK part of the same name and accepts all of its props, including render and a className that can be a function of the part’s state.
| Prop | Type | Default |
|---|---|---|
value | TokenInputValue | – |
defaultValue | TokenInputValue | – |
onValueChangeRuns on typing, pastes, menus, undo and controller calls. details lists the reason and any removed tokens. | (value, details) => void | – |
onSubmitRuns on the submit shortcut, unless a menu is open or an input method is composing. | (value, details) => void | – |
submitShortcut | "enter" | "mod+enter" | "enter" |
tokenDeletion | "whole" | "select" | "revert" | "whole" |
renderTokenRenders a token. Defaults to a TokenInputToken with its label and a remove button. | (token: Token) => ReactNode | – |
copy | "codec" | "label" | "codec" |
codec | TokenCodec | – |
getTokenDescriptionWhat a token says to screen readers besides its label. | (token: Token) => string | – |
history | { limit, groupDelay } | – |
name | string | – |
form | string | – |
required | boolean | false |
disabled | boolean | false |
readOnly | boolean | false |
controllerFrom createTokenInputController(). | TokenInputController | – |
| Prop | Type | Default |
|---|---|---|
placeholderShown through aria-placeholder while the input is empty. | string | – |
render | ReactElement | (props, state) => ReactElement | – |
| Attribute | Description |
|---|---|
data-slot="token-input-editor" | Target the editor in CSS. |
data-empty | Present while the input holds nothing. |
data-disabled | Present when disabled. |
data-readonly | Present when read-only. |
| Attribute | Description |
|---|---|
data-slot="token-input-token" | Target a token in CSS. |
data-token-type | The token’s type, like file or mention. |
data-selected | Present while the token is in the selection, or selected for removal. |
data-pending | Present while the token loads. |
data-unresolved | Present while the token needs the user, like an ambiguous mention. |
Removes its token and keeps focus in the editor. It renders a Tabler X icon unless you pass children, and hides itself while the input is disabled or read-only.
| Prop | Type | Default |
|---|---|---|
triggersThe characters that open the menu, the token type each inserts and its items. | TriggerDefinition[] | – |
onSelectRuns when an item is chosen. Return a promise to insert the token as pending. | (value, details) => void | Promise | – |
autoHighlight | boolean | true |
loop | boolean | true |
| Prop | Type | Default |
|---|---|---|
align | "start" | "center" | "end" | "start" |
sideOffset | number | 6 |
collisionPadding | number | 8 |
| Attribute | Description |
|---|---|
data-slot="trigger-menu-content" | Target the popup in CSS. |
data-trigger | The trigger of the current match, like @, so each list can look different. |
data-starting-style | Present while the popup animates in. |
data-ending-style | Present while the popup animates out. |
| Prop | Type | Default |
|---|---|---|
value | T | – |
labelThe text inserted and matched, when the content isn’t plain text. | string | – |
disabled | boolean | false |
| Attribute | Description |
|---|---|
data-highlighted | Present on the highlighted item. Focus stays in the editor. |
data-disabled | Present when the item can’t be chosen. |
- CalendarA date grid for single, range and multiple selection, with sliding months, range previews and touch-sized days.
- CheckboxA checkbox whose check draws in, with indeterminate parents, groups and labels that share its hover.
- ComboboxA filterable select with chips, groups and async results, in a popup that resizes as you type.
- Date pickerA button that opens a calendar in a popover, or a bottom sheet on phones, for single dates and ranges.
- FieldLabels, descriptions and errors wired to their control, with validation states and layouts for forms.
- InputA text input with three sizes, invalid and read-only states, native validation styling and a 16px touch font so phones never zoom in.