Tag
Versatile tag component with multiple variants, themes, and interactive features
"use client";
import { Tag } from "@/components/composed/tag/tag";
export function TagDemo() {
return (
<div className="flex flex-wrap gap-2">
<Tag variant="filled" theme="brand">
品牌标签
</Tag>
<Tag variant="outline" theme="success">
成功
</Tag>
<Tag variant="solid" theme="error">
错误
</Tag>
</div>
);
}
Tag 组件是一个功能丰富的标签组件,支持多种变体和主题组合,可用于分类标记、状态展示、标签管理等场景。
概述
- 五种变体:default、solid、outline、filled、filled-outline,满足不同视觉需求
- 五种主题:brand、success、warning、error、neutral,语义化的颜色系统
- 灵活组合:变体和主题可以自由组合,提供 25 种预设样式
- 前缀图标:支持自定义前缀图标,增强标签的表达力
- 可关闭:支持关闭功能,带回调事件和自定义关闭图标
- 添加模式:特殊的添加标签模式,支持动态标签管理
快速开始
import { Tag } from "@/registry/wuhan/composed/tag";
export function Example() {
return (
<div className="flex gap-2">
<Tag variant="filled" theme="brand">品牌标签</Tag>
<Tag variant="outline" theme="success">成功</Tag>
<Tag variant="solid" theme="error" closeable>可关闭</Tag>
</div>
);
}特性
-
变体系统:
default: 纯文本,无背景solid: 纯色背景,白色文字outline: 描边样式filled: 浅色背景和边框filled-outline: 浅色背景,带颜色边框
-
主题系统:每种主题使用对应的 CSS 变量,方便统一调整
-
交互功能:支持点击关闭、添加标签等交互场景
-
易于维护:清晰的样式分离,方便自定义和扩展
安装
代码演示
变体和主题
所有变体和主题的组合展示。
"use client";
import { Tag } from "@/components/composed/tag/tag";
export function TagDefault() {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
Default 变体:
</span>
<div className="flex flex-wrap gap-2">
<Tag variant="default" theme="brand">
品牌
</Tag>
<Tag variant="default" theme="success">
成功
</Tag>
<Tag variant="default" theme="warning">
警告
</Tag>
<Tag variant="default" theme="error">
错误
</Tag>
<Tag variant="default" theme="neutral">
中性
</Tag>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
Solid 变体:
</span>
<div className="flex flex-wrap gap-2">
<Tag variant="solid" theme="brand">
品牌
</Tag>
<Tag variant="solid" theme="success">
成功
</Tag>
<Tag variant="solid" theme="warning">
警告
</Tag>
<Tag variant="solid" theme="error">
错误
</Tag>
<Tag variant="solid" theme="neutral">
中性
</Tag>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
Outline 变体:
</span>
<div className="flex flex-wrap gap-2">
<Tag variant="outline" theme="brand">
品牌
</Tag>
<Tag variant="outline" theme="success">
成功
</Tag>
<Tag variant="outline" theme="warning">
警告
</Tag>
<Tag variant="outline" theme="error">
错误
</Tag>
<Tag variant="outline" theme="neutral">
中性
</Tag>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
Filled 变体:
</span>
<div className="flex flex-wrap gap-2">
<Tag variant="filled" theme="brand">
品牌
</Tag>
<Tag variant="filled" theme="success">
成功
</Tag>
<Tag variant="filled" theme="warning">
警告
</Tag>
<Tag variant="filled" theme="error">
错误
</Tag>
<Tag variant="filled" theme="neutral">
中性
</Tag>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
Filled-outline 变体:
</span>
<div className="flex flex-wrap gap-2">
<Tag variant="filled-outline" theme="brand">
品牌
</Tag>
<Tag variant="filled-outline" theme="success">
成功
</Tag>
<Tag variant="filled-outline" theme="warning">
警告
</Tag>
<Tag variant="filled-outline" theme="error">
错误
</Tag>
<Tag variant="filled-outline" theme="neutral">
中性
</Tag>
</div>
</div>
</div>
);
}
带图标
支持在标签前添加自定义图标。
"use client";
import { Star, Heart, Zap } from "lucide-react";
import { Tag } from "@/components/composed/tag/tag";
export function TagWithIcon() {
return (
<div className="flex flex-wrap gap-2">
<Tag
variant="filled"
theme="brand"
prefixIcon={<Star className="h-3.5 w-3.5" />}
>
收藏
</Tag>
<Tag
variant="outline"
theme="error"
prefixIcon={<Heart className="h-3.5 w-3.5" />}
>
喜欢
</Tag>
<Tag
variant="solid"
theme="warning"
prefixIcon={<Zap className="h-3.5 w-3.5" />}
>
热门
</Tag>
</div>
);
}
可关闭
可关闭的标签,支持关闭回调。
"use client";
import { Tag } from "@/components/composed/tag/tag";
export function TagCloseable() {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
可关闭标签:
</span>
<div className="flex flex-wrap gap-2">
<Tag variant="filled" theme="brand" closeable>
React
</Tag>
<Tag variant="filled" theme="success" closeable>
TypeScript
</Tag>
<Tag variant="filled" theme="warning" closeable>
JavaScript
</Tag>
<Tag variant="filled" theme="error" closeable>
CSS
</Tag>
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
带回调的可关闭标签:
</span>
<div className="flex flex-wrap gap-2">
<Tag
variant="outline"
theme="brand"
closeable
onClose={() => console.log("已关闭标签")}
>
点击 X 查看控制台
</Tag>
</div>
</div>
</div>
);
}
添加模式
动态标签管理,支持添加和删除标签。
"use client";
import * as React from "react";
import { Tag } from "@/components/composed/tag/tag";
import { BlockInput } from "@/components/composed/block-input/block-input";
export function TagAddable() {
const [tags, setTags] = React.useState<string[]>([
"React",
"TypeScript",
"Next.js",
]);
const [isAdding, setIsAdding] = React.useState(false);
const [inputValue, setInputValue] = React.useState("");
const handleAdd = () => {
setIsAdding(true);
};
const handleInputKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && inputValue.trim()) {
setTags([...tags, inputValue.trim()]);
setInputValue("");
setIsAdding(false);
} else if (e.key === "Escape") {
setInputValue("");
setIsAdding(false);
}
};
const handleInputBlur = () => {
if (inputValue.trim()) {
setTags([...tags, inputValue.trim()]);
setInputValue("");
}
setIsAdding(false);
};
const handleClose = (index: number) => {
setTags(tags.filter((_, i) => i !== index));
};
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
动态标签列表(点击添加标签,按回车或失焦保存):
</span>
<div className="flex flex-wrap gap-2 items-center">
{tags.map((tag, index) => (
<Tag
key={index}
variant="filled"
theme="brand"
closeable
onClose={() => handleClose(index)}
>
{tag}
</Tag>
))}
{isAdding ? (
<BlockInput
value={inputValue}
onChange={setInputValue}
onKeyDown={handleInputKeyDown}
onBlur={handleInputBlur}
placeholder="输入标签名称"
autoFocus
className="w-[120px]"
/>
) : (
<Tag addable onAdd={handleAdd} />
)}
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-sm text-[var(--Text-text-secondary)]">
自定义添加文本:
</span>
<div className="flex flex-wrap gap-2">
<Tag addable addText="添加技能" />
<Tag addable addText="新建标签" />
<Tag addable addText="+ 添加" />
</div>
</div>
</div>
);
}
可选中标签
支持单选和多选的可选中标签。
单个 CheckableTag
CheckableTagGroup - 单选模式
当前选中: react
CheckableTagGroup - 多选模式
当前选中: react, typescript
CheckableTagGroup - 禁用状态
CheckableTagGroup - 非受控模式(多选)
"use client";
import * as React from "react";
import {
CheckableTag,
CheckableTagGroup,
} from "@/components/composed/tag/tag";
import { Star } from "lucide-react";
export function TagCheckableExample() {
// 单选模式状态
const [singleValue, setSingleValue] = React.useState<string | number | null>(
"react",
);
// 多选模式状态
const [multipleValue, setMultipleValue] = React.useState<
Array<string | number>
>(["react", "typescript"]);
// 单个 CheckableTag 状态
const [checked1, setChecked1] = React.useState(false);
const [checked2, setChecked2] = React.useState(true);
return (
<div className="flex flex-col gap-8 p-8">
{/* 单个 CheckableTag 示例 */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
单个 CheckableTag
</h3>
<div className="flex flex-wrap gap-2">
<CheckableTag checked={checked1} onChange={setChecked1}>
可选中标签
</CheckableTag>
<CheckableTag checked={checked2} onChange={setChecked2}>
默认选中
</CheckableTag>
<CheckableTag checked={false} disabled>
禁用未选中
</CheckableTag>
<CheckableTag checked={true} disabled>
禁用已选中
</CheckableTag>
<CheckableTag
checked={checked1}
onChange={setChecked1}
icon={<Star className="h-3.5 w-3.5" />}
>
带图标
</CheckableTag>
</div>
</div>
{/* CheckableTagGroup 单选模式 */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
CheckableTagGroup - 单选模式
</h3>
<CheckableTagGroup
options={[
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular" },
{ label: "Svelte", value: "svelte" },
]}
value={singleValue}
onChange={(value) => {
setSingleValue(value as string | number | null);
console.log("单选值:", value);
}}
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
<p className="text-sm text-[var(--Text-text-secondary)]">
当前选中: {singleValue ? String(singleValue) : "无"}
</p>
</div>
{/* CheckableTagGroup 多选模式 */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
CheckableTagGroup - 多选模式
</h3>
<CheckableTagGroup
multiple
options={[
{ label: "React", value: "react" },
{ label: "TypeScript", value: "typescript" },
{ label: "Next.js", value: "nextjs" },
{ label: "Tailwind CSS", value: "tailwind" },
{ label: "Vite", value: "vite" },
]}
value={multipleValue}
onChange={(value) => {
setMultipleValue(value as Array<string | number>);
console.log("多选值:", value);
}}
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
<p className="text-sm text-[var(--Text-text-secondary)]">
当前选中:{" "}
{Array.isArray(multipleValue) ? multipleValue.join(", ") : "无"}
</p>
</div>
{/* CheckableTagGroup 禁用状态 */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
CheckableTagGroup - 禁用状态
</h3>
<CheckableTagGroup
disabled
options={[
{ label: "选项 1", value: "option1" },
{ label: "选项 2", value: "option2" },
{ label: "选项 3", value: "option3" },
]}
defaultValue="option1"
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
</div>
{/* CheckableTagGroup 非受控模式 */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
CheckableTagGroup - 非受控模式(多选)
</h3>
<CheckableTagGroup
multiple
options={[
{ label: "标签 A", value: "tagA" },
{ label: "标签 B", value: "tagB" },
{ label: "标签 C", value: "tagC" },
{ label: "标签 D", value: "tagD" },
]}
defaultValue={["tagA", "tagC"]}
onChange={(value) => {
console.log("非受控多选值变化:", value);
}}
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
</div>
</div>
);
}
API
Tag
标签组件,支持多种变体、主题和交互功能。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | 标签文本内容 |
variant | TagVariant | "default" | 变体:default | solid | outline | filled | filled-outline |
theme | TagTheme | "brand" | 主题:brand | success | warning | error | neutral |
prefixIcon | ReactNode | - | 前缀图标 |
closeable | boolean | false | 是否可关闭 |
closeIcon | ReactNode | - | 自定义关闭图标(默认为 X 图标) |
onClose | () => void | - | 关闭回调函数 |
addable | boolean | false | 是否为添加模式 |
addText | string | "添加标签" | 添加模式的文本 |
onAdd | () => void | - | 添加模式点击回调 |
className | string | - | 自定义类名 |
Example
import { Tag } from "@/registry/wuhan/composed/tag";
import { Star } from "lucide-react";
function TagExamples() {
return (
<div className="flex flex-col gap-4">
{/* 基础用法 */}
<Tag variant="filled" theme="brand">React</Tag>
{/* 带图标 */}
<Tag
variant="solid"
theme="warning"
prefixIcon={<Star className="h-3.5 w-3.5" />}
>
热门
</Tag>
{/* 可关闭 */}
<Tag
variant="outline"
theme="error"
closeable
onClose={() => console.log("标签已关闭")}
>
可删除
</Tag>
{/* 添加模式 */}
<Tag
addable
addText="添加技能"
onAdd={() => console.log("点击添加")}
/>
</div>
);
}CheckableTag
可选中的标签组件,支持单个标签的选中状态切换。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | 标签文本内容 |
checked | boolean | false | 是否选中 |
icon | ReactNode | - | 图标 |
onChange | (checked: boolean) => void | - | 选中状态变化回调 |
disabled | boolean | false | 是否禁用 |
className | string | - | 自定义类名 |
Example
import { CheckableTag } from "@/registry/wuhan/composed/tag";
import { Star } from "lucide-react";
function CheckableTagExample() {
const [checked, setChecked] = useState(false);
return (
<div className="flex gap-2">
{/* 基础用法 */}
<CheckableTag checked={checked} onChange={setChecked}>
可选中标签
</CheckableTag>
{/* 带图标 */}
<CheckableTag
checked={checked}
onChange={setChecked}
icon={<Star className="h-3.5 w-3.5" />}
>
带图标
</CheckableTag>
{/* 禁用状态 */}
<CheckableTag checked={true} disabled>
已禁用
</CheckableTag>
</div>
);
}样式说明
CheckableTag 有两种状态样式:
未选中状态(neutral + filled 变体):
- 默认:浅灰色背景,次要文本颜色
- Hover:
bg-neutral-light-hover背景,text-brand-hover文本 - Disabled:
bg-container-disable背景,text-disabled文本
选中状态(brand + solid 变体):
- 默认:品牌色背景,白色文本
- Hover:
bg-brand-hover背景,text-inverse文本 - Disabled:品牌色背景,
text-inverse文本,透明度bg-solid-disable
CheckableTagGroup
可选中标签组,支持单选和多选模式。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | Array<{ label: ReactNode; value: string | number }> | [] | 选项列表 |
multiple | boolean | false | 是否多选模式 |
value | string | number | Array<string | number> | null | - | 选中值(受控) |
defaultValue | string | number | Array<string | number> | null | - | 初始选中值(非受控) |
disabled | boolean | false | 是否禁用选中 |
onChange | (value: string | number | Array<string | number> | null) => void | - | 选中值变化回调 |
classNames | Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> | - | 自定义各部分类名 |
styles | Record<SemanticDOM, CSSProperties> | (info: { props }) => Record<SemanticDOM, CSSProperties> | - | 自定义各部分样式 |
SemanticDOM
| Key | Description |
|---|---|
root | 根容器 |
tag | 单个标签 |
Example
import { CheckableTagGroup } from "@/registry/wuhan/composed/tag";
function CheckableTagGroupExample() {
const [singleValue, setSingleValue] = useState<string | null>("react");
const [multipleValue, setMultipleValue] = useState<Array<string>>(["react", "vue"]);
return (
<div className="flex flex-col gap-4">
{/* 单选模式 */}
<CheckableTagGroup
options={[
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular" },
]}
value={singleValue}
onChange={(value) => setSingleValue(value as string | null)}
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
{/* 多选模式 */}
<CheckableTagGroup
multiple
options={[
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular" },
]}
value={multipleValue}
onChange={(value) => setMultipleValue(value as Array<string>)}
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
{/* 非受控模式 */}
<CheckableTagGroup
options={[
{ label: "选项 A", value: "optionA" },
{ label: "选项 B", value: "optionB" },
{ label: "选项 C", value: "optionC" },
]}
defaultValue="optionA"
onChange={(value) => console.log("选中值:", value)}
classNames={{
root: "flex flex-wrap gap-2",
tag: "",
}}
/>
</div>
);
}变体说明
Default
纯文本标签,无背景色,使用主题对应的文本颜色。适用于轻量级标记。
Solid
纯色背景,白色文字。视觉冲击力强,适用于重要标签。
Outline
描边样式,透明背景。简洁清爽,适用于分类标记。
Filled
浅色背景和边框,边框颜色与背景色相同。柔和的视觉效果。
Filled-outline
浅色背景,带颜色边框。在 filled 基础上增加边框层次感。
主题说明
每种主题使用对应的 CSS 变量:
| 主题 | CSS 变量示例 |
|---|---|
brand | --text-brand, --bg-brand, --border-brand, --bg-brand-light, --border-brand-light |
success | --text-success, --bg-success, --border-success, --bg-success-light, --border-success-light |
warning | --text-warning, --bg-warning, --border-warning, --bg-warning-light, --border-warning-light |
error | --text-error, --bg-error, --border-error, --bg-error-light, --border-error-light |
neutral | --text-secondary, --bg-neutral, --border-neutral, --bg-neutral-light, --border-neutral-light |
使用场景
标签分类
使用不同的主题区分不同类型的标签:
<div className="flex gap-2">
<Tag variant="filled" theme="brand">前端</Tag>
<Tag variant="filled" theme="success">后端</Tag>
<Tag variant="filled" theme="warning">设计</Tag>
</div>状态展示
使用 solid 变体展示明确的状态:
<Tag variant="solid" theme="success">已完成</Tag>
<Tag variant="solid" theme="warning">进行中</Tag>
<Tag variant="solid" theme="error">已取消</Tag>动态标签管理
结合添加模式和可关闭功能实现标签管理:
const [tags, setTags] = useState(["React", "Vue"]);
<div className="flex gap-2">
{tags.map((tag, i) => (
<Tag
key={i}
variant="filled"
closeable
onClose={() => setTags(tags.filter((_, idx) => idx !== i))}
>
{tag}
</Tag>
))}
<Tag addable onAdd={handleAddTag} />
</div>标签筛选
使用 CheckableTagGroup 实现多选筛选:
const [selectedTags, setSelectedTags] = useState<Array<string>>([]);
<CheckableTagGroup
multiple
options={["前端", "后端", "设计", "产品"]}
value={selectedTags}
onChange={(value) => {
setSelectedTags(value as Array<string>);
// 执行筛选逻辑
}}
classNames={{ root: "flex gap-2", tag: "" }}
/>设计指南
最佳实践
-
选择合适的变体:
- 重要标签使用
solid - 分类标记使用
filled或outline - 次要信息使用
default
- 重要标签使用
-
合理使用主题:
- 保持主题语义一致性(如成功用 success,错误用 error)
- 避免在同一场景混用过多主题
-
图标使用:
- 图标应该增强标签语义,而不是装饰
- 保持图标大小一致(推荐 3.5x3.5)
-
添加模式:
- 添加模式标签应放在标签列表末尾
- 使用清晰的提示文本
-
可选中标签:
- 单选模式用于互斥选择(如筛选条件)
- 多选模式用于多个标签组合(如技能标签)
- 提供清晰的视觉反馈区分选中和未选中状态
可访问性
- 关闭按钮支持键盘导航
- CheckableTag 作为 button 元素,支持完整的键盘交互
- 使用语义化的主题颜色
- 支持自定义关闭图标,可以使用更明确的提示
- 禁用状态有明确的视觉反馈
常见问题
Q: 如何自定义主题颜色?
A: 修改对应的 CSS 变量即可:
:root {
--text-brand: #your-color;
--bg-brand: #your-color;
--border-brand: #your-color;
}Q: 如何实现标签的批量管理?
A: 参考 tag-addable 示例,使用状态管理标签数组:
const [tags, setTags] = useState<string[]>([]);Q: 关闭标签后如何恢复?
A: 组件内部使用 visible 状态控制显示,如需恢复,应在父组件管理标签列表。
Q: 可以同时使用前缀图标和关闭按钮吗?
A: 可以,两者不冲突:
<Tag
prefixIcon={<Star className="h-3.5 w-3.5" />}
closeable
>
可关闭的图标标签
</Tag>Q: CheckableTagGroup 如何实现受控和非受控?
A: 使用 value prop 为受控模式,使用 defaultValue 为非受控模式:
// 受控模式
const [value, setValue] = useState<string | null>(null);
<CheckableTagGroup value={value} onChange={setValue} ... />
// 非受控模式
<CheckableTagGroup defaultValue="option1" onChange={...} ... />Q: 如何在 CheckableTagGroup 中自定义样式?
A: 使用 classNames 或 styles prop:
<CheckableTagGroup
classNames={{
root: "flex flex-wrap gap-3",
tag: "custom-tag-class",
}}
styles={{
root: { padding: "16px" },
tag: { fontSize: "14px" },
}}
/>原语组件
Tag 组件基于以下原语组件构建:
Tag 相关:
TagContainerPrimitive: 容器组件,处理变体和主题样式TagPrefixIconPrimitive: 前缀图标容器TagTextPrimitive: 文本组件TagCloseButtonPrimitive: 关闭按钮组件
CheckableTag 相关:
CheckableTagContainerPrimitive: 可选中标签容器,处理选中状态样式CheckableTagIconPrimitive: 图标容器CheckableTagTextPrimitive: 文本组件
如需更灵活的定制,可以直接使用这些原语组件。