@akhatmirzo/ui

TagInput

Enter a list of tags, keywords or emails: add with Enter or a comma, paste, suggestions, validation and inline editing.

  • React
  • TypeScript

Import

import { TagInput } from '@akhatmirzo/ui';

When to use

  • A list of free-form values — tags, keywords, skills, several emails or phone numbers.
  • When likely values are known — pass suggestions: a list appears while typing, but users can still add their own value.
  • When users must pick only from a list — use Combobox with multiple; it doesn't let users create new values.
  • For a single value, use a plain Input.

With Field

Inside a Field the label points at the inner <input>, and the description and error go into aria-describedby. value + onValueChange is the controlled mode, defaultValue the uncontrolled one.

Enter yoki vergul bilan qo'shing

  • dizayn
  • frontend

Qiymat: ["dizayn","frontend"]

Adding and removing

  • Enter or , turns the typed text into a tag (change this with delimiters, e.g. ['Enter', ',', 'Tab']). On an empty input Enter submits the form as usual.
  • Paste: "a, b, c" → 3 tags. Text is split on single-character delimiters and, when Enter is a delimiter, on new lines.
  • Duplicates are rejected — the comparison ignores case (allow them with allowDuplicates).
  • transform turns raw text into a tag (default — trim).
  • Double-click a tag (or press Enter / F2 while it's focused) to edit it in place: Enter saves, Esc cancels, clearing the text removes the tag.

Suggestions

With suggestions, matching options show up while typing. Matching uses normalizeUz: Cyrillic and Latin script and apostrophe variants are treated the same — typing «тош» finds «Toshkent». Tags that are already added are hidden. Move with ↓ / ↑, Enter adds; when nothing is highlighted, Enter adds the typed text itself.

maxTags caps the number of tags. Once the limit is reached the input hides and the container gets data-full.

Ko'pi bilan 4 ta. «тош» deb yozib ko'ring

Validation

validate runs for every new tag: true accepts it, false shows the default error (labels.invalid), a string shows that string. A rejected tag isn't added, the text stays in the input, the error appears below it and is announced to screen readers, and the input gets aria-invalid. The error clears on the next change.

Bir nechta emailni vergul bilan qo'ying

The validation error is separate from the Field error prop — TagInput renders it itself. Pass form-level errors (e.g. "At least one tag is required") through Field error.

Sizes and states

size — sm, md (default), lg: the same minimum height as Input (32 / 40 / 48px); the container grows as tags wrap. In disabled and readOnly states the ✕ buttons are hidden.

  • sm
  • md
  • lg
  • O'chirilgan

Forms

With name, every tag gets a hidden <input> with that name: new FormData(form).getAll('tags') → ['React', 'Vue']. For PHP-style arrays use name="tags[]".

Props

className goes to the root container; ref and other <input> attributes (placeholder, autoFocus, onBlur ...) go to the inner input.

PropTypeDefaultDescription
valuestring[]—Tags (controlled)
defaultValuestring[][]Initial tags
onValueChange(value: string[]) => void—Called when the list changes
delimitersstring[]['Enter', ',']Keys and characters that finish a tag ('Tab' is allowed too)
maxTagsnumber—Maximum number of tags
allowDuplicatesbooleanfalseAllow duplicate tags (compared case-insensitively)
validate(tag: string) => boolean | string—false or a message — the tag is rejected and the error is shown
transform(raw: string) => stringtrimTurns raw text into a tag
suggestionsstring[]—Suggestions shown while typing (script-insensitive)
size'sm' | 'md' | 'lg''md'Minimum height 32 / 40 / 48px
disabledboolean—Disabled state
readOnlyboolean—Read-only: no adding or removing
invalidboolean—Error state (automatic inside Field)
namestring—A hidden input with this name for every tag
labels{ remove?; suggestions?; added?; removed?; invalid?; duplicate? }—Built-in texts (Uzbek by default) — see the table below
inputClassNamestring—Class for the inner `<input>`

labels

PropTypeDefaultDescription
remove(tag: string) => string`${tag} ni olib tashlash`Name of the ✕ button
suggestionsstring'Takliflar'Name of the suggestion list
added(tag: string) => string`${tag} qo'shildi`Screen reader announcement
removed(tag: string) => string`${tag} olib tashlandi`Screen reader announcement
invalid(tag: string) => string`«${tag}» qabul qilinmadi`When `validate` returns false
duplicate(tag: string) => string`«${tag}» allaqachon qo'shilgan`Duplicate tag

Data attributes

AttributeWhereValue
data-sizecontainersm · md · lg
data-fullcontainerpresent when maxTags is hit
data-invalidcontainererror (or validation error)
data-disabledcontainerpresent when disabled
data-readonlycontainerpresent when readOnly
data-slotpartstag-input · tag-input-root

Keyboard

KeyAction
Enter / ,Turn the typed text into a tag
Backspace (empty input)Select the last tag; press again to remove it
← (at input start)Move to the last tag
← / → (on a tag)Move between tags; → on the last one returns to input
Backspace / Delete (on a tag)Remove the tag
Enter / F2 (on a tag)Edit the tag
Esc (on a tag)Back to the input
↓ / ↑Move through suggestions (with suggestions)
EscClose the suggestion list

Accessibility

  • List. Tags live in a <ul role="list"> — screen readers announce how many there are.
  • Announcements. Adding and removing are announced in a hidden aria-live="polite" region ("React qo'shildi", "React olib tashlandi"); customize with labels.added / labels.removed.
  • Errors. The validation error is visible text in a live region, linked to the input via aria-invalid and aria-describedby.
  • Suggestions. With suggestions the input is a role="combobox" (aria-expanded, aria-controls, aria-activedescendant) and the list is a role="listbox". Focus always stays in the input.
  • ✕ buttons have aria-label="React ni olib tashlash" and a hit area of at least 40px.
  • Tags are focused with the arrow keys (tabIndex={-1}); the ✕ buttons are also reachable with Tab.

On this page