Skip to content
omaris

Inputs

Tags Input

Free-form tags, typed into one field.

import { TagsInput } from 'omaris'
Learn

Examples

Basic

Type and press Enter, a comma or Tab. Backspace in the empty box selects the last tag, and a second press takes it off.

kebab grill
Helps people find the dish in search.
<script lang="ts">	import { TagsInput } from 'omaris';​	let keywords = $state(['kebab', 'grill']);</script>​<TagsInput	class="w-96 max-w-full"	label="Keywords"	placeholder="Add a keyword"	supportingText="Helps people find the dish in search."	bind:value={keywords}/>

Validation

validate returns the reason a tag was refused, and the typed text stays put for fixing. A paste with commas or newlines is split and vetted piece by piece; max adds a count and stops at the cap.

dara@rasil.app
1/5
<script lang="ts">	import { TagsInput } from 'omaris';​	let invites = $state(['dara@rasil.app']);​	const email = (tag: string) =>		/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(tag) || 'That is not an email address.';</script>​<TagsInput	class="w-96 max-w-full"	label="Invite teammates"	placeholder="name@company.com"	max={5}	validate={email}	bind:value={invites}/>

In a field

Inside a Field it takes the label, the help and the error from the field, like any other control. The variants and sizes are Input's.

Shown as badges on the menu item.

<script lang="ts">	import { Field, TagsInput } from 'omaris';​	let diets = $state<string[]>([]);</script>​<Field	class="w-96 max-w-full"	label="Dietary tags"	description="Shown as badges on the menu item."	error={diets.length === 0 ? 'Add at least one.' : undefined}	required>	<TagsInput variant="filled" placeholder="vegan, halal…" bind:value={diets} /></Field>

Overridden

A narrow box and long tags: a tag truncates inside its chip rather than pushing the field wider. classes reaches the frame, each chip and the box.

/var/lib/omaris/cache/thumbnails /tmp
<script lang="ts">	import { TagsInput } from 'omaris';</script>​<TagsInput	class="w-60 max-w-full"	label="Paths"	value={['/var/lib/omaris/cache/thumbnails', '/tmp']}	classes={{ field: 'rounded-shape-xl', chip: 'font-mono', input: 'font-mono' }}/>

When to use it

Use it for

  • Free-form values the person types — keywords, labels, email recipients, allowed domains — bound as a string[] with bind:value.
  • A list that is vetted as it grows: validate returns the reason a tag was refused and leaves the text in the box to fix; unique (on by default) refuses a repeat; max caps it and shows a count.
  • Pasting a list from somewhere else — a comma- or newline-separated paste becomes one tag per piece, each vetted on its own.
  • A form field like any other: inside a Field, or with its own label and supportingText, in Input's variants and sizes.

Not for

  • Picking several from a known list → Multi Select; its creatable covers the occasional value that is not on the list.
  • One free-text value → Input; one choice from a list → Combobox.
  • Filters the person switches on and off → filter Chips in a ChipGroup.
  • Showing tags nobody edits → Chips or Badges.

Do

  • Say what separates tags in the placeholder or supportingText — "Separate with commas" — the first time it matters.
  • Return a specific reason from validate — "Not an email address", not "Invalid" — and keep it short; it shares a line with the count.
  • Pass name in a plain form: every tag posts as its own value under it.
  • Localise removeLabel, duplicateMessage and maxMessage with the rest of the page's strings.

Don't

  • Use it for values that must come from a fixed set; a typed tag that the server then rejects is worse than a list that never offered it.
  • Put more than a line or two of tags in it — past about fifteen, the field is a list and wants to be a List with an add row.
  • Set required expecting the browser to enforce it; the box is empty whenever a tag has been added. Check value.length yourself.

Quick reference

size
  • sm
  • default (default)
  • lg

API

TagsInput

Free-form tags, typed into one field.

The field is an Input frame with the tags sitting in front of the text box, so it reads as one control and wraps onto a second row when it has to. Enter, a comma or Tab turns the text into a tag; a paste with commas or newlines in it becomes several at once.

Backspace in an empty box selects the last tag, and a second press takes it off — so a stray press never deletes something you can't see going. The arrows walk the tags from there, and Delete or Backspace removes the one you're on.

For picking from a fixed list rather than typing, see MultiSelect.

import { TagsInput } from 'omaris'
<TagsInput bind:value={tags} label="Keywords" max={8} /><TagsInput bind:value={emails} validate={(t) => t.includes('@') || 'Not an email.'} />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
Wrapper: label, field, supporting text.
field
The frame: border, background, height, adornment layout.
control
The bare <input>; the frame owns every decoration.
label
No description in the source yet.
adornment
Icon slots at either end of the frame.
affix
Flat text glued to the value — "https://", "kg".
action
An interactive trailing control: reveal, clear.
support
Helper line under the field, and the counter beside it.
counter
No description in the source yet.
list
The tags and the text box, wrapped inside the frame.
chip
One tag — a Chip, so it is the library's chip and not a lookalike.
input
The bare text box the tags sit in front of.

Props

value bindable

Defaults to []

string[]

The tags, in the order they were added. Bindable.

query bindable

Defaults to ''

string

What is typed but not yet a tag. Bindable — handy for suggestions.

variant

Defaults to 'outline'

InputVariant
size

Defaults to 'default'

InputSize
sm
default
lg
label
string

Label above the field. Wired to the text box, so clicking it focuses.

supportingText
string

Helper line under the field. A rejected tag's reason replaces it.

invalid
boolean

Marks the field invalid. Inside a Field, defaults to the field's.

required
boolean

Stars the label. Inside a Field, defaults to the field's.

max
number

Most tags it takes. Adds an n/max count under the field.

unique

Defaults to true

boolean

Refuse a tag that is already there, ignoring case.

validate
(tag: string, value: string[]) => string | true

Vets a tag before it goes in. Return true to accept it, or the reason it was refused — that text shows under the field and the typed text stays put for fixing.

duplicateMessage

Defaults to 'Already added.'

string

Shown when unique refuses a tag.

maxMessage
string

Shown when a tag would go past max. Defaults to "Up to {max}."

removeLabel

Defaults to (tag: string) => `Remove ${tag}`

(tag: string) => string

Accessible name of a tag's ×.

onchange
(value: string[]) => void

Fires with the new list after every add and remove.

class
string
classes
TagsInputClasses

Per-part Tailwind overrides. class still covers the root.

ref bindable

Defaults to null

HTMLInputElement | null