Designer view

Tag Input

An input that accepts multiple discrete values, rendering each committed value inline as a removable token next to a text input. Distinct from Combobox by emphasising visible accumulated state (the user sees all selected values as tokens), distinct from Select-multi by rendering tokens inline rather than as a count or comma-list, distinct from plain Input by structuring multi-value semantics. Used for email-recipient pickers, label/topic editors, filter-tag composers, multi-attribute search inputs.

When to use

Use

For multi-value inputs where the user benefits from seeing all selected values inline as removable tokens — email recipient pickers, label / topic editors, filter-tag composers, multi-attribute search inputs. The visible-state feedback is the canonical advantage over Combobox-multi (count-display) and Select-multi (no-typeahead).

Avoid

For single-value selection — that is `Combobox`, `Select`, or `Input`. For free-text without multi-value semantics — plain `Input`. For multi-select where the count is sufficient and inline tokens would crowd the layout — `Combobox[multi-select]` with count display. For very long lists (50+ tags) where wrapping breaks the layout — redesign with bulk-import flow.

Versus related

  • combobox

    `Combobox[multi-select]` displays the selection count in the input ("3 selected") rather than as inline tokens. `TagInput` shows the tokens. Use Combobox when screen real estate is constrained or the selection count is bounded; use TagInput when the user benefits from continuously seeing what they have selected.

  • select

    `Select[multi]` is a fixed-list multi-picker without text input; `TagInput` may accept free text (creatable variant) and renders selected values as inline tokens. Select is for bounded fixed lists; TagInput is for open-ended or visible-accumulation use cases.

  • input

    `Input` accepts free-text without structured multi- value semantics — the user types one value. `TagInput` structures multi-value input with explicit per-value tokens, paste-split, and per-value validation.

Highlight
Fig 1.1 · Tag Input · Designer view
Designer

Figma anatomy

Slot Figma type Hint
root frame Auto-layout horizontal frame; wraps tags plus input across multiple rows when overflowing
tag instance Tag/chip component instance with optional remove button; size and color from parent input
tag-remove instance Icon button instance inside the tag; visibility bound to "removable" property
input instance Text input component instance; min-width drives input visibility when many tags present
placeholder text Placeholder text style; only visible in zero-tag empty-input state
Designer

Token usage per slot

root
spacing
  • paddingspacing.compact
  • gapspacing.tight
radius
  • cornerradius.md
color
  • backgroundcolor.surface.bg
  • bordercolor.border.strong
  • ringcolor.border.focus
tag
spacing
  • paddingspacing.tight
  • gapspacing.tight
radius
  • cornerradius.sm
color
  • backgroundcolor.surface.sunken
  • foregroundcolor.text.primary
typography
  • sizetext.sm
tag-remove
spacing
  • paddingspacing.tight
radius
  • cornerradius.sm
color
  • foregroundcolor.text.muted
  • ringcolor.border.focus
input
spacing
  • paddingspacing.tight
color
  • backgroundcolor.surface.bg
  • foregroundcolor.text.primary
typography
  • sizetext.md
placeholder
color
  • foregroundcolor.text.muted
typography
  • sizetext.md
Both

Figma ↔ Code property map

FigmaTypeCodeNotes
VariantVariantvariantMaps free-text / constrained / creatable.
SizeVariantsizesm / md / lg.
RemovableBooleanremovableToggles the per-tag remove button slot. Read-only inputs (display historical tags) set false.
DuplicatesBooleanduplicatesDefault false (reject duplicates). True for use cases where the same value may legitimately appear multiple times (rare; typically false).
Max TagsVariantmaxTagsnone / low / medium / high. Drives the "+N more" affordance threshold and surfaces an inline error when the user attempts to exceed.
Has SuggestionsBooleansuggestionsFor constrained / creatable variants. Toggles the suggestion listbox below the input.
PlaceholderTextplaceholder
Tag LabelTexttagLabelPer-tag content; typically the same as the underlying value but may differ (e.g. value=user-id, label=display-name).
Tag IconInstance SwaptagIconOptional leading icon per tag (avatar for user-tags, color swatch for label-tags).
Designer

Motion

TransitionDuration token
tagEntermotion.duration.fast
tagExitmotion.duration.fast
Easing
motion.easing.standard
Reduced motion
Instant (jump cut)
Designer

Responsive behaviour

