代码演示
如何引入
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
基本演示
敲击回车键后,输入内容将成为标签。标签内容如果为空串或者纯空格时,则会被过滤。
抖音
火山
西瓜视频
代码
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :defaultValue="['抖音', '火山', '西瓜视频']" placeholder="请输入..." @change="(v) => console.log(v)" />
</template>批量添加
可以使用 separator 设置分隔符,来实现批量输入,它的默认值为英文逗号。支持多个分隔符以 string[] 格式传入。
代码
<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>批量删除
可使用 showClear 设置是否支持一键删除所有标签和输入框内容。
抖音
火山
代码
<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>禁用
抖音
火山
西瓜视频
代码
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :defaultValue="['抖音', '火山', '西瓜视频']" disabled placeholder="请输入..." showClear />
</template>尺寸大小
通过 size 控制标签输入框的大小尺寸,可选: small 、 default 、 large。
代码
<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>不同校验状态样式
可以使用 validateStatus 设置不同校验状态的样式,它仅影响背景颜色等样式表现,可选值: default 、 warning 、 error。
代码
<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 插槽传入输入框后缀,可以为文本或者图标等节点。
当插槽内容为纯文本或者 Icon 时,会自动带上左右间隔;若为自定义节点,则左右间隔为 0,如需可以在你传入的节点中自行设置。
代码
<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>失焦后自动创建标签
可使用 addOnBlur,设置是否在 blur 事件触发时,将当前 input 的值自动创建成 tag。
抖音
火山
西瓜视频
代码
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput addOnBlur :defaultValue="['抖音', '火山', '西瓜视频']" placeholder="请输入..." @change="(v) => console.log(v)" />
</template>过滤重复标签
可使用 allowDuplicates,设置是否允许创建相同 tag,默认为 true。
抖音
火山
西瓜视频
代码
<script setup lang="ts">
import { TagInput } from '@fibrous/semi-ui-vue';
</script>
<template>
<TagInput :allowDuplicates="false" :defaultValue="['抖音', '火山', '西瓜视频']" placeholder="请输入..." @change="(v) => console.log(v)" />
</template>输入限制
可使用 max 限制标签数量,添加后超出的部分不会成为标签,并且触发 exceed 事件。
可使用 maxLength 限制单个标签的最大长度,超出后将不允许再输入,并且触发 inputExceed 事件。
代码
<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>限制标签展示数量
利用 maxTagCount 可以限制展示的标签数量,超出部分将以 +N 的方式展示。使用 showRestTagsPopover 可以设置在超出 maxTagCount 后,hover +N 是否显示 Popover,并且可以在 restTagsPopoverProps 属性中配置 Popover。
抖音
火山
代码
<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>标签受控
可使用 v-model 绑定标签内容(modelValue 加 update:modelValue)实现标签内容受控。
抖音
代码
<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>输入受控
可使用 v-model:inputValue 绑定输入框内容实现输入内容受控。
代码
<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>回调
抖音
代码
<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>焦点管理
可以通过组件 ref 调用 blur() 和 focus() 方法对焦点进行管理。
抖音
火山
代码
<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>自定义标签渲染
可以使用 #tag 作用域插槽整体替换单个标签,作用域参数为 { value, index, onClose }。插槽内容为空时不渲染该标签。onClose 收到事件时会调用其 preventDefault,把它直接绑到按钮的 @click 即可。
代码
<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>拖拽排序
将 draggable设为 true,开启拖拽排序功能。拖拽功能开启后,聚焦 TagInput(点击或 Tab)后 Tag 可拖拽,焦点离开 TagInput 后 Tag 不可拖拽。拖拽句柄位于标签旁边,可用键盘操作,见下文「键盘和焦点」。值相同的标签也能分别拖拽排序。
抖音
火山
西瓜视频
代码
<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 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| modelValue | 当前标签,配合 v-model 使用;undefined 时按非受控处理,null 表示受控且没有标签;从数组变回 undefined 时清空标签。 | string[] | null | undefined |
| defaultValue | 非受控时的初始标签。 | string[] | semiGlobalDefault("TagInput", "defaultValue") |
| inputValue | 当前输入框内容,配合 v-model:inputValue 使用。 | string | semiGlobalDefault("TagInput", "inputValue") |
| inputValueModifiers | v-model:inputValue 的修饰符 trim 与 number,由 Vue 在使用 v-model:inputValue.trim 时传入。 | ModelModifiers | undefined |
| disabled | 是否禁用。 | boolean | semiGlobalDefault("TagInput", "disabled") |
| maxLength | 单个标签的最大长度。 | number | semiGlobalDefault("TagInput", "maxLength") |
| max | 允许的标签最大数量。 | number | semiGlobalDefault("TagInput", "max") |
| maxTagCount | 标签的最大展示数量,超出后以 +N 形式展示。 | number | semiGlobalDefault("TagInput", "maxTagCount") |
| showRestTagsPopover | 超过 maxTagCount 后,hover 到 +N 时是否通过 Popover 显示剩余标签。 | boolean | semiDefault("TagInput", "showRestTagsPopover", true) |
| restTagsPopoverProps | +N 浮层的 Popover 属性,不含 content。 | PopoverProps | semiGlobalDefault("TagInput", "restTagsPopoverProps") |
| showContentTooltip | 标签文字被截断时,hover 标签是否通过浮层显示全部内容;传对象时可指定浮层类型与透传给浮层的属性。 | boolean | ShowTooltip | semiDefault("TagInput", "showContentTooltip", true) |
| allowDuplicates | 是否允许添加相同的标签。 | boolean | semiDefault("TagInput", "allowDuplicates", true) |
| addOnBlur | 焦点离开组件时,是否将输入框内容自动创建为标签。 | boolean | semiDefault("TagInput", "addOnBlur", false) |
| draggable | 是否开启拖拽排序。 | boolean | semiDefault("TagInput", "draggable", false) |
| expandRestTagsOnClick | 不可拖拽时,焦点进入组件(点击、Tab 或 focus 方法)后是否展开被折叠的标签。 | boolean | semiDefault("TagInput", "expandRestTagsOnClick", true) |
| placeholder | 没有标签时输入框的占位文字。 | string | semiGlobalDefault("TagInput", "placeholder") |
| insetLabelId | 内嵌标签容器的 id,供 aria-labelledby 引用。 | string | undefined |
| separator | 批量输入时的分隔符,可传多个;传 null 不拆分。 | string | string[] | null | semiDefault("TagInput", "separator", ",") |
| showClear | 是否显示一键清除所有标签和输入内容的按钮。 | boolean | semiDefault("TagInput", "showClear", false) |
| size | 尺寸,可选 small、default、large。 | Size | semiDefault("TagInput", "size", "default") |
| split | 自定义分隔符处理函数,返回拆分后的字符串数组。 | Type((originString: string, separators: string | string[] | null) => string[]) | undefined |
| validateStatus | 校验状态,仅影响样式;error 时输入框带 aria-invalid。 | TagInputValidateStatus | semiDefault("TagInput", "validateStatus", "default") |
| autofocus | 挂载时是否自动聚焦输入框并激活组件,禁用时不生效。 | boolean | semiDefault("TagInput", "autofocus", false) |
| preventScroll | 组件内 focus 方法是否阻止浏览器滚动到新聚焦的元素。 | boolean | semiGlobalDefault("TagInput", "preventScroll") |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 标签变化时触发,供 v-model 使用。 | [value: string[]] |
| change | 标签变化时触发。 | [value: string[]] |
| update:inputValue | 输入框内容变化时触发,供 v-model:inputValue 使用。 | [value: string] |
| inputChange | 输入框内容变化时触发;事件对象可能是 input、keydown、focusout、click 或 composition 事件,读取专有字段前先按 event.type 收窄。 | [value: string, event: Event] |
| add | 添加标签时触发,参数为新增的标签。 | [addedValue: string[]] |
| remove | 移除标签时触发,参数为被移除的标签及其序号。 | [removedValue: string, index: number] |
| exceed | 添加后超过 max 时触发,参数为截断前的完整列表。 | [value: string[]] |
| inputExceed | 输入内容超过 maxLength 时触发,参数为被拒绝的输入内容。 | [value: string] |
| focus | 焦点从组件外进入时触发一次;焦点在输入框、标签、清除按钮之间移动不触发。 | [event: FocusEvent] |
| blur | 焦点离开整个组件时触发一次;焦点在组件内部移动不触发。 | [event: FocusEvent] |
| keydown | 输入框按键时在组件处理之后触发;其中调用 preventDefault 不会阻止 Enter 添加标签。 | [event: KeyboardEvent] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| prefix | 输入框前缀,文字或图标会自动带上左右间隔。 | any |
| suffix | 输入框后缀,文字或图标会自动带上左右间隔。 | any |
| insetLabel | 内嵌标签,显示在输入框前;同时提供 prefix 插槽时只显示 prefix。显示时,没有 aria-label、aria-labelledby 的输入框以它命名。 | any |
| clearIcon | 替换清除按钮中的图标;内容为空时使用默认图标。 | any |
| tag | 整体替换单个标签,作用域参数为 value、index 和关闭函数 onClose;内容为空时不渲染该标签。 | TagInputTagSlotProps |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| focus | 使输入框获得焦点并激活组件。 | Type() => void |
| blur | 使组件内任意持有焦点的元素(输入框、标签、清除按钮)失去焦点。 | Type() => void |
未声明的属性中,id、name、role 与 aria-* 里描述输入框的部分(aria-label、aria-labelledby、aria-describedby、aria-invalid、aria-required、aria-controls、aria-expanded、aria-haspopup、aria-autocomplete、aria-activedescendant、aria-errormessage)落在内部输入框上;class、style、data-*、title 等其余属性落在根元素上,根元素上的 @click 与组件内部的激活处理都会执行。focus、blur 事件的对象是根元素上的 focusin、focusout,声明为组件事件后,@change、@focus、@blur、@keydown 只收到组件事件,不再收到冒泡上来的原生事件。
Accessibility
ARIA
- TagInput 支持传入
aria-label来表示该 TagInput 作用,它落在输入框上,根节点不带; - 根节点的角色是
group,把输入框、标签和清除按钮作为一个整体;禁用时根节点带aria-disabled="true",未禁用时不带; - 禁用时输入框是原生
disabled,标签是静态文本,不带aria-label;validateStatus为error时输入框带aria-invalid,显式传入的aria-invalid优先; showClear开启、有标签或输入内容且未禁用时,渲染清除按钮。它是原生button,名称为语言包中的「清空标签输入」,在 hover 或焦点位于 TagInput 内时显示。从输入框按Tab移到清除按钮,Enter或Space清空,效果与鼠标点击相同,清空后焦点回到输入框;也可在空输入框中按Backspace删除最后一个标签。
键盘和焦点
- 输入框中按
Enter把输入内容添加为标签,空输入时不做任何事。Enter一律阻止默认行为,TagInput 位于表单内时不会提交表单。 - 输入框为空时按
Backspace删除最后一个标签。 - 可关闭的标签是
role="button"的 Tab 停靠点,位于输入框之前;聚焦标签后按Backspace或Delete关闭它,标签被移除后焦点回到输入框。禁用时标签是静态文本,不可聚焦。 - 开启
showClear且有内容时,清除按钮在输入框之后:Tab到达,Enter或Space清空,焦点回到输入框。 - 激活状态跟随焦点:焦点从组件外进入(点击、
Tab或focus())即激活,焦点离开整个组件即退出。焦点在输入框、标签与清除按钮之间移动不算离开组件,不触发focus、blur,也不会触发addOnBlur。激活时,被maxTagCount折叠的标签会展开(expandRestTagsOnClick为false时不展开)。 - 开启
draggable并激活后,每个标签前出现拖拽句柄,句柄是独立的 Tab 停靠点,位于它所属的标签之前。聚焦句柄后按Space拿起,方向键移动,Space放下,读屏会播报拿起、移动与放下。
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $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 文字字重 |