Tag Input
Many short values in one field: add with Enter or a comma, paste lists, remove with Backspace, with suggestions and validation.
Rendering Tag Input…
Installation
Copy the source into your project with the CLI. It lands under @/components/ui and is yours to edit.
npx mlola-ui add tag-inputUsage
import { TagInput } from "@/components/ui/tag-input";
export default function Example() {
return <TagInput label="Labels" defaultValue={["design", "billing"]} placeholder="Add a label" max={5} />;
}Styling comes from the engine stylesheet and the nearest theme. Set it once at the root:
<html data-theme="graphite" data-mode="light">HTML
The markup the example above renders, at rest. The interaction lives in the React component; from another framework, set the attributes below as the state changes.
<div class="ml-input-field ml-tag-input">
<label class="ml-input-label" id="tag-input-1-label" for="tag-input-1">Labels</label>
<div class="ml-tag-input-control">
<ul class="ml-tag-input-tags" aria-label="Added">
<li class="ml-tag-input-tag">
<span class="ml-tag-input-text">design</span>
<button class="ml-tag-input-remove" type="button" data-hit="expand" tabindex="-1" aria-label="Remove design"><svg class="mlola-glyph" width="0.8em" height="0.8em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="var(--mlola-glyph-stroke, var(--ml-icon-stroke, 1.75))" stroke-linecap="round" stroke-linejoin="round" focusable="false" aria-hidden="true" data-optical-size="default" style="color:var(--mlola-glyph-color, currentColor);scale:var(--mlola-glyph-optical-scale, 1);transform-origin:center"><path d="M18 6 6 18M6 6l12 12" pathLength="1"></path></svg></button>
</li>
<li class="ml-tag-input-tag">
<span class="ml-tag-input-text">billing</span>
<button class="ml-tag-input-remove" type="button" data-hit="expand" tabindex="-1" aria-label="Remove billing"><svg class="mlola-glyph" width="0.8em" height="0.8em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="var(--mlola-glyph-stroke, var(--ml-icon-stroke, 1.75))" stroke-linecap="round" stroke-linejoin="round" focusable="false" aria-hidden="true" data-optical-size="default" style="color:var(--mlola-glyph-color, currentColor);scale:var(--mlola-glyph-optical-scale, 1);transform-origin:center"><path d="M18 6 6 18M6 6l12 12" pathLength="1"></path></svg></button>
</li>
</ul>
<input class="ml-tag-input-field" id="tag-input-1" placeholder="Add a label" autoComplete="off" value=""/>
<span class="ml-tag-input-count" aria-hidden="true">2/5</span>
</div>
<p class="ml-visually-hidden" role="status" aria-live="polite"></p>
</div>Classes and attributes
A class names each element; data-* and aria-* attributes carry its variant and state. The values listed are the ones the stylesheet draws; the default needs no attribute.
| Class | Attributes |
|---|---|
ml-tag-input | — |
ml-tag-input-control | data-disableddata-invalid |
ml-tag-input-tags | — |
ml-tag-input-tag | data-armed |
ml-tag-input-text | — |
ml-tag-input-remove | — |
ml-tag-input-field | — |
ml-tag-input-count | — |
Accessibility and theming
- Semantic elements, accessible names and state relationships are part of the contract.
- Keyboard, pointer and touch paths are operable; focus stays visible in every theme.
- Light and dark modes pass WCAG AA contrast in all 5 canonical themes.
- Reduced motion and forced colors keep meaning and operation.
Dependencies
Installed alongside this component. No third-party UI runtime is ever added.