A tag input is a text field that turns strings into chips. Fifteen minutes of work, right? Then someone pastes design, engineering, video and gets one tag. Someone else hits backspace and silently deletes a tag they couldn't see was selected. The component that demos in a day takes a week to stop feeling broken.
Velocity X's standard-tag-input ships the week version: commit keys, paste splitting, two-stage backspace, duplicate rejection — one React file you can read and restyle.
The Money Pattern
All the behaviour lives in one keydown handler plus a paste handler:
const commit = (raw: string) => {
const t = raw.trim().toLowerCase();
if (t && !tags.includes(t)) setTags([...tags, t]);
setDraft('');
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); commit(draft); }
if (e.key === 'Backspace' && draft === '') {
e.preventDefault();
// Two-stage: first backspace SELECTS the last chip, second deletes it
if (pending) { setTags(tags.slice(0, -1)); setPending(false); }
else setPending(true);
} else if (pending) setPending(false);
};
const onPaste = (e: ClipboardEvent) => {
e.preventDefault();
e.clipboardData.getData('text').split(/[,\n]/).forEach(commit);
};
The two-stage backspace is the detail users can't name but always feel: deleting data that's outside the text cursor needs a visible warning state (the last chip highlights), not an instant deletion. It's the same reason your terminal asks before closing a running process.
The Catch
Duplicate policy is a product decision wearing a technical hat. Case-insensitive rejection ("Design" === "design") is right for topic tags and wrong for case-sensitive identifiers — so it belongs in a prop, not buried in the commit function. Same for the separator set: emails want ; and whitespace, topics want commas.
And the chips need to be reachable: each delete button is a real <button aria-label={`Remove ${tag}`}>, not a styled span with an onClick — keyboard users remove tags too.
The Verdict
Tag inputs are the textbook case of "simple UI, deep behaviour". Commit keys, paste splitting, staged backspace, prop-driven duplicate policy — that's the actual spec. The finished file is in Velocity X's Forms category, source you own, beside the OTP input and wizard that round out the hard-input set.









