Demos
How to import
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
Basic Usage
After pressing the Enter key, the input will add value as a tag. If the tag content is an empty string or pure space, it will be filtered.
抖音
火山
西瓜视频
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :defaultValue="['抖音', '火山', '西瓜视频']" placeholder="请输入..." @change="(v) => console.log(v)" />
</template>Batch Add
You can use separator to set the separator to achieve batch input, and its default value is a comma. Multiple separators are supported in string[] format.
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
const separators = ['-', '/', '|', '++'];
</script>
<template>
<TagInput placeholder="使用 - 进行批量输入" separator="-" @change="(v) => console.log(v)" />
<br />
<br />
<TagInput placeholder="支持多个分隔符进行批量输入" :separator="separators" @change="(v) => console.log(v)" />
</template>Batch Remove
You can also use showClear to set whether to support one-click deletion of all tags and input.
抖音
火山
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
const defaultValue = ['抖音', '火山'];
</script>
<template>
<TagInput :defaultValue="defaultValue" placeholder="请输入..." showClear @change="(v) => console.log(v)" />
</template>Disabled
抖音
火山
西瓜视频
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :defaultValue="['抖音', '火山', '西瓜视频']" disabled placeholder="请输入..." showClear />
</template>Size
Use size to set the size of the TagInput, optional: small, default, large.
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput placeholder="small" size="small" />
<br />
<br />
<TagInput placeholder="default" />
<br />
<br />
<TagInput placeholder="large" size="large" />
</template>Different validate status
validateStatus: default, warning, error.
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput placeholder="default" />
<br />
<br />
<TagInput placeholder="warning" validateStatus="warning" />
<br />
<br />
<TagInput placeholder="error" validateStatus="error" />
</template>Prefix / Suffix
You can pass the input box prefix through the #prefix slot and the suffix through the #suffix slot, as text or nodes such as icons.
The left and right padding is added automatically when the slot content is plain text or an Icon. For custom nodes the padding is 0; set it on the nodes you pass in if needed.
Code
<script setup lang="ts">
import { IconGift, IconVigoLogo } from '@fibrous/semi-icons-vue';
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput>
<template #prefix>
<IconVigoLogo />
</template>
</TagInput>
<br />
<br />
<TagInput>
<template #prefix>Prefix</template>
</TagInput>
<br />
<br />
<TagInput>
<template #suffix>
<IconGift />
</template>
</TagInput>
<br />
<br />
<TagInput>
<template #suffix>Suffix</template>
</TagInput>
</template>Autocomplete
You can use addOnBlur to set whether the current input value is automatically created as a tag when the blur event is triggered.
抖音
火山
西瓜视频
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput addOnBlur :defaultValue="['抖音', '火山', '西瓜视频']" placeholder="请输入..." @change="(v) => console.log(v)" />
</template>Allow Duplicates
You can use allowDuplicates to set whether to allow the creation of the same tag.
抖音
火山
西瓜视频
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :allowDuplicates="false" :defaultValue="['抖音', '火山', '西瓜视频']" placeholder="请输入..." @change="(v) => console.log(v)" />
</template>Limits
You can use max to limit the number of tags. Tags added past the limit are dropped, and the exceed event is emitted.
You can use maxLength to limit the maximum length of a single tag, and the inputExceed event is emitted when this value is exceeded.
Code
<script setup lang="ts">
import { TagInput, Toast } from '@fibrous/semi-ui-vue';
const onExceed = (value: string[]) => {
Toast.warning('超过 max');
console.log(value);
};
const onInputExceed = (value: string) => {
Toast.warning('超过 maxLength');
console.log(value);
};
</script>
<template>
<TagInput :max="3" placeholder="最多输入3条标签.." @change="(v) => console.log(v)" @exceed="onExceed" />
<TagInput :maxLength="5" placeholder="单个标签长度不超过5..." style="margin-top: 12px" @change="(v) => console.log(v)" @inputExceed="onInputExceed" />
</template>Limit the number of tags displayed
You can use maxTagCount to limit the number of tags displayed, and the excess will be displayed as +N. You can use showRestTagsPopover to set whether hover +N displays Popover after maxTagCount is exceeded, and you can configure Popover in the restTagsPopoverProps property.
抖音
火山
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
const defaultValue = ['抖音', '火山', '西瓜视频'];
const restTagsPopoverProps = { position: 'top' } as const;
</script>
<template>
<TagInput :defaultValue="defaultValue" :maxTagCount="2" :restTagsPopoverProps="restTagsPopoverProps" showRestTagsPopover @change="(v) => console.log(v)" />
</template>Controlled Tag
Bind the tags with v-model (modelValue plus update:modelValue) to control them.
抖音
Code
<script setup lang="ts">
import { ref } from 'vue';
import { TagInput } from '@fibrous/semi-ui-vue';
const value = ref(['抖音']);
</script>
<template>
<TagInput v-model="value" />
</template>Controlled Input
Bind the input text with v-model:inputValue to control it.
Code
<script setup lang="ts">
import { ref } from 'vue';
import { TagInput } from '@fibrous/semi-ui-vue';
const value = ref('abc');
</script>
<template>
<TagInput v-model:inputValue="value" />
</template>Callback
抖音
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput
:defaultValue="['抖音']"
placeholder="请输入..."
showClear
@add="(tag) => console.log(`onAdd,新增:${tag}`)"
@blur="() => console.log('onBlur')"
@change="(tag) => console.log(`onChange,当前标签数组:${tag}`)"
@focus="() => console.log('onFocus')"
@inputChange="(input) => console.log(`onInputChange,当前输入内容:${input}`)"
@remove="(v, i) => console.log(`onRemove,移除:${v}, 序号:${i}`)"
/>
</template>Focus Management
You can call the blur() and focus() methods through the component ref to manage the focus.
抖音
火山
Code
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { Button, TagInput, type TagInputHandle } from '@fibrous/semi-ui-vue';
const tagInput = useTemplateRef<TagInputHandle>('tagInput');
const handleTagInputFocus = () => {
tagInput.value?.focus();
};
</script>
<template>
<TagInput ref="tagInput" :defaultValue="['抖音', '火山']" />
<Button style="margin-top: 10px" @click="handleTagInputFocus">点击按钮聚焦</Button>
</template>Custom TagInput rendering
You can use the #tag scoped slot to replace each tag as a whole; its scoped props are { value, index, onClose }. Empty slot content renders no tag. onClose calls preventDefault on the event it receives, so you can bind it straight to a button's @click.
Code
<script setup lang="ts">
import { ref } from 'vue';
import { IconClose } from '@fibrous/semi-icons-vue';
import { Avatar, Button, TagInput } from '@fibrous/semi-ui-vue';
const value = ref(['夏可漫']);
const fallbackAvatar = '/assets/placeholder/avatar-a.svg';
const avatars = new Map([
['夏可漫', '/assets/root-web-sites/avatarDemo.jpeg'],
['申悦', '/assets/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg'],
['曲晨一', '/assets/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg'],
['文嘉茂', '/assets/root-web-sites/avatarDemo.jpeg'],
]);
const itemStyle = {
backgroundColor: 'var(--semi-color-info-light-default)',
padding: '4px 8px',
borderRadius: '12px',
display: 'flex',
alignItems: 'center',
fontSize: '14px',
marginRight: '10px',
};
</script>
<template>
<TagInput v-model="value">
<template #tag="{ value: tag, onClose }">
<div :style="itemStyle">
<Avatar alt="avatar" size="extra-small" :src="avatars.get(tag) ?? fallbackAvatar" />
<span :style="{ marginLeft: '8px' }">{{ `${tag}@semi.com` }}</span>
<Button :aria-label="`移除 ${tag}`" size="small" theme="borderless" type="tertiary" @click="onClose">
<template #icon>
<IconClose size="small" />
</template>
</Button>
</div>
</template>
</TagInput>
</template>Drag to sort
Set draggable to true to enable drag and drop sorting. After the drag function is enabled, once TagInput is focused (by click or Tab) the tags can be dragged; after the focus leaves TagInput they cannot. The drag handle sits beside its tag and works from the keyboard, see "Keyboard and focus" below. Tags with the same value can be sorted separately.
抖音
火山
西瓜视频
Code
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :allowDuplicates="false" :defaultValue="['抖音', '火山', '西瓜视频']" draggable placeholder="请输入..." @change="(v) => console.log(v)" />
</template>API Reference
Props
| Property | Description | Type | Default |
|---|---|---|---|
| modelValue | Current tags, for v-model. undefined makes it uncontrolled; null means controlled with no tags; going back from an array to undefined clears the tags. | string[] | null | undefined |
| defaultValue | Initial tags when uncontrolled. | string[] | semiGlobalDefault("TagInput", "defaultValue") |
| inputValue | Current input text, for v-model:inputValue. | string | semiGlobalDefault("TagInput", "inputValue") |
| inputValueModifiers | The trim and number modifiers of v-model:inputValue, passed by Vue for v-model:inputValue.trim. | ModelModifiers | undefined |
| disabled | Whether the component is disabled. | boolean | semiGlobalDefault("TagInput", "disabled") |
| maxLength | Maximum length of one tag. | number | semiGlobalDefault("TagInput", "maxLength") |
| max | Maximum number of tags. | number | semiGlobalDefault("TagInput", "max") |
| maxTagCount | Maximum number of tags shown; the rest are shown as +N. | number | semiGlobalDefault("TagInput", "maxTagCount") |
| showRestTagsPopover | Whether hovering +N shows the remaining tags in a Popover once maxTagCount is exceeded. | boolean | semiDefault("TagInput", "showRestTagsPopover", true) |
| restTagsPopoverProps | Popover props of the +N popover, without content. | PopoverProps | semiGlobalDefault("TagInput", "restTagsPopoverProps") |
| showContentTooltip | Whether hovering a truncated tag shows its full text in an overlay; an object picks the overlay type and the props passed to it. | boolean | ShowTooltip | semiDefault("TagInput", "showContentTooltip", true) |
| allowDuplicates | Whether the same tag can be added more than once. | boolean | semiDefault("TagInput", "allowDuplicates", true) |
| addOnBlur | Whether the input text becomes a tag when the focus leaves the component. | boolean | semiDefault("TagInput", "addOnBlur", false) |
| draggable | Whether tags can be reordered by dragging. | boolean | semiDefault("TagInput", "draggable", false) |
| expandRestTagsOnClick | When not draggable, whether the collapsed tags expand once focus enters the component (click, Tab or the focus method). | boolean | semiDefault("TagInput", "expandRestTagsOnClick", true) |
| placeholder | Placeholder of the input while there are no tags. | string | semiGlobalDefault("TagInput", "placeholder") |
| insetLabelId | id of the inset label container, for aria-labelledby. | string | undefined |
| separator | Separator(s) for adding several tags at once; null disables splitting. | string | string[] | null | semiDefault("TagInput", "separator", ",") |
| showClear | Whether to show the button that clears all tags and the input text. | boolean | semiDefault("TagInput", "showClear", false) |
| size | Size: small, default or large. | Size | semiDefault("TagInput", "size", "default") |
| split | Custom split function that returns the split strings. | Type((originString: string, separators: string | string[] | null) => string[]) | undefined |
| validateStatus | Validation status, which only affects styling; error also sets aria-invalid on the input. | TagInputValidateStatus | semiDefault("TagInput", "validateStatus", "default") |
| autofocus | Whether the input is focused and the component activated on mount; ignored when disabled. | boolean | semiDefault("TagInput", "autofocus", false) |
| preventScroll | Whether the focus calls inside the component stop the browser from scrolling the focused element into view. | boolean | semiGlobalDefault("TagInput", "preventScroll") |
Events
| Event | Description | Callback parameters |
|---|---|---|
| update:modelValue | Emitted when the tags change, for v-model. | [value: string[]] |
| change | Emitted when the tags change. | [value: string[]] |
| update:inputValue | Emitted when the input text changes, for v-model:inputValue. | [value: string] |
| inputChange | Emitted when the input text changes. The event may be an input, keydown, focusout, click or composition event; narrow it by event.type before reading its own fields. | [value: string, event: Event] |
| add | Emitted when tags are added, with the added tags. | [addedValue: string[]] |
| remove | Emitted when a tag is removed, with the removed tag and its index. | [removedValue: string, index: number] |
| exceed | Emitted when adding would exceed max, with the whole list before it is cut. | [value: string[]] |
| inputExceed | Emitted when the input text exceeds maxLength, with the rejected input text. | [value: string] |
| focus | Emitted once when the focus enters from outside; moving between the input, tags and clear button does not emit it. | [event: FocusEvent] |
| blur | Emitted once when the focus leaves the whole component; moving inside it does not emit it. | [event: FocusEvent] |
| keydown | Emitted on keydown in the input after the component handled it; calling preventDefault there does not stop Enter from adding a tag. | [event: KeyboardEvent] |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| prefix | Prefix of the input; text or an icon gets side spacing automatically. | any |
| suffix | Suffix of the input; text or an icon gets side spacing automatically. | any |
| insetLabel | Inset label shown before the input; when the prefix slot is also given, only prefix is shown. When shown, it names an input that has no aria-label or aria-labelledby. | any |
| clearIcon | Replaces the icon in the clear button; empty content keeps the default icon. | any |
| tag | Replaces each tag as a whole, with the scoped props value, index and the close function onClose; empty content renders no tag. | TagInputTagSlotProps |
Exposes
| Name | Description | Type |
|---|---|---|
| focus | Focuses the input and activates the component. | Type() => void |
| blur | Removes focus from whichever element inside the component holds it (input, tag or clear button). | Type() => void |
Among the undeclared attributes, id, name, role and the aria-* ones that describe the input (aria-label, aria-labelledby, aria-describedby, aria-invalid, aria-required, aria-controls, aria-expanded, aria-haspopup, aria-autocomplete, aria-activedescendant, aria-errormessage) land on the inner input; class, style, data-*, title and the rest land on the root element, where a @click listener and the component's own activation handler both run. The focus and blur events carry the focusin and focusout of the root element. Because they are declared component events, @change, @focus, @blur and @keydown receive only the component events, not native events bubbling up.
Accessibility
ARIA
- TagInput supports the input of
aria-labelto indicate the function of the TagInput; it lands on the input box, not on the root node; - The root node has the
grouprole, which holds the input, the tags and the clear button as one control; when disabled it carriesaria-disabled="true", otherwise it has noaria-disabled; - When disabled, the input is natively
disabledand the tags are static text without anaria-label; withvalidateStatusset toerrorthe input carriesaria-invalid, and an explicitaria-invalidwins; - With
showClear, tags or input text, and nodisabled, a clear button is rendered. It is a nativebuttonnamed "Clear TagInput value" from the locale, shown on hover or while focus is inside the TagInput. From the input,Tabmoves to the clear button;EnterorSpaceclears, as a mouse click does, and the focus goes back to the input.Backspacein the empty input also removes the last tag.
Keyboard and focus
Enterin the input adds the input text as a tag; with empty input it does nothing.Enteralways has its default action prevented, so a TagInput inside a form never submits it.Backspacein the empty input removes the last tag.- A closable tag is a
role="button"Tab stop placed before the input; with a tag focused,BackspaceorDeletecloses it and the focus goes back to the input. When disabled, tags are static text and not focusable. - With
showClearand some content, the clear button follows the input:Tabreaches it,EnterorSpaceclears, and the focus goes back to the input. - The active state follows the focus: it starts when the focus comes in from outside (click,
Taborfocus()) and ends when it leaves the whole component. Moving the focus between the input, the tags and the clear button is not leaving, so it does not firefocusorblurand does not triggeraddOnBlur. While active, tags folded bymaxTagCountexpand (unlessexpandRestTagsOnClickisfalse). - With
draggableand the component active, each tag gets a drag handle that is its own Tab stop, placed before the tag it belongs to. Focus a handle, pressSpaceto pick up, the arrow keys to move andSpaceto drop; screen readers announce the pick up, moves and drop.
Design Tokens
| Sass variable | Value | Usage |
|---|---|---|
| $transition_duration-tagInput-bg | var(--semi-transition_duration-none) | 标签输入框-背景色-动画持续时间 |
| $transition_function-tagInput-bg | var(--semi-transition_function-easeIn) | 标签输入框-背景色-过渡曲线 |
| $transition_delay-tagInput-bg | var(--semi-transition_delay-none) | 标签输入框-背景色-延迟时间 |
| $transition_duration-tagInput-border | var(--semi-transition_duration-none) | 标签输入框-边框-动画持续时间 |
| $transition_function-tagInput-border | var(--semi-transition_function-easeIn) | 标签输入框-边框-过渡曲线 |
| $transition_delay-tagInput-border | var(--semi-transition_delay-none) | 标签输入框-边框-延迟时间 |
| $color-tagInput-icon-default | var(--semi-color-text-2) | 标签输入框图标颜色 - 默认 |
| $color-tagInput-icon-hover | var(--semi-color-primary-hover) | 标签输入框图标颜色 - 悬浮 |
| $color-tagInput_disabled-text-default | var(--semi-color-disabled-text) | 标签输入框禁用文字颜色 |
| $color-tagInput_disabled-bg | var(--semi-color-disabled-fill) | 标签输入框禁用背景色 |
| $color-tagInput_default-bg-focus | var(--semi-color-fill-0) | 标签输入框背景颜色 - 选中态 |
| $color-tagInput-border-default | transparent | 标签输入框描边颜色 - 默认 |
| $color-tagInput-border-hover | transparent | 标签输入框描边颜色 - 悬浮 |
| $color-tagInput-border-focus | var(--semi-color-focus-border) | 标签输入框描边颜色 - 选中态 |
| $color-tagInput_prefix-default | var(--semi-color-text-2) | 标签输入框 prefix 颜色 |
| $color-tagInput_suffix-default | var(--semi-color-text-2) | 标签输入框 suffix 颜色 |
| $color-tagInput_default-bg-default | var(--semi-color-fill-0) | 标签输入框背景颜色 - 默认 |
| $color-tagInput_default-bg-hover | var(--semi-color-fill-1) | 标签输入框背景颜色 - 悬浮 |
| $color-tagInput_warning-bg-default | var(--semi-color-warning-light-default) | 警告标签输入框背景颜色 - 默认 |
| $color-tagInput_warning-border-default | var(--semi-color-warning-light-default) | 警告标签输入框描边颜色 - 默认 |
| $color-tagInput_warning-bg-hover | var(--semi-color-warning-light-hover) | 警告标签输入框背景颜色 - 悬浮 |
| $color-tagInput_warning-border-hover | var(--semi-color-warning-light-hover) | 警告标签输入框描边颜色 - 悬浮 |
| $color-tagInput_warning-bg-focus | var(--semi-color-warning-light-default) | 警告标签输入框背景颜色 - 选中 |
| $color-tagInput_warning-border-focus | var(--semi-color-warning) | 警告标签输入框描边颜色 - 选中 |
| $color-tagInput_danger-bg-default | var(--semi-color-danger-light-default) | 危险标签输入框背景颜色 - 默认 |
| $color-tagInput_danger-border-default | var(--semi-color-danger-light-default) | 危险标签输入框描边颜色 - 默认 |
| $color-tagInput_danger-bg-hover | var(--semi-color-danger-light-hover) | 危险标签输入框背景颜色 - 悬浮 |
| $color-tagInput_danger-border-hover | var(--semi-color-danger-light-hover) | 危险标签输入框描边颜色 - 悬浮 |
| $color-tagInput_danger-bg-focus | var(--semi-color-danger-light-default) | 危险标签输入框背景颜色 - 选中 |
| $color-tagInput_danger-border-focus | var(--semi-color-danger) | 危险标签输入框描边颜色 - 选中 |
| $color-tagInput_sortable_item_over-bg | var(--semi-color-primary) | 拖拽经过的元素前竖线背景色 |
| $color-tagInput_handler-icon-default | var(--semi-color-text-2) | 可拖拽的标签拖拽按钮颜色 |
| $spacing-tagInput_small-Y | 1px | 小尺寸标签输入框标签顶部外边距 |
| $spacing-tagInput_default-Y | $spacing-super-tight | 默认尺寸标签输入框标签顶部外边距 |
| $spacing-tagInput_large-Y | 3px | 大尺寸标签输入框标签顶部外边距 |
| $spacing-tagInput_wrapper_n_paddingX | $spacing-tight | 标签输入框标签容器水平内边距 |
| $spacing-tagInput_drag_handler-marginRight | 4px | 拖拽handler icon的右外边距 |
| $spacing-tagInput_tag_icon_paddingLeft | 4px | tag中有handler icon时tag的左内边距 |
| $spacing-tagInput_prefix_suffix-marginX | $spacing-base-tight | 标签输入框 prefix/suffix 水平外边距 |
| $height-tagInput-large | $height-control-large | 大尺寸标签输入框高度 |
| $height-tagInput-default | $height-control-default | 默认尺寸标签输入框高度 |
| $height-tagInput-small | $height-control-small | 小尺寸标签输入框高度 |
| $height-tagInput_input_small | 20px | 小尺寸标签输入框Input框高度 |
| $height-tagInput_input_default | 24px | 默认尺寸标签输入框Input框高度 |
| $height-tagInput_input_large | 24px | 大尺寸标签输入框Input框高度 |
| $width-tagInput-clear-medium | $width-icon-medium * 2 | 标签输入框清空按钮宽度 |
| $width-tagInput-border-default | $border-thickness-control | 标签输入框描边描边宽度 - 默认 |
| $width-tagInput-border-hover | $width-tagInput-border-default | 标签输入框描边描边宽度 - 悬浮 |
| $width-tagInput-border-focus | $border-thickness-control-focus | 标签输入框描边宽度 - 选中态 |
| $width-tagInput_sortable_item_over | 2px | 拖拽经过的元素前竖线宽度 |
| $radius-tagInput | var(--semi-border-radius-small) | 标签输入框圆角 |
| $font-tagInput_prefix_suffix-fontWeight | $font-weight-bold | 标签输入框 prefix/suffix 文字字重 |