“Build a tags input” is a frontend machine-coding question that looks easy and rewards careful thinking. The user types, hits Enter or comma, and the text becomes a styled chip; backspace removes the last chip; paste inserts multiple chips. Used in Gmail recipients, Stack Overflow tags, Linear labels, GitHub repo topics. The interview signals are pointer/keyboard handling, paste parsing, and accessibility.
Clarify scope
- Free-text tags, or pick from a list (autocomplete)?
- Allow duplicates?
- Validate format (no spaces, max length, alphanumeric)?
- Editable chips (click to edit) or static?
- Drag-to-reorder?
Architecture
One component, three concerns:
- State: array of tag strings
- Input: a contenteditable or input that lives at the end
- Render: chips before the input, with delete X on each
Adding a tag
Trigger on:
- Enter key (preventDefault to avoid form submit)
- Comma typed — strip the comma, add the tag
- Tab (Gmail-style commit-and-move-on)
- Blur of the input (commit pending text)
Validate before committing: trim, check length, check format, check duplicate. Show inline error if invalid; do not silently drop input.
Removing a tag
- Click the X on the chip
- Backspace when input is empty: select last chip; second backspace deletes it (Gmail pattern)
- Selected-chip delete: arrow keys navigate chips, Enter or Delete removes
Paste handling
Paste is where junior implementations break:
- User pastes “alice@x.com, bob@y.com, charlie@z.com” — should produce three chips
- User pastes “single value” — produces one chip
- User pastes ” spaced , values ” — trim and split, drop empties
Split on common separators (comma, semicolon, newline, tab). Validate each. Reject invalid ones with a clear summary (“3 added, 2 invalid: …”).
Autocomplete variant
If picking from a list, layer in the combobox pattern:
- Dropdown of matching options below the input
- Arrow keys navigate
- Enter inserts highlighted option
- Free-text creation as a special “+ Create new” item at the bottom
Accessibility — the WAI-ARIA pattern
- Container has
role="combobox"when autocomplete; otherwise just a labeled group - Each chip is
role="button"with explicit label “Remove tag X” - Selected chip has
aria-selectedwhen keyboard-navigated - Live region announces additions and removals (
aria-live="polite") - Visible focus ring on the focused chip or input
Visual states
- Default: pale background, dark text, X on hover
- Selected (keyboard nav): primary color border
- Invalid: red border + tooltip with reason
- Dragging (if reorderable): elevation / shadow
Mobile considerations
- Comma key on mobile keyboards is buried; rely on the soft “Done” / Enter
- Backspace-to-delete-chip is awkward on mobile; rely on the X button being big enough
- Paste is more common on mobile than desktop in some flows; test it
Edge cases interviewers love
- User types past the input width — chips wrap to next line
- User pastes 1000 emails — show progress, validate in chunks, do not freeze the UI
- IME composition (Japanese, Chinese) — do not commit on Enter during composition
- Initial value with already-set tags — render correctly on mount
What separates senior from staff
Junior: types and removes work. Senior: paste handles bulk input cleanly, accessibility is correct, validation is meaningful. Staff: discusses the API for a controlled vs uncontrolled component, the typing surface for valid tag types (string, object, custom), and the upstream error handling integration.
Frequently Asked Questions
Library options for production?
react-tag-input, react-select (creatable variant), or downshift for a custom build. Most senior teams roll their own because requirements diverge enough to make libraries painful.
How do I handle “too many tags”?
Cap and show “+N more” after a threshold; or scroll horizontally; or drop oldest. Whichever you pick, surface the limit in the UI before they hit it.
Should chips be editable?
Click-to-edit is nice but adds complexity. Most products skip it; users delete and retype.