Tags Input
Production-ready tags input with extensive token integration, state management, and semantic HTML.
Overview
The Tags Input component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.
Why this exists
Tags input lets users freely add custom values (not just select from a list) as discrete chips. It is the go-to pattern for labeling, keywords, and skill entries.
Mental model
A tags input converts free-text entry into discrete removable chips. Press Enter or comma to add a tag.
When to use
When not to use
Basic example
The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.
Press Enter to add a tag. Backspace removes the last tag.
import { useState } from 'react';
import { TagsInput } from '@byteion/react';
export default function TagsInputDemo() {
const [tags, setTags] = useState(['vue', 'react', 'tailwind']);
return (
<div className="grid max-w-md gap-4 p-6">
<TagsInput label="Topics" value={tags} onChange={setTags}
placeholder="Add a topic and press Enter" />
<TagsInput label="Recipients"
defaultValue={['ada@example.com', 'linus@example.com']}
placeholder="user@example.com" />
</div>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { TagsInput } from '@byteion/vue';
const tags = ref(['vue', 'react', 'tailwind']);
</script>
<template>
<div class="grid max-w-md gap-4 p-6">
<TagsInput v-model="tags" label="Topics"
placeholder="Add a topic and press Enter" />
<TagsInput label="Recipients"
:model-value="['ada@example.com', 'linus@example.com']"
placeholder="user@example.com" />
</div>
</template>
Variants
Each variant communicates a different meaning. Choose based on priority, not aesthetics.
Single default variant.
Sizes
Size should match the importance and context of the component.
- md — md size.
States
This component reacts seamlessly to the following interaction and data states:
- default — Resting appearance before interaction.
- hover — Pointer is over the element — visual feedback via color or shadow.
- focus — Keyboard or tab focus — shows ring indicator for accessibility.
- active — Currently selected or in use.
- disabled — Non-interactive — grayed out, cursor not-allowed.
- loading — Action in progress — spinner replaces icon, button is disabled.
- invalid — Input value does not match expected format.
- error — Validation failed — red border and error message.
- success — Validation passed — green indicator.
Anatomy
Usage guidelines
- Accept Enter and comma as delimiters.
- Prevent duplicate tags.
- Validate format (e.g., no special characters).
Do and Don't
Do
- Accept Enter/comma to add.
- Prevent duplicates.
- Validate tag format.
Don't
- Do not allow empty tags.
- Do not allow duplicates.
- Do not use for predefined lists.
Accessibility
Each chip is focusable and removable. Input announces number of tags.
Role:combobox listbox
ARIA Attributes
aria-expandedaria-activedescendantrole
Keyboard Navigation
- Enter: Adds typed tag or selected suggestion
- Comma: Adds typed tag when configured as separator
- Backspace: Removes the last tag when input is empty
- ArrowDown: Moves to next suggestion
- ArrowUp: Moves to previous suggestion
- Escape: Closes suggestions
Customization
Tag chips use badge/soft tokens.
Compose with React cva()
Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.
import { cva, cn, type VariantProps } from '@byteion/react';
const myTagsInput = cva('base classes…', {
variants: {
accent: { soft:'border-primary-200', loud:'border-primary-500' },
},
defaultVariants: { accent:'soft' },
});
type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof myTagsInput>;
Compose with Vue cva()
Use the exportedcva() +cn() utilities inside standard Vue setup blocks.
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';
interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();
const myTagsInput = cva('base classes…', {
variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});
const cls = computed(() => myTagsInput({ accent: props.accent, class: attrs.class as string | undefined }));
</script>
Runtime events
Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.
HTML / JS events
bi:tags-input:addbi:tags-input:changebi:tags-input:errorbi:tags-input:loadbi:tags-input:removebi:tags-input:searchbi:tags:addbi:tags:changebi:tags:remove
React callbacks
onValueChange(value: string[]) => void
Vue emits
update:modelValue[value: string[]]
Remote / AJAX
Remote tag suggestions
Add data-bi-source to fetch tag suggestions. When data-bi-allow-create is true, users may still create a tag that is not returned by the backend.
Attributes
data-bi-sourcedata-bi-search-paramdata-bi-min-charsdata-bi-debouncedata-bi-value-fielddata-bi-label-fielddata-bi-allow-createdata-bi-namedata-bi-loading-textdata-bi-empty-textdata-bi-error-text
Runtime events
bi:tags-input:searchbi:tags-input:loadbi:tags-input:errorbi:tags-input:addbi:tags-input:removebi:tags-input:change
Accessibility: loading sets aria-busy="true"; empty states use role="status"; error states use alert/status messaging where the runtime renders remote status rows.
API Reference
Requires JS for chip management.
HTML Data Attributes
No custom data attributes required.
React Props · TagsInputProps
Extracted directly fromreact/src/components/TagsInput.tsx.
export interface TagsInputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange' | 'size'> {
/** Controlled list of tags. */
value?: string[];
defaultValue?: string[];
onValueChange?: (value: string[]) => void;
/** Optional label rendered above the field. */
label?: ReactNode;
/** Helper / error / success text below the field. */
helperText?: ReactNode;
error?: ReactNode;
success?: ReactNode;
/** Render the field in a loading state (input disabled, spinner inline). */
loading?: boolean;
/** Keys that commit the current input. Default: Enter + comma. */
commitKeys?: string[];
/** Reject duplicate tags. Default true. */
unique?: boolean;
/** Max number of tags allowed. */
max?: number;
/** Transform a raw input string before committing (e.g. trim, slugify). */
transform?: (raw: string) => string;
/** Validate a tag before committing. Return false to reject. */
validate?: (tag: string, existing: string[]) => boolean;
}
Vue Props · TagsInputProps
Extracted directly fromvue/src/components/TagsInput.vue.
export interface TagsInputProps {
/** Controlled list of tags. */
modelValue?: string[];
/** Initial list of tags when uncontrolled. */
defaultValue?: string[];
/** Label rendered above the field. */
label?: string;
helperText?: string;
error?: string;
success?: string;
loading?: boolean;
/** Keys that commit the current input. Default: Enter + comma. */
commitKeys?: string[];
/** Reject duplicate tags. Default true. */
unique?: boolean;
/** Max number of tags allowed. */
max?: number;
/** Transform a raw input string before committing (e.g. trim, slugify). */
transform?: (raw: string) => string;
/** Validate a tag before committing. Return false to reject. */
validate?: (tag: string, existing: string[]) => boolean;
disabled?: boolean;
placeholder?: string;
id?: string;
}
Developer tips
Common mistakes
Production usage tips
data-bi-* attributes instead of styling hooks.
Premium examples
Advanced patterns available in Byteion Pro:
Related components
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.
Explore Pro Components