Skip to content

Components · Forms

TagInput

Chips for a list of short values: Enter or comma to add, paste to split, keys to remove.

@rookcom/monarch-reactComponent

Skills with suggestions

  • Research
  • TypeScript

Usage examples

Validated emails

Read-only and small

Purpose

Edits a list of short free-form values (skills, labels, keywords, email recipients) as removable chips in one field, with validation, paste splitting and optional suggestions.

Use when

  • The user types several values into one field: labels, skills, Cc addresses.
  • Values are mostly free text, perhaps with suggestions of known ones.

Avoid when

  • Values must come from a fixed list: use a multi-select.
  • Each value has several properties to edit: use a list or table with an add row.
  • Only one value is allowed: use Input or Combobox.

Design principles

  • The product owns the rules: normalize and validate decide what a valid tag is; the component handles entry, display and removal.
  • A rejected value stays in the field with a message, so the user can fix it instead of retyping.
  • Removing is never one stray key away: the first Backspace in an empty field moves to the last chip, the second removes it.
  • Chips are quiet, neutral surfaces; colour is left for status, not decoration.

API reference

import { TagInput } from '@rookcom/monarch-react';
ExportKindRenderingRoot class
TagInputcomponentclient.mn-tag-input

Renders a .mn-tag-input wrapper containing the chip control (.mn-tag-input__control, styled as .mn-input), the message and a hidden status region. The ref and native input props go to the text input; className goes to the wrapper.

With suggestions, the text input becomes a combobox and the list is a Radix Popover, as in Combobox.

With name, one <input type="hidden"> per tag is rendered for native form posts.

Generated from the TypeScript declarations of packages/react/src/components/forms/tag-input.

Properties

validate(tag, tags) returns a message to reject the tag, or null/undefined to accept it. It runs after normalize, the duplicate check and max.

TagInput

Edits a list of short values as removable chips: skills, labels, recipient addresses. Enter or a separator adds the typed text, pasted lists are split, a validator can reject values, and optional suggestions appear in a listbox.

PropTypeDescription
addOnBlurbooleanCommit the typed text when the field loses focus. Default false.
allowDuplicatesbooleanAllow the same tag twice. Duplicates are compared case-insensitively. Default false.
childrenReactNode
classNamestringClass for the outer wrapper (the text input is `.mn-tag-input__input`).
defaultValuestring[]Initial tags when uncontrolled.
disabledboolean
filterSuggestions((suggestion: string, query: string) => boolean)Which suggestions match the typed text. Default: case-insensitive substring. Pass `() => true` for server-filtered lists.
idstring
inputValuestringText being typed (controlled). Use it to fetch suggestions from a server.
invalidbooleanMarks the value invalid. Inside a Field, the Field's `invalid` is used.
maxnumberMaximum number of tags. Further additions are rejected with `messages.max`.
messagesPartial<TagInputMessages>Override the built-in messages (rejections and screen reader announcements).
namestringForm field name: one hidden input per tag is rendered with this name.
normalize((raw: string) => string)Cleans typed text before it is checked and added. Default: trims whitespace.
onInputValueChange((text: string) => void)
onValueChange((tags: string[]) => void)Called with the full new list whenever a tag is added or removed.
removeLabel((tag: string) => string)Accessible name of each tag's remove button. Default `Remove <tag>`.
separatorsstring[]Characters that commit the typed text as a tag, besides Enter. They also split pasted text, together with new lines and tabs. Default `[',']`.
size"sm" | "md" | "lg"
suggestionsstring[]Values offered in a listbox while typing. Already added tags are left out.
validate((tag: string, tags: readonly string[]) => string | null | undefined)Checks a tag before it is added. Return a message to reject it (shown under the field and announced), or nothing to accept it.
valuestring[]The tags (controlled).

Variants

PropValuesDescription
size'sm''md''lg'Minimum height of the control from --mn-control-*. The control grows as chips wrap.

States

