Skip to content
Forms, switch product

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[]. There is no prop that caps the number of tags; the widget never silently drops an entry.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"kind": "input",
"type": "tags",
"path": "keywords",
"label": "Keywords"
}
]
}

Use these props to customize your Tags widget.

proptypedescription
allowDuplicatesbooleanWhen false, typing an existing tag is silently ignored. Defaults to true
hintstringA description displayed below the row
iconstringCSS class string for a leading icon (rendered to the left of the pills)
placeholderstringPlaceholder text shown in the input when empty
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
separators('Enter' | ',' | 'Tab' | 'blur' | string)[]Triggers that commit the current draft as a new chip. Defaults to ['Enter', ',', 'Tab', 'blur']
trimbooleanWhen true (default), whitespace is trimmed from each value before committing

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', {
placeholder: 'Add a keyword and press Enter',
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
hint: 'Use Enter, Tab or comma to add a tag',
placeholder: 'Add a keyword…',
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
icon: 'label',
placeholder: 'Add a keyword…',
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"kind": "input",
"type": "tags",
"path": "keywords",
"label": "Keywords",
"props": {
"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', {
hint: 'Only Enter or comma commit a tag — Tab and blur do not',
placeholder: 'Type something and press Enter or ,',
separators: ['Enter', ','],
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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",
","
]
}
}
]
}

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', {
hint: 'Try adding the same tag twice — the second attempt is silently ignored',
placeholder: 'Add a tag…',
allowDuplicates: false,
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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).

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
hint: 'trim is off — leading/trailing whitespace is preserved verbatim',
placeholder: 'Try adding \' spaced \'',
trim: false,
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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
}
}
]
}

There is no prop that caps the number of chips. Use the array validator’s maxItems instead: entry is never silently blocked, the extra tag is committed and the validator flags it, so the user is told why. Combine it with messages to change the wording, as in Validation.

tags.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.tags('keywords', {
hint: 'A maxItems validator flags a fourth tag instead of blocking it',
placeholder: 'Up to 3 tags',
label: 'Keywords (max 3)',
validator: {
maxItems: 3,
},
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"kind": "input",
"type": "tags",
"path": "keywords",
"label": "Keywords (max 3)",
"props": {
"hint": "A maxItems validator flags a fourth tag instead of blocking it",
"placeholder": "Up to 3 tags"
},
"validator": {
"type": "array",
"maxItems": 3
}
}
]
}

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', {
hint: 'Custom remove-button icon and ARIA label',
placeholder: 'Add a tag…',
removeIcon: 'delete',
removeAriaLabel: 'Discard tag',
label: 'Keywords',
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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"
}
}
]
}

The widget supports the standard array validator: required, minItems and maxItems. A tag past maxItems is committed and flagged, never dropped. 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', {
hint: 'Between 1 and 5 tags',
placeholder: 'Add a keyword…',
label: 'Keywords',
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',
},
},
}),
];
tags.json
{
"$schema": "https://golemui.com/schemas/form.schema.json",
"form": [
{
"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"
}
}
}
]
}

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-xsDefault value of --gui-pill-font-size
--gui-pill-heightChip height. 24px, 32px under pointer: coarse
--gui-pill-action-sizeRemove icon size. 16px, 24px under pointer: coarse
--gui-pill-action-hitRemove icon hit area. 24px, 32px under pointer: coarse
--gui-pill-font-sizeChip text font size. var(--gui-font-xs), var(--gui-font-sm) under pointer: coarse

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>