Tags
Tags widgets are Input Fields that capture an
ordered list of free-form text values. Each committed value becomes a removable pill
shown to the left of the input, and the value bound at the widget’s path is a string[].
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords" } ]}Use these props to customize your Tags widget.
| prop | type | value |
|---|---|---|
placeholder | string | Placeholder text shown in the input when empty |
hint | string | A description displayed below the row |
icon | string | CSS class string for a leading icon (rendered to the left of the pills) |
separators | ('Enter' | ',' | 'Tab' | 'blur' | string)[] | Triggers that commit the current draft as a new chip. Defaults to ['Enter', ',', 'Tab', 'blur'] |
allowDuplicates | boolean | When false, typing an existing tag is silently ignored. Defaults to true |
trim | boolean | When true (default), whitespace is trimmed from each value before committing |
limit | number | Soft cap on the number of chips. The widget stops accepting new chips at the limit (no error) |
removeAriaLabel | string | ARIA label prefix used on each chip’s remove button. Defaults to Remove tag |
removeIcon | string | CSS class string for a custom remove-icon. When omitted, a built-in × glyph is rendered |
Placeholder
Section titled “Placeholder”Use the property placeholder to set the text shown inside the empty input.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', placeholder: 'Add a keyword and press Enter', }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "placeholder": "Add a keyword and press Enter" } } ]}Use the property hint to add a description below the row.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', hint: 'Use Enter, Tab or comma to add a tag', placeholder: 'Add a keyword…', }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "hint": "Use Enter, Tab or comma to add a tag", "placeholder": "Add a keyword…" } } ]}Use the property icon to render a leading icon to the left of the pills. The value is a
CSS class string — the same convention used by Textinput.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', icon: 'label', placeholder: 'Add a keyword…', }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "icon": "label", "placeholder": "Add a keyword…" } } ]}Separators
Section titled “Separators”By default a tag is committed on Enter, ,, Tab, or blur. Override the property
separators to restrict the commit triggers — useful when you want the user to commit
explicitly (e.g. Enter only) and treat commas as part of the tag value.
Pasting text that contains any of the configured character separators (e.g. commas) also splits the paste into multiple chips in a single commit.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', hint: 'Only Enter or comma commit a tag — Tab and blur do not', placeholder: 'Type something and press Enter or ,', separators: ['Enter', ','], }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "hint": "Only Enter or comma commit a tag — Tab and blur do not", "placeholder": "Type something and press Enter or ,", "separators": ["Enter", ","] } } ]}Allow duplicates
Section titled “Allow duplicates”Set allowDuplicates: false to silently ignore tags the user has already added — no error
message, the second attempt simply does nothing. This is a UX safeguard at commit time;
there is no form-level error to surface.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', hint: 'Try adding the same tag twice — the second attempt is silently ignored', placeholder: 'Add a tag…', allowDuplicates: false, }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "hint": "Try adding the same tag twice — the second attempt is silently ignored", "placeholder": "Add a tag…", "allowDuplicates": false } } ]}trim defaults to true so leading/trailing whitespace is removed before a tag is
committed. Set it to false when surrounding whitespace is semantically meaningful (for
example, deliberately quoting a phrase with padding).
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', hint: 'trim is off — leading/trailing whitespace is preserved verbatim', placeholder: "Try adding ' spaced '", trim: false, }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "hint": "trim is off — leading/trailing whitespace is preserved verbatim", "placeholder": "Try adding ' spaced '", "trim": false } } ]}Use the property limit to cap the number of chips a user can add. The cap is a soft UX
limit — once reached, new entries are silently dropped. For a form-level error, combine
with the maxItems validator below.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords (max 3)', limit: 3, placeholder: 'Up to 3 tags', }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords (max 3)", "props": { "limit": 3, "placeholder": "Up to 3 tags" } } ]}Customize the remove button
Section titled “Customize the remove button”removeIcon swaps the built-in × glyph for any icon (CSS class string, same convention
as icon). removeAriaLabel sets the prefix announced by assistive tech alongside the
tag value — useful for translations or wording adjustments (e.g. "Discard tag: foo").
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', hint: 'Custom remove-button icon and ARIA label', placeholder: 'Add a tag…', removeIcon: 'delete', removeAriaLabel: 'Discard tag', }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "hint": "Custom remove-button icon and ARIA label", "placeholder": "Add a tag…", "removeIcon": "delete", "removeAriaLabel": "Discard tag" } } ]}Validation
Section titled “Validation”The widget supports the standard array validator: required, minItems, maxItems.
Validation errors surface like any other input — below the row, with aria-invalid and
aria-errormessage wired automatically. Override the default error copy per-rule with a
messages object — each key matches the rule it customises, and values can be a string or
a Localizable for i18n.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.tags('keywords', { label: 'Keywords', hint: 'Between 1 and 5 tags', placeholder: 'Add a keyword…', validator: { required: true, minItems: 1, maxItems: 5, messages: { required: 'Please add at least one keyword', minItems: 'You need at least 1 keyword to continue', maxItems: 'No more than 5 keywords, please', }, }, }),];{ "form": [ { "uid": "", "kind": "input", "type": "tags", "path": "keywords", "label": "Keywords", "props": { "hint": "Between 1 and 5 tags", "placeholder": "Add a keyword…" }, "validator": { "type": "array", "required": true, "minItems": 1, "maxItems": 5, "messages": { "required": "Please add at least one keyword", "minItems": "You need at least 1 keyword to continue", "maxItems": "No more than 5 keywords, please" } } } ]}Keyboard
Section titled “Keyboard”The widget keeps the input as the single tab stop. Pills are reachable from the input via arrow keys, mirroring chip-input conventions in Gmail recipients and MUI Autocomplete.
| Key | Action |
|---|---|
Enter / , (on input) | Commit the current draft as a new chip |
Tab (on input, non-empty draft) | Commit the current draft, then leave the widget |
Escape (on input) | Clear the current draft without committing |
← Arrow (caret at start of input) | Enter the pill list and focus the last pill (opens the dropdown in compact mode) |
Backspace (empty input) | Focus the last pill (does not delete it) |
← Arrow / → Arrow (pill focused) | Navigate between pills. → past the last pill returns focus to the input |
Home / End (pill focused) | Focus the first / last pill |
Delete / Backspace (pill focused) | Remove the focused pill, then focus the next pill, else the previous, else the input |
↑ Arrow / ↓ Arrow (pill in dropdown) | Navigate the dropdown list when the count bubble is open |
Escape (pill in dropdown) | Close the dropdown and return focus to the input |
Accessibility
Section titled “Accessibility”- The pills row, input and icon are wrapped in a
<div role="group">that carries the widget’saria-label, focus border and error border. aria-describedby,aria-invalidandaria-errormessageare wired automatically on the group when a hint or validation error is present.- Each chip is exposed as a
role="listitem"witharia-label="Remove tag: <value>", so screen readers announce both the value and the destructive action that the chip’sDelete/Backspacekey triggers. - The count bubble is a
<button aria-expanded>so assistive tech can announce the collapsed/expanded state of the dropdown list of pills.
Styling
Section titled “Styling”Tags can be styled as explained in the Styling Guide.
CSS Variables
Section titled “CSS Variables”The chip and count-badge visuals are shared with the Range Calendar / Range Date Input pills, so they pick up the same intent / spacing / radius tokens.
| CSS Variable | Description |
|---|---|
--gui-color-border | Group border colour |
--gui-color-bg | Group background colour and scroll-fade |
--gui-color-fg | Input text colour |
--gui-color-primary | Group focus-ring colour |
--gui-color-error | Group border colour when invalid |
--gui-intent-primary | Chip and count-bubble background colour |
--gui-color-white | Chip text / remove-icon colour |
--gui-radius-md | Chip corner radius |
--gui-radius-full | Count-bubble corner radius |
--gui-space-1 | Chip vertical padding |
--gui-space-2 | Row gap between chips |
--gui-space-3 | Gap between chip text and remove button |
--gui-font-xs | Chip text font-size |
Anatomy
Section titled “Anatomy”This is the anatomy of the Tags Widget in case you want to use your CSS styles.
<gui-tags> <span class="gui-label" id="fieldUid_label"> Label <div class="gui-widget-hint" id="fieldUid_hint">Hint</div> </span>
<div class="gui-widget"> <div class="gui-tags gui-tags--icon" role="group" aria-label="Keywords"> <span class="gui-widget-icon label" data-icon="label"></span>
<div class="gui-tags__pills-wrapper"> <div class="gui-tags__pills" role="list"> <span class="gui-sentinel gui-sentinel__start"></span> <div class="gui-tags__chip" role="listitem" tabindex="-1" aria-label="Remove tag: design" > <span class="gui-tags__chip-text">design</span> <button class="gui-tags__chip-remove" tabindex="-1" aria-hidden="true">×</button> </div> <div class="gui-tags__chip" role="listitem" tabindex="-1" aria-label="Remove tag: product" > <span class="gui-tags__chip-text">product</span> <button class="gui-tags__chip-remove" tabindex="-1" aria-hidden="true">×</button> </div> <span class="gui-sentinel gui-sentinel__end"></span> </div> </div>
<div class="gui-tags__pills-compact"> <button class="gui-tags__pill--count" aria-label="2 tags" aria-expanded="false">2</button> </div>
<input type="text" id="fieldUid" class="gui-tags__input" placeholder="Add a tag…" aria-describedby="fieldUid_hint" /> </div> </div></gui-tags>