StateExposed asBehaviour
Rejected entryaria-invalid="true" on the inputThe message appears under the field and is linked with aria-describedby; it clears when the user types.
Invaliddata-invalid on the controlDanger border, from invalid, the surrounding Field or a rejected entry.
Disableddata-disabled on the control, disabled on the inputChips stay visible; remove buttons are not rendered.
Read-onlyreadonly on the inputChips are shown without remove buttons and nothing can be added.
Suggestions openaria-expanded="true" on the inputA listbox of matching suggestions below the field.

Interaction behaviour

  • Enter or a separator (default comma) adds the typed text after normalize, the duplicate check (case-insensitive), max and validate.
  • Pasting text that contains a separator, a new line or a tab adds each part; parts that are rejected stay in the field, joined by commas.
  • Clicking a chip’s remove button removes it and returns focus to the input.
  • Additions and removals are announced in a polite status region ("Added Research.").
  • With suggestions, typing opens a listbox of matches that are not already added; clicking one adds it.
  • addOnBlur commits the typed text when focus leaves the field.
  • Controlled with value + onValueChange (and optionally inputValue + onInputValueChange), or uncontrolled with defaultValue.

Keyboard support

KeysAction
EnterAdds the typed text, or the highlighted suggestion.
, (or any `separators` key)Adds the typed text.
Backspace (empty field)Moves focus to the last chip’s remove button; Backspace again removes it.
ArrowLeft (caret at start)Moves focus to the last chip.
ArrowLeft / ArrowRight (on a chip)Moves between chips; ArrowRight past the last chip returns to the input.
Backspace / Delete (on a chip)Removes it and focuses the previous chip (Backspace) or the next one (Delete).
ArrowDown / ArrowUpOpens and moves through suggestions.
EscapeCloses the suggestions.

Accessibility

Role
textbox, or combobox with a listbox when suggestions is set; chips are a list
Accessible name
A FieldLabel or aria-label on the field. Each remove button is named by removeLabel (default "Remove <tag>").
  • Chips are list items, so screen readers announce how many there are.
  • Remove buttons are real buttons in the Tab order, so the arrow-key shortcuts are a convenience, not the only way.
  • Suggestions follow the APG combobox pattern: the input keeps focus and points at the highlighted option with aria-activedescendant.
  • Rejection messages are linked to the input and announced; additions and removals are announced in a separate status region.

Responsive behaviour

  • Chips wrap onto new lines and the control grows; the text input keeps at least 6rem so there is always room to type.
  • Long tags are truncated with an ellipsis inside their chip.

Theme support

  • Chips use --mn-color-bg-muted and --mn-color-border-subtle; the message uses --mn-color-danger-strong. All are semantic tokens, so light, dark and more-contrast themes apply.
  • Remove-button hover transitions are removed under prefers-reduced-motion.

Do and don't

Do

  • Say in a FieldDescription how values are separated when it is not just commas.
  • Write rejection messages that say what is wrong with that value.
  • Pass normalize when case or spacing should not create a new tag.

Don't

  • Do not use it for a fixed set of options; a multi-select makes the choices visible.
  • Do not hide the reason a value was rejected; return a message from validate.
  • Combobox: Pick a single value from a list.
  • Input: A single free-text value.
  • Filter Chips (stable): Chips that show active filters rather than edit a value.
  • Field: Label, help and error wiring.

Dependencies

KindDepends on
npm packagesreact@radix-ui/react-popover
Monarch componentsField

Uses the .mn-input styles from text-field for the control and .mn-menu for suggestions.

Testing coverage

  • Adds on Enter and comma; trims and ignores blank entries; labels remove buttons; announces additions.
  • Splits pasted lists; leaves pastes without separators to the browser.
  • A validator rejection keeps the text, marks the input invalid and links the message; typing clears it.
  • Rejects duplicates case-insensitively and enforces max.
  • Backspace moves to the last chip, then removes it; arrows move between chips; Delete removes; focus lands sensibly.
  • Click removal returns focus to the input.
  • Suggestions: listbox, already-added values left out, arrow navigation with aria-activedescendant, Enter and click to add, Escape to close.
  • Controlled tags and typed text; addOnBlur; hidden inputs for name.
  • Disabled (from Field) and read-only render no remove buttons.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery; Cross-browser axe: /components/tag-input

Tests: packages/react/src/components/forms/tag-input