Skip to content

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[].

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
label: 'Keywords',
}),
];

Use these props to customize your Tags widget.

proptypevalue
placeholderstringPlaceholder text shown in the input when empty
hintstringA description displayed below the row
iconstringCSS 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']
allowDuplicatesbooleanWhen false, typing an existing tag is silently ignored. Defaults to true
trimbooleanWhen true (default), whitespace is trimmed from each value before committing
limitnumberSoft cap on the number of chips. The widget stops accepting new chips at the limit (no error)
removeAriaLabelstringARIA label prefix used on each chip’s remove button. Defaults to Remove tag
removeIconstringCSS class string for a custom remove-icon. When omitted, a built-in × glyph is rendered

Use the property placeholder to set the text shown inside the empty input.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
label: 'Keywords',
placeholder: 'Add a keyword and press Enter',
}),
];

Use the property hint to add a description below the row.

tags.ts
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…',
}),
];

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.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
label: 'Keywords',
icon: 'label',
placeholder: 'Add a keyword…',
}),
];

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.

tags.ts
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', ','],
}),
];

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.

tags.ts
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,
}),
];

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).

tags.ts
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,
}),
];

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.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
label: 'Keywords (max 3)',
limit: 3,
placeholder: 'Up to 3 tags',
}),
];

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").

tags.ts
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',
}),
];

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.

tags.ts
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',
},
},
}),
];

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.

KeyAction
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
  • The pills row, input and icon are wrapped in a <div role="group"> that carries the widget’s aria-label, focus border and error border.
  • aria-describedby, aria-invalid and aria-errormessage are wired automatically on the group when a hint or validation error is present.
  • Each chip is exposed as a role="listitem" with aria-label="Remove tag: <value>", so screen readers announce both the value and the destructive action that the chip’s Delete / Backspace key triggers.
  • The count bubble is a <button aria-expanded> so assistive tech can announce the collapsed/expanded state of the dropdown list of pills.

Tags can be styled as explained in the Styling Guide.

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 VariableDescription
--gui-color-borderGroup border colour
--gui-color-bgGroup background colour and scroll-fade
--gui-color-fgInput text colour
--gui-color-primaryGroup focus-ring colour
--gui-color-errorGroup border colour when invalid
--gui-intent-primaryChip and count-bubble background colour
--gui-color-whiteChip text / remove-icon colour
--gui-radius-mdChip corner radius
--gui-radius-fullCount-bubble corner radius
--gui-space-1Chip vertical padding
--gui-space-2Row gap between chips
--gui-space-3Gap between chip text and remove button
--gui-font-xsChip text font-size

This is the anatomy of the Tags Widget in case you want to use your CSS styles.

tags.html
<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>