TagInput 标签输入
多标签输入控件,键入回车追加,退格删除,支持粘贴批量导入。
基础用法
v-model 绑定一个 string[]。键入文字后按 Enter 追加;输入框为空时按 Backspace 删除最后一个标签;点击标签上的 × 也可移除。
背景 视口
vuereact
<script setup lang="ts">
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const tags = ref<string[]>(['vue', 'react']);
</script>
<template>
<CfTagInput v-model="tags" placeholder="输入后按回车添加" />
</template> <script setup>
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const tags = ref<string[]>(['vue', 'react']);
</script>
<template>
<CfTagInput v-model="tags" placeholder="输入后按回车添加" />
</template> import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [tags, setTags] = useState<string[]>(['vue', 'react']);
return <CfTagInput value={tags} placeholder="输入后按回车添加" onChange={setTags} />;
} import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [tags, setTags] = useState<string[]>(['vue', 'react']);
return <CfTagInput value={tags} placeholder="输入后按回车添加" onChange={setTags} />;
} 色调
tone 控制标签底色,与 Tag 组件的语义色一一对应。
背景 视口
neutral
accenthighlight
ok
warn
error
<script setup lang="ts">
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const a = ref<string[]>(['neutral']);
const b = ref<string[]>(['accent', 'highlight']);
const c = ref<string[]>(['ok']);
const d = ref<string[]>(['warn']);
const e = ref<string[]>(['error']);
</script>
<template>
<div style="display:flex; flex-direction: column; gap: 8px;">
<CfTagInput v-model="a" tone="neutral" placeholder="neutral" />
<CfTagInput v-model="b" tone="accent" placeholder="accent" />
<CfTagInput v-model="c" tone="success" placeholder="success" />
<CfTagInput v-model="d" tone="warning" placeholder="warning" />
<CfTagInput v-model="e" tone="danger" placeholder="danger" />
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const a = ref<string[]>(['neutral']);
const b = ref<string[]>(['accent', 'highlight']);
const c = ref<string[]>(['ok']);
const d = ref<string[]>(['warn']);
const e = ref<string[]>(['error']);
</script>
<template>
<div style="display:flex; flex-direction: column; gap: 8px;">
<CfTagInput v-model="a" tone="neutral" placeholder="neutral" />
<CfTagInput v-model="b" tone="accent" placeholder="accent" />
<CfTagInput v-model="c" tone="success" placeholder="success" />
<CfTagInput v-model="d" tone="warning" placeholder="warning" />
<CfTagInput v-model="e" tone="danger" placeholder="danger" />
</div>
</template> import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState(['neutral']);
const [a, setA] = useState(['neutral']);
const [b, setB] = useState(['accent', 'highlight']);
const [b, setB] = useState(['accent', 'highlight']);
const [c, setC] = useState(['ok']);
const [c, setC] = useState(['ok']);
const [d, setD] = useState(['warn']);
const [d, setD] = useState(['warn']);
const [e, setE] = useState(['error']);
const [e, setE] = useState(['error']);
return (
<>
<CfTagInput value={a} tone="neutral" onChange={setA} />
<CfTagInput value={b} tone="accent" onChange={setB} />
<CfTagInput value={c} tone="success" onChange={setC} />
<CfTagInput value={d} tone="warning" onChange={setD} />
<CfTagInput value={e} tone="danger" onChange={setE} />
</>
);
} import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState(['neutral']);
const [a, setA] = useState(['neutral']);
const [b, setB] = useState(['accent', 'highlight']);
const [b, setB] = useState(['accent', 'highlight']);
const [c, setC] = useState(['ok']);
const [c, setC] = useState(['ok']);
const [d, setD] = useState(['warn']);
const [d, setD] = useState(['warn']);
const [e, setE] = useState(['error']);
const [e, setE] = useState(['error']);
return (
<>
<CfTagInput value={a} tone="neutral" onChange={setA} />
<CfTagInput value={b} tone="accent" onChange={setB} />
<CfTagInput value={c} tone="success" onChange={setC} />
<CfTagInput value={d} tone="warning" onChange={setD} />
<CfTagInput value={e} tone="danger" onChange={setE} />
</>
);
} 三档尺寸
size 与 Input、Select 保持一致 —— sm / md / lg。
背景 视口
smtag
mdtag
lgtag
<script setup lang="ts">
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const a = ref<string[]>(['sm', 'tag']);
const b = ref<string[]>(['md', 'tag']);
const c = ref<string[]>(['lg', 'tag']);
</script>
<template>
<div class="demo-stack">
<CfTagInput v-model="a" size="sm" placeholder="sm" />
<CfTagInput v-model="b" size="md" placeholder="md" />
<CfTagInput v-model="c" size="lg" placeholder="lg" />
</div>
</template>
<style scoped>
.demo-stack { display: flex; flex-direction: column; gap: 10px; }
</style> <script setup>
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const a = ref<string[]>(['sm', 'tag']);
const b = ref<string[]>(['md', 'tag']);
const c = ref<string[]>(['lg', 'tag']);
</script>
<template>
<div class="demo-stack">
<CfTagInput v-model="a" size="sm" placeholder="sm" />
<CfTagInput v-model="b" size="md" placeholder="md" />
<CfTagInput v-model="c" size="lg" placeholder="lg" />
</div>
</template>
<style scoped>
.demo-stack { display: flex; flex-direction: column; gap: 10px; }
</style> import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState(['sm', 'tag']);
const [a, setA] = useState(['sm', 'tag']);
const [b, setB] = useState(['md', 'tag']);
const [b, setB] = useState(['md', 'tag']);
const [c, setC] = useState(['lg', 'tag']);
const [c, setC] = useState(['lg', 'tag']);
return (
<>
<CfTagInput value={a} onChange={setA} size="sm" />
<CfTagInput value={b} onChange={setB} size="md" />
<CfTagInput value={c} onChange={setC} size="lg" />
</>
);
} import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState(['sm', 'tag']);
const [a, setA] = useState(['sm', 'tag']);
const [b, setB] = useState(['md', 'tag']);
const [b, setB] = useState(['md', 'tag']);
const [c, setC] = useState(['lg', 'tag']);
const [c, setC] = useState(['lg', 'tag']);
return (
<>
<CfTagInput value={a} onChange={setA} size="sm" />
<CfTagInput value={b} onChange={setB} size="md" />
<CfTagInput value={c} onChange={setC} size="lg" />
</>
);
} 视觉变体
variant —— outline(默认,带描边)/ filled(实底,密集表单常用)/ ghost(透明,悬停描边)。error 切到红色边框,常配合表单校验。
背景 视口
outline
filled
ghost
<script setup lang="ts">
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const a = ref<string[]>(['outline']);
const b = ref<string[]>(['filled']);
const c = ref<string[]>(['ghost']);
const d = ref<string[]>([]);
</script>
<template>
<div class="demo-stack">
<CfTagInput v-model="a" variant="outline" placeholder="outline" />
<CfTagInput v-model="b" variant="filled" placeholder="filled" />
<CfTagInput v-model="c" variant="ghost" placeholder="ghost" />
<CfTagInput v-model="d" error placeholder="error 态" />
</div>
</template>
<style scoped>
.demo-stack { display: flex; flex-direction: column; gap: 10px; }
</style> <script setup>
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const a = ref<string[]>(['outline']);
const b = ref<string[]>(['filled']);
const c = ref<string[]>(['ghost']);
const d = ref<string[]>([]);
</script>
<template>
<div class="demo-stack">
<CfTagInput v-model="a" variant="outline" placeholder="outline" />
<CfTagInput v-model="b" variant="filled" placeholder="filled" />
<CfTagInput v-model="c" variant="ghost" placeholder="ghost" />
<CfTagInput v-model="d" error placeholder="error 态" />
</div>
</template>
<style scoped>
.demo-stack { display: flex; flex-direction: column; gap: 10px; }
</style> import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState(['outline']);
const [a, setA] = useState(['outline']);
const [b, setB] = useState(['filled']);
const [b, setB] = useState(['filled']);
const [c, setC] = useState(['ghost']);
const [c, setC] = useState(['ghost']);
const [d, setD] = useState([]);
const [d, setD] = useState([]);
return (
<>
<CfTagInput value={a} onChange={setA} variant="outline" />
<CfTagInput value={b} onChange={setB} variant="filled" />
<CfTagInput value={c} onChange={setC} variant="ghost" />
<CfTagInput value={d} onChange={setD} error placeholder="error 态" />
</>
);
} import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState(['outline']);
const [a, setA] = useState(['outline']);
const [b, setB] = useState(['filled']);
const [b, setB] = useState(['filled']);
const [c, setC] = useState(['ghost']);
const [c, setC] = useState(['ghost']);
const [d, setD] = useState([]);
const [d, setD] = useState([]);
return (
<>
<CfTagInput value={a} onChange={setA} variant="outline" />
<CfTagInput value={b} onChange={setB} variant="filled" />
<CfTagInput value={c} onChange={setC} variant="ghost" />
<CfTagInput value={d} onChange={setD} error placeholder="error 态" />
</>
);
} 自定义分隔符
separators 是一个键名数组,键入这些键就把当前草稿提交为标签。逗号 / 空格 / Tab 都可以加进去。粘贴时会按 , \\n \\t 自动拆分。
背景 视口
<script setup lang="ts">
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const tags = ref<string[]>([]);
</script>
<template>
<CfTagInput
v-model="tags"
:separators="['Enter', ',', ' ']"
placeholder="回车 / 逗号 / 空格 都能分隔"
/>
</template> <script setup>
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const tags = ref<string[]>([]);
</script>
<template>
<CfTagInput
v-model="tags"
:separators="['Enter', ',', ' ']"
placeholder="回车 / 逗号 / 空格 都能分隔"
/>
</template> import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [tags, setTags] = useState([]);
const [tags, setTags] = useState([]);
return (
<>
<CfTagInput
value={tags}
separators={['Enter', ',', ' ']}
placeholder="回车 / 逗号 / 空格 都能分隔"
onChange={setTags}
/>
</>
);
} import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
export default function Demo() {
const [tags, setTags] = useState([]);
const [tags, setTags] = useState([]);
return (
<>
<CfTagInput
value={tags}
separators={['Enter', ',', ' ']}
placeholder="回车 / 逗号 / 空格 都能分隔"
onChange={setTags}
/>
</>
);
} 数量限制 / 校验
max 限制总数,validate 是一个返回 boolean 的函数;不通过的输入会被默默丢弃。
背景 视口
已添加 0 / 5;非法格式不会被加入。
<script setup lang="ts">
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const tags = ref<string[]>([]);
function isEmail(t: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t);
}
</script>
<template>
<div style="display:flex; flex-direction:column; gap:6px;">
<CfTagInput
v-model="tags"
:max="5"
:validate="isEmail"
:separators="['Enter', ',', ' ']"
placeholder="最多 5 个邮箱地址"
/>
<small style="color: var(--fg-3); font-size: 12px;">
已添加 {{ tags.length }} / 5;非法格式不会被加入。
</small>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfTagInput } from '@chufix-design/vue';
const tags = ref<string[]>([]);
function isEmail(t) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t);
}
</script>
<template>
<div style="display:flex; flex-direction:column; gap:6px;">
<CfTagInput
v-model="tags"
:max="5"
:validate="isEmail"
:separators="['Enter', ',', ' ']"
placeholder="最多 5 个邮箱地址"
/>
<small style="color: var(--fg-3); font-size: 12px;">
已添加 {{ tags.length }} / 5;非法格式不会被加入。
</small>
</div>
</template> import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
const isEmail = (t: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t);
export default function Demo() {
const [tags, setTags] = useState<string[]>([]);
return (
<CfTagInput
value={tags}
max={5}
validate={isEmail}
separators={['Enter', ',', ' ']}
placeholder="最多 5 个邮箱地址"
onChange={setTags}
/>
);
} import { useState } from 'react';
import { CfTagInput } from '@chufix-design/react';
const isEmail = (t) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t);
export default function Demo() {
const [tags, setTags] = useState<string[]>([]);
return (
<CfTagInput
value={tags}
max={5}
validate={isEmail}
separators={['Enter', ',', ' ']}
placeholder="最多 5 个邮箱地址"
onChange={setTags}
/>
);
} API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue (Vue) / value (React) | string[] | [] | 当前标签数组 |
placeholder | string | '输入后回车添加' | 占位文案,仅在空状态显示 |
variant | 'outline' | 'filled' | 'ghost' | 'outline' | 视觉模式 |
size | 'sm' | 'md' | 'lg' | 'md' | 尺寸 |
tone | 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'neutral' | 标签底色 |
disabled | boolean | false | 禁用 |
error | boolean | false | 错误态 |
max | number | — | 标签数量上限 |
separators | string[] | ['Enter'] | 触发提交的键,可包含 'Enter' ',' ' ' 'Tab' 或单字符 |
unique | boolean | true | 是否去重,重复的输入会被忽略 |
trim | boolean | true | 提交前是否 trim |
validate | (tag: string) => boolean | — | 返回 false 的输入会被丢弃 |
name | string | — | 透传给隐式表单字段,用于原生表单提交 |
id | string | — | 根元素 id,便于外部 <label for> 关联 |
Events
| Vue 事件 | React 回调 | 载荷类型 | 说明 |
|---|---|---|---|
update:modelValue | onChange | string[] | 标签数组发生变化时(新增或删除)触发 |
add | onAdd | string | 新增单个标签时触发,载荷是被添加的字符串 |
remove | onRemove | (tag: string, index: number) | 删除单个标签时触发;提供原值与位置索引 |
反馈与讨论
TagInput 标签输入 的讨论