Skip to content
txbrendan/ui

Token Field

beta

A field for entries that each resolve to one real thing, like emails and people. A finished entry becomes a soft token, a known person shows with their photo, and a bad email turns red.

Preview

entries

dan.kerr@mail.comTom HaleMaya Patel

Type an email and press Enter or a comma.

Try “Riley Shaw”.

size

dan.kerr@mail.com
dan.kerr@mail.com
dan.kerr@mail.com

states

dan.kerr@mail.comtom.hale@mail, not a valid email address
dan.kerr@mail.com

Invites are closed.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/token-field

Use it for

  • Inviting people by email, or adding known people to a team or a message.
  • An entry is finished by a comma, Enter, Tab, a paste, or leaving the field. Backspace in the empty field removes the last token.
  • Pass people with { name, email, photo }. An entry that matches a person's email or name shows as them. Without a photo it shows their initials.
  • An entry with an @ that is not a valid email shows red, and says so in its tooltip and to screen readers. Say what to fix in error.
  • Text that is not an email and matches no person stays as text. It never becomes a token.
  • value is the list of entries as typed. Check each one on your server too.

Avoid it for

  • Free tags or labels people make up. This field only takes entries that resolve to someone or something real.
  • Picking from a fixed list. Use a combobox with multiple.
  • One email address. Use an input with type="email".

Props

  • value

    The entries. Use it with onValueChange.

    string[]

  • defaultValue

    The entries at first render, when you do not control them.

    string[]

  • onValueChange

    Called with the new list each time an entry is added or removed.

    (tokens: string[]) => void

  • people

    Known people. A matching entry shows as them, with their photo or initials.

    { name: string; email?: string; photo?: string }[]

  • placeholder

    Shown while the field is empty. Also names the field when there is no label.

    string

  • label

    Names the field, above it.

    ReactNode

  • hint

    A short note under the field.

    ReactNode

  • error

    Replaces the hint, in red, and is announced.

    ReactNode

  • size

    Height, padding, and text size. The same heights as Input.

    "sm" | "md" | "lg" · default "md"

  • disabled

    Blocks typing, and hides the remove buttons.

    boolean · default false

  • className

    Classes for the frame. Other props, like name and aria-*, land on the input.

    string

Accessibility

  • The text field is a native input, named by the label, or by the placeholder when there is no label.
  • Each token reads as its name. A bad email adds ", not a valid email address" for screen readers, and a tooltip for the mouse.
  • Each token has a remove button named "Remove" plus the token, with a tooltip that says the same. It is not a Tab stop, since Backspace removes from the keyboard.
  • A polite live region says what was added or removed.
  • The × shows on hover with a mouse, and always on touch screens, where nothing hovers.
  • On touch screens the text is 16px, so iOS Safari does not zoom in when the field gets focus.

Examples

Invite by email

<TokenField
  label="Invite"
  placeholder="Add email addresses"
  defaultValue={["dan.kerr@mail.com", "tom.hale@mail.com"]}
  hint="Separate them with commas."
/>

Known people

<TokenField
  label="People"
  placeholder="Add people by name or email"
  people={[
    { name: "Maya Patel", email: "maya@mail.com" },
    { name: "Tom Hale", email: "tom.hale@mail.com" },
  ]}
  defaultValue={["maya@mail.com"]}
/>