BreakpointChange
breakpoint.smAt and below, the input expands to full inline-size of its container (typical mobile pattern). Tags wrap to many rows naturally; consider switching `maxTags` to a lower threshold to surface a "+N more" affordance earlier. For constrained variant, the suggestion listbox may degrade to a bottom-anchored sheet (same pattern as Combobox at this breakpoint).
breakpoint.mdAbove this width, the input renders as authored — fixed inline-size with tags wrapping inside.
Both

Internationalisation

RTL · mirroring

Tag-list inline order reverses logically — first-added tag appears at the inline-start (visual right in RTL). ArrowLeft / ArrowRight keyboard navigation follows logical direction (ArrowLeft moves toward inline-start in both LTR and RTL; canonical model; in RTL contexts SR users may experience this as "moving toward the visual right", which is correct because tag order is also visually- right-first). The remove button on each tag moves to the inline-end of the tag (visual left in RTL).

Text expansion

Tag labels grow with translation; tags wrap to additional rows naturally. Long-text labels (DE / RU / FI) may force wider tags than designed; allow tag inline-size to grow intrinsically. Avoid fixed tag widths. The placeholder text expansion follows Input's expansion rules; consider longer placeholders ("Add tag…" → "Schlagwort hinzufügen…") forcing the input to a minimum inline-size sufficient to show full placeholder.

Both

Variants, properties, states

Variants

Structurally different versions of the component.

free-textconstrainedcreatable

Properties

The same component, parameterised.

PropertyType
removableboolean
duplicatesboolean
sizesm | md | lg
maxTagsnone | low | medium | high

States

Browser/user-driven (interactive) vs. app-driven (data).

KindStates
interactive
hoverfocus-visibleactivedisabled
data
emptyfilledbusyinvalid
Both

State transitions

