diff --git a/src-next/tag/README.md b/src-next/tag/README.md
new file mode 100644
index 000000000..eb36c57e8
--- /dev/null
+++ b/src-next/tag/README.md
@@ -0,0 +1,139 @@
+# Tag
+
+### Install
+
+```js
+import Vue from 'vue';
+import { Tag } from 'vant';
+
+Vue.use(Tag);
+```
+
+## Usage
+
+### Basic Usage
+
+```html
+Tag
+Tag
+Tag
+Tag
+Tag
+```
+
+### Round style
+
+```html
+Tag
+Tag
+Tag
+Tag
+Tag
+```
+
+### Mark style
+
+```html
+Tag
+Tag
+Tag
+Tag
+Tag
+```
+
+### Plain style
+
+```html
+Tag
+Tag
+Tag
+Tag
+Tag
+```
+
+### Custom Color
+
+```html
+Tag
+Tag
+Tag
+Tag
+Tag
+```
+
+### Custom Size
+
+```html
+Tag
+Tag
+Tag
+```
+
+### Closeable
+
+```html
+
+ Tag
+
+
+ Tag
+
+```
+
+```js
+export default {
+ data() {
+ return {
+ show: {
+ primary: true,
+ success: true,
+ },
+ };
+ },
+ methods: {
+ close(type) {
+ this.show[type] = false;
+ },
+ },
+};
+```
+
+## API
+
+### Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| type | Type, can be set to `primary` `success` `danger` `warning` | _string_ | `default` |
+| size | Size, can be set to `large` `medium` | _string_ | - |
+| color | Custom color | _string_ | - |
+| plain | Whether to be plain style | _boolean_ | `false` |
+| round | Whether to be round style | _boolean_ | `false` |
+| mark | Whether to be mark style | _boolean_ | `false` |
+| text-color | Text color | _string_ | `white` |
+| closeable `v2.2.9` | Whether to be closeable | _boolean_ | `false` |
+
+### Slots
+
+| Name | Description |
+| ------- | ------------ |
+| default | Default slot |
+
+### Events
+
+| Event | Description | Arguments |
+| ----- | ------------------------------- | -------------- |
+| click | Triggered when clicked | _event: Event_ |
+| close | Triggered when click close icon | - |
diff --git a/src-next/tag/README.zh-CN.md b/src-next/tag/README.zh-CN.md
new file mode 100644
index 000000000..34b682c6f
--- /dev/null
+++ b/src-next/tag/README.zh-CN.md
@@ -0,0 +1,149 @@
+# Tag 标记
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { Tag } from 'vant';
+
+Vue.use(Tag);
+```
+
+## 代码演示
+
+### 基础用法
+
+通过`type`属性控制标签颜色,默认为灰色
+
+```html
+标签
+标签
+标签
+标签
+标签
+```
+
+### 圆角样式
+
+通过`round`设置为圆角样式
+
+```html
+标签
+标签
+标签
+标签
+标签
+```
+
+### 标记样式
+
+通过`mark`设置为标记样式(半圆角)
+
+```html
+标签
+标签
+标签
+标签
+标签
+```
+
+### 空心样式
+
+设置`plain`属性设置为空心样式
+
+```html
+标签
+标签
+标签
+标签
+标签
+```
+
+### 自定义颜色
+
+```html
+标签
+标签
+标签
+标签
+标签
+```
+
+### 标签大小
+
+```html
+标签
+标签
+标签
+```
+
+### 可关闭标签
+
+添加`closeable`属性表示标签是可关闭的,关闭标签时会触发`close`事件,在`close`事件中可以执行隐藏标签的逻辑
+
+```html
+
+ 标签
+
+
+ 标签
+
+```
+
+```js
+export default {
+ data() {
+ return {
+ show: {
+ primary: true,
+ success: true,
+ },
+ };
+ },
+ methods: {
+ close(type) {
+ this.show[type] = false;
+ },
+ },
+};
+```
+
+## API
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| type | 类型,可选值为`primary` `success` `danger` `warning` | _string_ | `default` |
+| size | 大小, 可选值为`large` `medium` | _string_ | - |
+| color | 标签颜色 | _string_ | - |
+| plain | 是否为空心样式 | _boolean_ | `false` |
+| round | 是否为圆角样式 | _boolean_ | `false` |
+| mark | 是否为标记样式 | _boolean_ | `false` |
+| text-color | 文本颜色,优先级高于`color`属性 | _string_ | `white` |
+| closeable `v2.2.9` | 是否为可关闭标签 | _boolean_ | `false` |
+
+### Slots
+
+| 名称 | 说明 |
+| ------- | ------------ |
+| default | 标签显示内容 |
+
+### Events
+
+| 事件名 | 说明 | 回调参数 |
+| ------ | -------------- | -------------- |
+| click | 点击时触发 | _event: Event_ |
+| close | 关闭标签时触发 | - |
diff --git a/src-next/tag/demo/index.vue b/src-next/tag/demo/index.vue
new file mode 100644
index 000000000..0ea06aeba
--- /dev/null
+++ b/src-next/tag/demo/index.vue
@@ -0,0 +1,126 @@
+
+
+
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+
+
+
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+
+
+
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+
+
+
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+
+
+
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+
+
+
+ {{ t('tag') }}
+ {{ t('tag') }}
+ {{ t('tag') }}
+
+
+
+
+ {{ t('tag') }}
+
+
+ {{ t('tag') }}
+
+
+
+
+
+
+
+
diff --git a/src-next/tag/index.js b/src-next/tag/index.js
new file mode 100644
index 000000000..8b3543438
--- /dev/null
+++ b/src-next/tag/index.js
@@ -0,0 +1,66 @@
+// Utils
+import { createNamespace } from '../utils';
+import { BORDER_SURROUND } from '../utils/constant';
+
+// Components
+import Icon from '../icon';
+
+const [createComponent, bem] = createNamespace('tag');
+
+export default createComponent({
+ props: {
+ size: String,
+ mark: Boolean,
+ color: String,
+ plain: Boolean,
+ round: Boolean,
+ textColor: String,
+ closeable: Boolean,
+ type: {
+ type: String,
+ default: 'default',
+ },
+ },
+
+ setup(props, { slots, emit }) {
+ return function () {
+ const { type, mark, plain, color, round, size } = props;
+
+ const key = plain ? 'color' : 'backgroundColor';
+ const style = { [key]: color };
+
+ if (props.textColor) {
+ style.color = props.textColor;
+ }
+
+ const classes = { mark, plain, round };
+ if (size) {
+ classes[size] = size;
+ }
+
+ const CloseIcon = props.closeable && (
+ {
+ event.stopPropagation();
+ emit('close');
+ }}
+ />
+ );
+
+ return (
+
+
+ {slots.default?.()}
+ {CloseIcon}
+
+
+ );
+ };
+ },
+});
diff --git a/src-next/tag/index.less b/src-next/tag/index.less
new file mode 100644
index 000000000..b7ebdb053
--- /dev/null
+++ b/src-next/tag/index.less
@@ -0,0 +1,90 @@
+@import '../style/var';
+
+.van-tag {
+ display: inline-flex;
+ align-items: center;
+ padding: @tag-padding;
+ color: @tag-text-color;
+ font-size: @tag-font-size;
+ line-height: normal;
+ border-radius: @tag-border-radius;
+
+ &::after {
+ border-color: currentColor;
+ border-radius: @tag-border-radius * 2;
+ }
+
+ &--default {
+ background-color: @tag-default-color;
+
+ &.van-tag--plain {
+ color: @tag-default-color;
+ }
+ }
+
+ &--danger {
+ background-color: @tag-danger-color;
+
+ &.van-tag--plain {
+ color: @tag-danger-color;
+ }
+ }
+
+ &--primary {
+ background-color: @tag-primary-color;
+
+ &.van-tag--plain {
+ color: @tag-primary-color;
+ }
+ }
+
+ &--success {
+ background-color: @tag-success-color;
+
+ &.van-tag--plain {
+ color: @tag-success-color;
+ }
+ }
+
+ &--warning {
+ background-color: @tag-warning-color;
+
+ &.van-tag--plain {
+ color: @tag-warning-color;
+ }
+ }
+
+ &--plain {
+ background-color: @tag-plain-background-color;
+ }
+
+ &--mark {
+ padding-right: 0.7em;
+
+ &,
+ &::after {
+ border-radius: 0 @tag-round-border-radius @tag-round-border-radius 0;
+ }
+ }
+
+ &--round {
+ &,
+ &::after {
+ border-radius: @tag-round-border-radius;
+ }
+ }
+
+ &--medium {
+ font-size: @tag-medium-font-size;
+ }
+
+ &--large {
+ font-size: @tag-large-font-size;
+ }
+
+ &__close {
+ min-width: 1em;
+ margin-left: 2px;
+ cursor: pointer;
+ }
+}
diff --git a/src-next/tag/test/__snapshots__/demo.spec.js.snap b/src-next/tag/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..622597aba
--- /dev/null
+++ b/src-next/tag/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+
标签 标签 标签 标签 标签
+
标签 标签 标签 标签 标签
+
标签 标签 标签 标签 标签
+
标签 标签 标签 标签 标签
+
标签 标签 标签 标签 标签
+
标签 标签 标签
+
+ 标签
+
+ 标签
+
+
+`;
diff --git a/src-next/tag/test/demo.spec.js b/src-next/tag/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/tag/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/tag/test/index.spec.js b/src-next/tag/test/index.spec.js
new file mode 100644
index 000000000..864947a4f
--- /dev/null
+++ b/src-next/tag/test/index.spec.js
@@ -0,0 +1,54 @@
+import Tag from '..';
+import { mount } from '../../../test';
+
+test('click event', () => {
+ const click = jest.fn();
+ const wrapper = mount(Tag, {
+ context: {
+ on: {
+ click,
+ },
+ },
+ });
+
+ wrapper.trigger('click');
+ expect(click).toHaveBeenCalledTimes(1);
+});
+
+test('close event', () => {
+ const close = jest.fn();
+ const wrapper = mount(Tag, {
+ propsData: {
+ closeable: true,
+ },
+ context: {
+ on: {
+ close,
+ },
+ },
+ });
+
+ wrapper.find('.van-tag__close').trigger('click');
+ expect(close).toHaveBeenCalledTimes(1);
+});
+
+test('should not trigger click event when close', () => {
+ const close = jest.fn();
+ const click = jest.fn();
+
+ const wrapper = mount(Tag, {
+ propsData: {
+ closeable: true,
+ },
+ context: {
+ on: {
+ close,
+ click,
+ },
+ },
+ });
+
+ wrapper.find('.van-tag__close').trigger('click');
+ expect(close).toHaveBeenCalledTimes(1);
+ expect(click).toHaveBeenCalledTimes(0);
+});
diff --git a/vant.config.js b/vant.config.js
index 34735e591..68bc655c6 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -277,10 +277,10 @@ module.exports = {
// path: 'swipe',
// title: 'Swipe 轮播',
// },
- // {
- // path: 'tag',
- // title: 'Tag 标记',
- // },
+ {
+ path: 'tag',
+ title: 'Tag 标记',
+ },
],
},
{
@@ -611,10 +611,10 @@ module.exports = {
// path: 'swipe',
// title: 'Swipe',
// },
- // {
- // path: 'tag',
- // title: 'Tag',
- // },
+ {
+ path: 'tag',
+ title: 'Tag',
+ },
],
},
{