InputsReact
A text field whose value is a list of tags.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<TagsInput.Root>
<TagsInput.List>
<TagsInput.Tag>
<TagsInput.TagContent>
<TagsInput.TagText />
<TagsInput.TagRemove />
</TagsInput.TagContent>
</TagsInput.Tag>
</TagsInput.List>
<TagsInput.Input />
<TagsInput.Clear />
</TagsInput.Root>
// Not in this example: TagsInput.TagEditInputExamples
Form
Skills in a form: validateTag refuses anything but lowercase letters, digits and hyphens, max stops at four, onTagReject says why, and required refuses an empty submit.
Editable
Recipients with editable: Enter or a double-click on a tag opens a TagEditInput; Enter saves and Escape cancels.
Suggestions
Frameworks typed into an input that is also an autocomplete's; a picked suggestion is added through actionsRef.
API reference
31 props across 9 parts, in React syntax. className, style, render are listed once, under Shared props.
ClearTagsInput.Clear
Removes every removable tag and the Input's text, then focuses the Input. Rendered while there is something to clear. It has no default name: give it one. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
keepMountedWhether the component should remain mounted in the DOM when there is nothing to clear. | boolean | false |
InputTagsInput.Input
The text field new tags are typed into. Place it after the List. It is described by the count of tags, rendered as its next sibling. Renders an <input>.
| Data attribute | Description |
|---|---|
data-rejected | The first error of the last refused entry, until the text changes. |
ListTagsInput.List
The tags: a grid with one row per tag, a group while there are none. Place it before the Input. Renders a <div>.
RootTagsInput.Root
Groups all parts of the tags input: a text field whose value is the list of tags typed into it. with the hidden inputs, followed by the status region. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
valueThe tags. | readonly string[] | — |
defaultValueThe tags when the component is initially rendered. | readonly string[] | — |
onValueChangeEvent handler called when the tags change, once per gesture. | ((value: string[], eventDetails: TagsInputRootChangeEventDetails) => void) | — |
inputValueThe text of the Input. | string | — |
defaultInputValueThe text of the Input when the component is initially rendered. | string | — |
onInputValueChangeEvent handler called when the text of the Input changes. | ((inputValue: string, eventDetails: TagsInputRootInputValueChangeEventDetails) => void) | — |
delimitersThe strings that end a tag in typed, pasted or dropped text. | readonly string[] | — |
addOnBlurWhether the text left in the Input is added when focus leaves the component. | boolean | true |
allowDuplicatesWhether a tag equal to one already there is accepted. | boolean | false |
validateTagChecks an entry after it is trimmed. | TagsInputValidate | — |
maxThe most tags the value holds. | number | Infinity |
onTagRejectEvent handler called with the entries of a gesture that were not added, each with its errors. | ((rejections: TagsInputRejection[], eventDetails: TagsInputRootRejectEventDetails) => void) | — |
editableWhether a tag can be edited in place, with Enter or a double-click. | boolean | false |
allowMoveWhether tags can be moved by dragging, and with Ctrl/Cmd+X then Ctrl/Cmd+V. | boolean | false |
canMoveWhether a move may happen. | ((details: GridListMoveDetails<string>) => boolean) | — |
loopFocusWhether the arrow keys wrap from the last tag to the first and back. | boolean | false |
disabledWhether the component should ignore user interaction. | boolean | false |
readOnlyWhether the user should be unable to change the tags. | boolean | false |
requiredWhether the user must add a tag before submitting a form. | boolean | false |
nameIdentifies the field when a form is submitted: one value per tag. | string | — |
formIdentifies the form that owns the hidden inputs. | string | — |
actionsRefA ref to add text from the app (a picked suggestion). | React.RefObject<TagsInputRootActions | null> | — |
labelsThe texts, in English by default. | Partial<TagsInputLabels> | — |
| Data attribute | Description |
|---|---|
data-disabled | Present when the component is disabled. |
data-readonly | Present when the component is read-only. |
data-required | Present when a tag is required. |
data-empty | Present when the value has no tags. |
data-full | Present when the value holds max tags. |
data-valid | Present when the field is valid. |
data-invalid | Present when the field is invalid. |
data-dirty | Present when the value has changed. |
data-touched | Present when focus has left the component. |
data-filled | Present when a tag exists. |
data-focused | Present while focus is inside the component. |
TagTagsInput.Tag
A tag: a row of the List with its one cell (TagsInput.TagContent). Renders a <div>.
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
valuerequiredThe tag's text: the entry of value this tag shows. | string | — |
labelThe name the announcements read. | string | — |
disabledWhether the tag should ignore user interaction. | boolean | false |
removableWhether the tag can be removed by a key, by TagsInput.TagRemove or by TagsInput.Clear. | boolean | true |
| Data attribute | Description |
|---|---|
data-highlighted | Present when focus is on the tag or inside it. |
data-disabled | Present when the tag is disabled. |
data-removable | Present when the tag can be removed. |
data-editing | Present while the tag is being edited. |
data-moving | Present while the tag is being moved. |
data-drop-target | Where a dragged tag would land relative to this one. |
data-dragging | Present while the tag is being dragged. |
TagContentTagsInput.TagContent
The one cell of a tag: its text, its edit input and its remove button. Renders a <div>.
TagEditInputTagsInput.TagEditInput
The text field that edits a tag in place. Rendered only while its tag is being edited, in place of TagsInput.TagText: Enter or a blur saves, Escape cancels. Renders an <input>.
TagRemoveTagsInput.TagRemove
A button that removes its tag. Named "Remove" and the tag's text. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
TagTextTagsInput.TagText
The text of a tag: it names the tag. Renders the tag's value when it has no children, and is not rendered while the tag is being edited. Renders a <span>.
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| Enter | Adds the typed text and empties the Input; in an empty Input submits the form | Input |
| , | Adds the text before it and keeps the rest in the Input | Input |
| Backspace | In an empty Input, focuses the last tag and removes nothing | Input |
| ArrowLeft | With the caret at the start and no selection, focuses the last tag | Input |
| ArrowRightArrowLeft | Moves to the next or previous tag, swapped in right-to-left text | Tag |
| ArrowRight | On the last tag, focuses the Input | Tag |
| ControlHomeControlEnd | Moves to the first or last tag | Tag |
| DeleteBackspace | Removes the focused tag and focuses the next one | Tag |
| Escape | Focuses the Input | Tag |
| Enter | With editable, opens the tag's text for editing, selected | Tag |
| Enter | Saves the edited text and focuses the tag | TagEditInput |
| Escape | Cancels the edit, restores the text and focuses the tag | TagEditInput |
| Tab | From a tag, reaches its remove button, then the Input | Tag |
| EnterSpace | Removes the tag and focuses the Input | TagRemove |
| ControlXControlV | With allowMove, picks up the focused tag and puts it at another | Tag |
Accessibility
| Part | Sets | So that |
|---|---|---|
| List | role="grid" while there are tags, role="group" with data-empty while there are none; named by the Field label | the tags are one composite with a single Tab stop, and the empty list is still named |
| Tag | role="row", one tab stop on the first tag; aria-disabled on a disabled tag, which arrow keys still reach | a reader moves through the tags with the arrow keys and hears which cannot be changed |
| TagContent | role="gridcell", the row's only element child | each row has exactly one cell, as the grid pattern needs |
| TagText | the row's name, the value when it has no children | a tag is read by its text, not by its remove button |
| TagRemove | a button named "Remove {tag}", reached by Tab from its own tag only; a mousedown on it is prevented | the button says what it removes, and a press does not move focus |
| TagEditInput | a text field holding the tag's text while it is edited | an edit is typed in place and read as a field |
| Input | a text field with autocomplete="off", named by the Field label and described by the count ("2 tags"), then the description | the count is heard on entering the field, and a refused entry sets aria-invalid |
| Clear | a button after the Input, rendered only while there is something to clear unless keepMounted | one press removes every tag and returns focus to the Input |
| Root | one polite role="status" region after the Root | an add, a removal, a refusal, an edit and a move are announced ("react added", "vue removed") |
Focus order
- The List is one Tab stop, on the first tag; the arrow keys move between tags, and Tab from a tag reaches its remove button.
- Then the Input, where Left at the start or Backspace in an empty field moves back to the last tag.
- Then Clear, while there is something to clear.