FromToTrigger
emptyfilledUser commits the first tag — by typing into the input and pressing Enter, comma, or losing focus (consumer- configured commit triggers). The placeholder disappears; the tag-list begins rendering tokens.
filledemptyUser removes the last tag (via the tag's remove button, Backspace at empty input, or programmatic clear). The placeholder reappears.
filledbusyFor constrained / creatable variants with async suggestions, the user types; the suggestion request is in flight. The input shows a busy indicator; the tag list is unaffected.
busyfilledAsync suggestions return; the input shows the listbox with results.
filledinvalidUser attempts to commit a value that violates a constraint (already-present in `duplicates: false` mode, exceeds maxTags, fails a custom validator). The input surfaces an inline error and sets `aria-invalid="true"`.
invalidfilledUser clears the in-flight value or successfully commits a valid alternative. `aria-invalid` is removed.
Both

Figma↔Code mismatches

  1. 01
    Figma

    Tags drawn as static text-with-border decorations next to an input

    Code

    Tags as live DOM elements with remove buttons, accumulated into the form value

    Consequence

    Designers may treat tags as visual decoration. Implementations following the Figma file ship tags as styled spans with no remove affordance and no keyboard contract; users cannot remove tags except by retyping the entire value, and keyboard users cannot remove at all.

    Correct

    Tags are interactive elements with remove buttons; the tag-list is part of the form value, not decoration. The Figma component must encode the remove affordance and the removable state; the canonical reference documents the keyboard contract (Backspace at empty input deletes last tag).

  2. 02
    Figma

    Tag remove button drawn but no keyboard navigation between tags

    Code

    ArrowLeft / ArrowRight from input navigates between tags; Backspace deletes selected tag

    Consequence

    Designers draw the remove × on each tag without considering keyboard navigation. Developers shipping mouse-only remove break a11y for keyboard users — they must Tab through every tag's remove button, with no "select tag, then delete" canonical pattern.

    Correct

    Document the canonical keyboard model: ArrowLeft from empty input selects the last tag (visual highlight), ArrowLeft / ArrowRight navigate between selected tags, Backspace / Delete removes the selected tag. The remove buttons remain mouse-targets; keyboard users use the navigation pattern.

  3. 03
    Figma

    Pasted comma-separated string drawn as a single tag

    Code

    Pasted "a, b, c" splits into three separate tags by canon

    Consequence

    Designers compose mock pastes as single tags; developers following the design ship literal-paste behaviour. Users pasting from spreadsheets or comma-separated lists get a single tag containing the entire pasted string; retyping each tag manually defeats the input's purpose.

    Correct

    Document the canonical paste-split behaviour: pasted text containing commas, semicolons, or newlines splits into multiple tags on commit. Splitter characters are consumer-configurable but the default is `[,;\n]`.

  4. 04
    Figma

    Tag overflow drawn as truncating with ellipsis at the end of the input

    Code

    Tags wrap to multiple rows; the input grows in block-size to accommodate

    Consequence

    Designers draw tag overflow as horizontal-truncate (the input has fixed inline-size and tags clip). Implementations following the design hide tags beyond a count; users cannot see what they have selected. Or developers ignore the design and ship multi-row wrap; the design and production diverge.

    Correct

    Tags wrap naturally to multiple rows; the input grows in block-size. The wrapped layout is canonical because tags represent committed values that must remain visible. For space-constrained contexts, document `maxTags` plus a "+N more" affordance — but never silent clipping.

Designer

Common mistakes

#taginput-no-keyboard-token-removal

Tags cannot be removed by keyboard

Problem

Each tag has a × button reachable only via Tab through the entire tag list, plus there is no Backspace-at- empty-input canonical removal. Keyboard users must laboriously Tab through every tag's remove button; the a11y experience is significantly worse than mouse.

Fix

Implement the canonical keyboard model: Backspace at empty input selects (highlights) the last tag without removing; second Backspace removes it. ArrowLeft from empty input also selects the last tag for navigation. Once a tag is selected, ArrowLeft / ArrowRight navigate between tags; Backspace / Delete remove the selected tag.

#taginput-paste-no-split

Pasting comma-separated text creates a single tag

Problem

User pastes "alice@x, bob@y, carol@z" expecting three tags; gets one tag containing the entire string. Spreadsheet imports and quick-fill flows break.

Fix

On paste, split the pasted text on canonical separator characters (`,`, `;`, newline by default) and commit each segment as a separate tag. Validate each segment individually; segments that fail validation surface as a single "X invalid entries" error rather than aborting the entire paste.

#taginput-duplicates-silent

Duplicate tags accepted silently

Problem

`duplicates: false` is set but committing an already- present value silently no-ops. The input clears, but no error is shown; users do not know their attempted addition was rejected.

Fix

On duplicate-rejection, surface inline-error feedback: the input briefly highlights with an error tone, `aria-invalid="true"` is set, and a hidden status region announces "<value> already added". Avoid silent rejection; document the user-feedback contract canonically.

#taginput-no-aria-on-tags

Tags rendered as styled `<span>`s with no SR label structure

Problem

Tag list is a `<div>` of styled spans. SR users hear everything as plain text; the structure (this is a list of values, this is a remove button) is invisible. Worse, tag count and "X of Y selected" are not announced.

Fix

Render the tag list as a `<ul>` (or `<div role="list">`) with each tag as `<li>`. Each tag's accessible name is its label; the remove button is a real `<button>` with `aria-label="Remove <label>"`. SR navigates by list-item structure and hears each tag plus the remove option.

#taginput-tokens-clipped

Tag overflow silently clipped at input edge

Problem

Input has fixed inline-size; tags accumulate horizontally and clip at the inline-end. Users cannot see what they have committed beyond the visible tags; SR users hear everything but pointer users with vision rely on what they see.

Fix

Tags wrap to multiple rows by canon; the input grows in block-size. For dense layouts, document `maxTags` plus a "+N more" affordance — never silent clipping. Mature primitives (Radix, React Aria) wrap by default; implementations should not override.

Accessibility hints
Slot Accessibility hint
root Root is a layout container; no required ARIA role. The focusable element inside is the `<input>` — clicking the root delegates focus to the input. Wrap with a `<label>` or pair via `aria-labelledby` so SR users hear a name when focus enters.
tag Each tag is a list item inside an implicit list (the tag-list region of the input). For SR users, the tag's accessible name is its label; the remove affordance is an additional button labelled "Remove <tag label>". Tags themselves are not buttons — clicking the tag focuses or selects it, depending on consumer choice; clicking the remove affordance removes the tag.
tag-remove Real `<button>` with accessible name "Remove <tag label>" (composed via aria-label). Activation removes the tag and moves focus to the next tag (or to the input if it was the last tag). Tab from the input lands here only when the input is empty and Backspace navigation has selected the tag for removal.
input For free-text variant, plain `<input type="text">`. For constrained / creatable variants, the input is wired as a Combobox (`role="combobox"`, `aria-controls` referencing the suggestion listbox, `aria-activedescendant` for the highlighted suggestion). The accessible name comes from the wrapping label; the input's value is the in-flight text only — committed values live in the tag list.
placeholder Placeholder is not the input's accessible name (the wrapping label is). Avoid placeholder-only labelling — when typing or after committing tags the placeholder disappears and SR users have nothing to identify the field from.