Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UI ARC Radiant Components
v4.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

Tag Input

Free-text token entry field with optional autocomplete suggestions, delimiter splitting, and duplicate rejection.

Components Tag Input
input interactive beta
<arc-tag-input>

Overview

TagInput lets users build a list of free-text values as removable tag chips. Typing a value and pressing Enter — or the configurable delimiter character (comma by default) — commits the trimmed text as a tag. Pasting text containing delimiters splits it into multiple tags at once, making it fast to import comma-separated lists. An optional `suggestions` array turns the field into a lightweight autocomplete: as the user types, matching suggestions appear in a dropdown listbox navigable with ArrowUp/ArrowDown and committed with Enter or a click. Free text is still allowed alongside suggestions unless `allowCustom` is set to false, in which case only values from the suggestion list can be added. Duplicate entries are rejected — the existing chip shakes briefly to show why nothing was added (the animation is suppressed under reduced-motion preferences). TagInput is form-associated: it submits one FormData entry per tag under its `name`, so servers receive the values as a repeated field. A `maxTags` limit disables further entry with a "-- max reached" hint once reached. Keyboard editing mirrors MultiSelect: Backspace in an empty input removes the last tag, and ArrowLeft from the start of the input walks focus into the chips where arrows navigate and Backspace/Delete removes.

Guidelines

When to use

  • Always provide a `label` so the field is accessible to screen readers
  • Provide `suggestions` when a known vocabulary exists — it speeds entry and reduces typos
  • Set `allowCustom` to false when values must come from a controlled vocabulary
  • Use `maxTags` to cap entries when downstream systems limit how many values are accepted
  • Listen to `arc-input` to fetch or refine suggestions from a server as the user types

When not to use

  • Do not use TagInput when values must be chosen from a fixed list and casual browsing matters — use MultiSelect instead
  • Do not pick a delimiter character that legitimately appears inside your values
  • Do not use extremely long tag values — they will overflow the chips
  • Do not rely on the shake animation alone to explain rejected input in critical flows — pair with an `error` message where it matters

Features

  • Free-text tag creation on Enter or a configurable delimiter character (comma by default)
  • Paste splitting: pasted text containing delimiters becomes multiple tags in one action
  • Optional autocomplete dropdown driven by a `suggestions` array with type-ahead filtering
  • `allowCustom={false}` restricts entry to suggestion values only
  • Duplicate rejection with a brief shake animation on the existing chip (respects reduced motion)
  • `maxTags` limit with an inline "-- max reached" hint when full
  • Full keyboard editing: Backspace removes the last tag, ArrowLeft walks into chips, arrows navigate, Backspace/Delete removes, Escape returns to the input
  • Form-associated: submits one FormData entry per tag under `name`

Preview

Usage

This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.

<arc-tag-input
  label="Topics"
  placeholder="Add a topic..."
  suggestions='["JavaScript","TypeScript","Python"]'
  max-tags="5"
></arc-tag-input>
import { TagInput } from '@arclux/arc-ui-react';

export default function Example() {
  return (
    <TagInput
      label="Topics"
      placeholder="Add a topic..."
      suggestions={['JavaScript', 'TypeScript', 'Python']}
      maxTags={5}
      onArcChange={(e) => console.log(e.detail.value)}
    />
  );
}
<script setup>
import { TagInput } from '@arclux/arc-ui-vue';
</script>

<template>
  <TagInput
    label="Topics"
    placeholder="Add a topic..."
    :suggestions="['JavaScript', 'TypeScript', 'Python']"
    :maxTags="5"
  />
</template>
<script>
  import { TagInput } from '@arclux/arc-ui-svelte';
</script>

<TagInput
  label="Topics"
  placeholder="Add a topic..."
  suggestions={['JavaScript', 'TypeScript', 'Python']}
  maxTags={5}
/>
import { Component } from '@angular/core';
import { TagInput } from '@arclux/arc-ui-angular';

@Component({
  imports: [TagInput],
  template: `
    <arc-tag-input
      label="Topics"
      placeholder="Add a topic..."
      [suggestions]="['JavaScript', 'TypeScript', 'Python']"
      [maxTags]="5"
    />
  `,
})
export class MyComponent {}
import { TagInput } from '@arclux/arc-ui-solid';

export default function Example() {
  return (
    <TagInput
      label="Topics"
      placeholder="Add a topic..."
      suggestions={['JavaScript', 'TypeScript', 'Python']}
      maxTags={5}
    />
  );
}
import { TagInput } from '@arclux/arc-ui-preact';

export default function Example() {
  return (
    <TagInput
      label="Topics"
      placeholder="Add a topic..."
      suggestions={['JavaScript', 'TypeScript', 'Python']}
      maxTags={5}
    />
  );
}

API

delimiter string ','
Character that commits the current text as a tag when typed; pasted text is split on it.
label string ''
Visible label rendered above the field in a small uppercase style.
placeholder string ''
Hint text shown inside the field when no tags exist and the input is empty.
name string ''
Form field name. Each tag is submitted as its own FormData entry under this name.
disabled boolean false
Disables the control, preventing interaction and reducing opacity to 50%.
error string ''
Error message shown below the field; also applies error styling to the border.
value string[] []
Array of current tags. Updated on add/remove and emitted via arc-change.
suggestions string[] []
Autocomplete candidates. When non-empty, typing filters them into a dropdown listbox.
maxTags number 0
Maximum number of tags (0 = unlimited). At the limit, entry is disabled with a "-- max reached" hint.
allowCustom boolean true
When false, only values from suggestions can be added; free text is rejected.
readonly boolean false
Prevents adding or removing tags while the field stays focusable and the tags still submit with the form.
size 'sm' | 'md' | 'lg' 'md'
Control size. md is the default; sm and lg scale the field height and padding.
formAssociated boolean true
properties object { // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped — // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers — so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. required participates in constraint validation below; readonly reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
autoValidates boolean true
Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
form
validity
validationMessage
required boolean false

Methods

checkValidity() boolean
Whether the control currently satisfies its constraints, per the native constraint-validation API. Fires invalid on the element when it does not, and reports nothing to the user.
reportValidity() boolean
As checkValidity(), but also shows the browser's validation message against the control when it fails.

Events

arc-change detail: { value: string[] }
Fired when a tag is added or removed; detail contains { value }
arc-input
Fired as the user types; detail contains { query }

See Also