alpha

TokenField

A token field allows users to enter text with inline tokens. Use it to build AI prompt fields, tag inputs, structured search fields, mention inputs, and multi-select comboboxes.

Theme 
Message
This example automatically tokenizes #hashtags and @usernames in the text.
 
 
allowsNewlines 
isDisabled 
Example
TokenField.tsx
TokenField.css
TokenizingFieldValue.ts
import {Token, TokenField} from './TokenField';
import {TokenizingFieldValue} from './TokenizingFieldValue';

<TokenField
  label="Message"
  allowsNewlines
  defaultValue={TokenizingFieldValue.tokenize(
    'This example automatically tokenizes #hashtags and @usernames in the text.',
    /(?<=\s|^)[#@]\S+(?=\s)/g
  )}>
  {segment => <Token>{segment.text}</Token>}
</TokenField>

Value

TokenField is controlled via a TokenFieldValue value, which represents a sequence of text and token segments. Use the value or defaultValue prop to set the value, and onChange to handle user input.

Message
Hello @username!

Value: Hello @username!

import {Token, TokenField} from './TokenField';
import {TokenFieldValue} from 'react-stately/useTokenFieldState';
import {useState} from 'react';

function Example() {
  let [value, setValue] = useState(
    new TokenFieldValue([
      {type: 'text', text: 'Hello '},
      {type: 'token', text: '@username'},
      {type: 'text', text: '!'}
    ])
  );

  return (
    <>
      <TokenField value={value} onChange={setValue} label="Message">
        {segment => <Token>{segment.text}</Token>}
      </TokenField>
      <p>Value: {value.toString()}</p>
    </>
  );
}

Tokenization

Extend the TokenFieldValue class to customize how text is converted into tokens. Override the tokenize method to parse user input, and use createFieldValue to return new instances of your subclass when the value changes.

Message
This example automatically tokenizes #hashtags and @usernames in the text.
import {Token, TokenField} from './TokenField';
import {TokenizingFieldValue} from './TokenizingFieldValue';

<TokenField
  allowsNewlines
  defaultValue={TokenizingFieldValue.tokenize(
    'This example automatically tokenizes #hashtags and @usernames in the text.',
    /(?<=\s|^)[#@]\S+(?=\s)/g
  )}
  label="Message">
  {segment => <Token>{segment.text}</Token>}
</TokenField>

Autocomplete

Combine TokenField with Autocomplete to provide inline completions such as @mentions and slash commands. Use TokenFieldValue.findText to locate the anchor character, and tokenFieldPositionToDOMRange to position a Popover relative to the filter text.

Prompt
This example has autocomplete for @usernames and /commands
import {Autocomplete} from 'react-aria-components/Autocomplete';
import {Text} from 'react-aria-components/Text';
import {Token, TokenField} from './TokenField';
import {tokenFieldPositionToDOMRange} from 'react-aria/useTokenField';
import {TokenFieldValue} from 'react-aria-components/TokenField';
import {Menu, MenuItem} from './Menu';
import {Popover} from './Popover';
import {useMemo, useRef, useState} from 'react';

type Item = {username: string} | {command: string; description: string};

function Example() { let inputRef = useRef(null); let [value, setValue] = useState( new TokenFieldValue([ {type: 'text', text: 'This example has autocomplete for '}, {type: 'token', text: '@usernames'}, {type: 'text', text: ' and '}, {type: 'token', text: '/commands'} ]) ); let [filterAnchor, filterValue] = useMemo(() => { let filterAnchor = value.findText(value.caretPosition, TokenFieldValue.Direction.Backward, /(?<=^|\s)[@/]/); if (filterAnchor != null) { let filterValue = value.slice(filterAnchor, value.caretPosition).toString(); return [filterAnchor, filterValue]; } return [null, null]; }, [value]); let items: Item[] = []; if (filterValue != null && filterValue.startsWith('/')) { items = slashCommands.filter(item => item.command.includes(filterValue.slice(1))); } else if (filterValue != null && filterValue.startsWith('@')) { items = usernames.filter(item => item.username.includes(filterValue.slice(1))); }
return ( <Autocomplete> <TokenField value={value} onChange={setValue} label="Prompt" allowsNewlines inputRef={inputRef}> {segment => <Token>{segment.text}</Token>} </TokenField> <Popover triggerRef={inputRef} isOpen={filterAnchor != null && items.length > 0} isNonModal hideArrow placement="bottom start" trigger="MenuTrigger" getTargetRect={target => { return tokenFieldPositionToDOMRange(target, filterAnchor!).getBoundingClientRect(); }}> <Menu items={items} dependencies={[filterAnchor]}> {item => ( <MenuItem id={'username' in item ? item.username : item.command} onAction={() => { setValue(value => value.replaceRangeWithSegments( filterAnchor!, value.caretPosition, [ { type: 'token', text: 'username' in item ? '@' + item.username : item.command }, {type: 'text', text: ' '} ], false ) ); }}> <Text slot="label">{'username' in item ? item.username : item.command}</Text> {'description' in item ? <Text slot="description">{item.description}</Text> : null} </MenuItem> )} </Menu> </Popover> </Autocomplete> ); }

Tag field

Use a custom TokenFieldValue to build a tag input. This example converts comma, space, or newline separated text into tokens.

Categories
ArchitectureDesignDevelopmentMarketingSales
Example
TagFieldValue.ts
import {Token, TokenField} from './TokenField';
import {TagFieldValue} from './TagFieldValue';

<TokenField
  allowsNewlines
  defaultValue={
    new TagFieldValue([
      {type: 'token', text: 'Architecture'},
      {type: 'token', text: 'Design'},
      {type: 'token', text: 'Development'},
      {type: 'token', text: 'Marketing'},
      {type: 'token', text: 'Sales'}
    ])
  }
  label="Categories">
  {segment => <Token>{segment.text}</Token>}
</TokenField>

Token fields can be used to build advanced search UIs with structured query tokens. This example suggests filters based on the current text segment.

Search
import {Autocomplete} from 'react-aria-components/Autocomplete';
import {Collection} from 'react-aria-components/Collection';
import {Token, TokenField} from './TokenField';
import {Header, Menu, MenuItem, MenuSection} from './Menu';
import {Popover} from './Popover';
import {useRef, useState} from 'react';
import {TokenFieldValue} from 'react-stately/useTokenFieldState';

function Example() { let inputRef = useRef(null); let [value, setValue] = useState( new TokenFieldValue([{type: 'token', text: 'From: Alice Smith'}]) ); let last = value.segments[value.caretPosition.index]; let filterText = last?.type === 'text' ? last.text : null; let suggestions: {name: string; items: string[]}[] = []; if (filterText != null) { let users = usernames .filter(item => item.username.includes(filterText)) .map(u => u.username) .slice(0, 5); if (users.length > 0) { if ( !value.segments.some( segment => segment.type === 'token' && segment.text.startsWith('From: ') ) ) { suggestions.push({ name: 'From', items: users }); } suggestions.push({ name: 'To', items: users }); } suggestions.push({ name: 'Subject', items: [filterText] }); } return ( <Autocomplete> <TokenField role="searchbox" value={value} onChange={setValue} label="Search" inputRef={inputRef}> {segment => <Token>{segment.text}</Token>} </TokenField> <Popover triggerRef={inputRef} isOpen={suggestions.length > 0} isNonModal hideArrow placement="bottom start" style={{width: 'var(--trigger-width)'}} trigger="MenuTrigger"> <Menu items={suggestions}> {section => ( <MenuSection> <Header>{section.name}</Header> <Collection items={section.items}> {item => ( <MenuItem id={section.name + '-' + item} onAction={() => { setValue(value => value.replaceRangeWithSegments( {index: value.caretPosition.index, offset: 0}, value.caretPosition, [{type: 'token', text: section.name + ': ' + item}], false ) ); }}> {item} </MenuItem> )} </Collection> </MenuSection> )} </Menu> </Popover> </Autocomplete> ); }

ComboBox

Use TokenField as the input for a ComboBox to build a multi-select field with tokens. Synchronize the TokenFieldValue with the ComboBox value and inputValue props using a custom segment list class.

Example
ComboBoxTokenFieldValue.ts
import {ComboBox} from 'react-aria-components/ComboBox';
import {Token, TokenField} from './TokenField';
import {ComboBoxItem, ComboBoxListBox} from './ComboBox';
import {FieldButton, Label} from './Form';
import {Popover} from './Popover';
import {ComboBoxTokenFieldValue} from './ComboBoxTokenFieldValue';
import {ChevronDown} from 'lucide-react';
import {useState} from 'react';

function Example() { let [value, setValue] = useState(new ComboBoxTokenFieldValue([])); return ( <ComboBox selectionMode="multiple" style={{width: 500, maxWidth: '100%'}} value={value.getSelectedKeys()} inputValue={value.getInputValue()} onChange={keys => setValue(value.setSelectedKeys(keys))}> <Label>Users</Label> <div className="combobox-field"> <TokenField value={value} onChange={setValue} placeholder="Select users" style={{paddingInlineEnd: 36}}> {segment => <Token>{segment.text}</Token>} </TokenField> <FieldButton> <ChevronDown /> </FieldButton> </div> <Popover hideArrow className="combobox-popover"> <ComboBoxListBox items={usernames}> {state => <ComboBoxItem id={state.username}>{state.username}</ComboBoxItem>} </ComboBoxListBox> </Popover> </ComboBox> ); }

API

<TokenField>
  <Label />
  <TokenInput>{segment => <Token />}</TokenInput>
  <Text slot="description" />
</TokenField>

TokenField

NameType
allowsNewlinesboolean

Whether the token field allows newlines.

isReadOnlyboolean

Whether the token field is read only.

isDisabledboolean

Whether the token field is disabled.

children<>

The children of the component. A function may be provided to alter the children based on component state.

value

The current value (controlled).

defaultValue

The default value (uncontrolled).

onChange(value: ) => void

Handler that is called when the value changes.

Default className: react-aria-TokenField

Render PropCSS Selector
isDisabledCSS Selector: [data-disabled]
Whether the token field is disabled.
isReadOnlyCSS Selector: [data-readonly]
Whether the token field is read only.

TokenInput

NameType
children(segment: < extends <any> ? V : never>) => React.ReactElement

A function that renders a token for each segment in the token field.

isDisabledboolean

Whether the hover events should be disabled.

Default className: react-aria-TokenInput

Render PropCSS Selector
isHoveredCSS Selector: [data-hovered]
Whether the token input is currently hovered with a mouse.
isFocusedCSS Selector: [data-focused]
Whether the token input is focused, either via a mouse or keyboard.
isFocusVisibleCSS Selector: [data-focus-visible]
Whether the token input is keyboard focused.
isDisabledCSS Selector: [data-disabled]
Whether the token input is disabled.
isReadOnlyCSS Selector: [data-readonly]
Whether the token input is read only.

Token

NameType
children<>

The children of the component. A function may be provided to alter the children based on component state.

Default className: react-aria-Token

Render PropCSS Selector
isSelectedCSS Selector: [data-selected]
Whether the token is selected.
isDisabledCSS Selector: [data-disabled]
Whether the token is disabled.

TokenFieldValue

Properties

NameType
Directionany
segmentsreadonly <T>[]

The text and token segments in the list.

caretPosition

The caret position.

Methods

constructor(tokens: readonly <T>[], options?: ): void
Create a new list with the given segments.
withCaretPosition(caretPosition: ): this
Create a new list with the caret position set to the given position.
replaceRange( start: , end: , text: string, coalesce: any ): this
Replace the text between two positions with new text.
replaceRangeWithSegments( start: , end: , insert: <T>[], coalesce: any ): this
Replace the text between two positions with new segments.
findBoundaryWithSegmenter( position: , segmenter: Intl.Segmenter, direction: any ): null
Find the boundary before or after a position using an Intl.Segmenter.
findLineBoundary(position: , direction: any): null
Find a line boundary before or after a position.
findText( position: , direction: any, search: stringRegExp ): null
Find a string or regular expression match before or after a position.
delete( position: , segmenter: Intl.Segmenter, direction: any, coalesce: any ): this
Delete text at a position using a segmenter.
deleteLine( position: , direction: any, coalesce: any ): this
Delete text to the next or previous line break.
slice(start: , end: ): this
Create a new list containing a subset of the segments.
toString(): string
Convert the list to a string.
undo(): this
Returns the previous list in the undo history.
redo(): this
Returns the next list in the redo history.
endCoalescing(): void
End coalescing undo/redo history.