diff --git a/src-next/skeleton/README.md b/src-next/skeleton/README.md
new file mode 100644
index 000000000..3cf31e308
--- /dev/null
+++ b/src-next/skeleton/README.md
@@ -0,0 +1,62 @@
+# Skeleton
+
+### Install
+
+```js
+import Vue from 'vue';
+import { Skeleton } from 'vant';
+
+Vue.use(Skeleton);
+```
+
+## Usage
+
+### Basic Usage
+
+```html
+
+```
+
+### Show Avatar
+
+```html
+
+```
+
+### Show Children
+
+```html
+
+ Content
+
+```
+
+```js
+export default {
+ data() {
+ return {
+ loading: true,
+ };
+ },
+ mounted() {
+ this.loading = false;
+ },
+};
+```
+
+## API
+
+### Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| row | Row count | _number \| string_ | `0` |
+| row-width | Row width, can be array | _number \| string \|
(number \| string)[]_ | `100%` |
+| title | Whether to show title placeholder | _boolean_ | `false` |
+| avatar | Whether to show avatar placeholder | _boolean_ | `false` |
+| loading | Whether to show skeleton,pass `false` to show child component | _boolean_ | `true` |
+| animate | Whether to enable animation | _boolean_ | `true` |
+| round `v2.8.5` | Whether to show round title and row | _boolean_ | `false` |
+| title-width | Title width | _number \| string_ | `40%` |
+| avatar-size | Size of avatar placeholder | _number \| string_ | `32px` |
+| avatar-shape | Shape of avatar placeholder,can be set to `square` | _string_ | `round` |
diff --git a/src-next/skeleton/README.zh-CN.md b/src-next/skeleton/README.zh-CN.md
new file mode 100644
index 000000000..fc69a07ee
--- /dev/null
+++ b/src-next/skeleton/README.zh-CN.md
@@ -0,0 +1,68 @@
+# Skeleton 骨架屏
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { Skeleton } from 'vant';
+
+Vue.use(Skeleton);
+```
+
+## 代码演示
+
+### 基础用法
+
+通过`title`属性显示标题占位图,通过`row`属性配置占位段落行数
+
+```html
+
+```
+
+### 显示头像
+
+通过`avatar`属性显示头像占位图
+
+```html
+
+```
+
+### 展示子组件
+
+将`loading`属性设置成`false`表示内容加载完成,此时会隐藏占位图,并显示`Skeleton`的子组件
+
+```html
+
+ 实际内容
+
+```
+
+```js
+export default {
+ data() {
+ return {
+ loading: true,
+ };
+ },
+ mounted() {
+ this.loading = false;
+ },
+};
+```
+
+## API
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| row | 段落占位图行数 | _number \| string_ | `0` |
+| row-width | 段落占位图宽度,可传数组来设置每一行的宽度 | _number \| string \|
(number \| string)[]_ | `100%` |
+| title | 是否显示标题占位图 | _boolean_ | `false` |
+| avatar | 是否显示头像占位图 | _boolean_ | `false` |
+| loading | 是否显示骨架屏,传 `false` 时会展示子组件内容 | _boolean_ | `true` |
+| animate | 是否开启动画 | _boolean_ | `true` |
+| round `v2.8.5` | 是否将标题和段落显示为圆角风格 | _boolean_ | `false` |
+| title-width | 标题占位图宽度 | _number \| string_ | `40%` |
+| avatar-size | 头像占位图大小 | _number \| string_ | `32px` |
+| avatar-shape | 头像占位图形状,可选值为`square` | _string_ | `round` |
diff --git a/src-next/skeleton/demo/index.vue b/src-next/skeleton/demo/index.vue
new file mode 100644
index 000000000..9dc9591d7
--- /dev/null
+++ b/src-next/skeleton/demo/index.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
{{ t('title') }}
+
{{ t('desc') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src-next/skeleton/index.js b/src-next/skeleton/index.js
new file mode 100644
index 000000000..c7b767726
--- /dev/null
+++ b/src-next/skeleton/index.js
@@ -0,0 +1,110 @@
+import { createNamespace, addUnit } from '../utils';
+
+const [createComponent, bem] = createNamespace('skeleton');
+const DEFAULT_ROW_WIDTH = '100%';
+const DEFAULT_LAST_ROW_WIDTH = '60%';
+
+export default createComponent({
+ props: {
+ title: Boolean,
+ round: Boolean,
+ avatar: Boolean,
+ row: {
+ type: [Number, String],
+ default: 0,
+ },
+ loading: {
+ type: Boolean,
+ default: true,
+ },
+ animate: {
+ type: Boolean,
+ default: true,
+ },
+ avatarSize: {
+ type: String,
+ default: '32px',
+ },
+ avatarShape: {
+ type: String,
+ default: 'round',
+ },
+ titleWidth: {
+ type: [Number, String],
+ default: '40%',
+ },
+ rowWidth: {
+ type: [Number, String, Array],
+ default: DEFAULT_ROW_WIDTH,
+ },
+ },
+
+ setup(props, { slots }) {
+ return function () {
+ if (!props.loading) {
+ return slots.default && slots.default();
+ }
+
+ function Title() {
+ if (props.title) {
+ return (
+
+ );
+ }
+ }
+
+ function Rows() {
+ const Rows = [];
+ const { rowWidth } = props;
+
+ function getRowWidth(index) {
+ if (rowWidth === DEFAULT_ROW_WIDTH && index === +props.row - 1) {
+ return DEFAULT_LAST_ROW_WIDTH;
+ }
+
+ if (Array.isArray(rowWidth)) {
+ return rowWidth[index];
+ }
+
+ return rowWidth;
+ }
+
+ for (let i = 0; i < props.row; i++) {
+ Rows.push(
+
+ );
+ }
+
+ return Rows;
+ }
+
+ function Avatar() {
+ if (props.avatar) {
+ const size = addUnit(props.avatarSize);
+ return (
+
+ );
+ }
+ }
+
+ return (
+
+ {Avatar()}
+
+ {Title()}
+ {Rows()}
+
+
+ );
+ };
+ },
+});
diff --git a/src-next/skeleton/index.less b/src-next/skeleton/index.less
new file mode 100644
index 000000000..95ab87c47
--- /dev/null
+++ b/src-next/skeleton/index.less
@@ -0,0 +1,62 @@
+@import '../style/var';
+
+.van-skeleton {
+ display: flex;
+ padding: 0 @padding-md;
+
+ &__avatar {
+ flex-shrink: 0;
+ margin-right: @padding-md;
+ background-color: @skeleton-avatar-background-color;
+
+ &--round {
+ border-radius: @border-radius-max;
+ }
+ }
+
+ &__content {
+ width: 100%;
+ }
+
+ &__avatar + &__content {
+ padding-top: @padding-xs;
+ }
+
+ &__row,
+ &__title {
+ height: @skeleton-row-height;
+ background-color: @skeleton-row-background-color;
+ }
+
+ &__title {
+ margin: 0;
+ }
+
+ &__row {
+ &:not(:first-child) {
+ margin-top: @skeleton-row-margin-top;
+ }
+ }
+
+ &__title + &__row {
+ margin-top: 20px;
+ }
+
+ &--animate {
+ animation: van-skeleton-blink @skeleton-animation-duration ease-in-out
+ infinite;
+ }
+
+ &--round {
+ .van-skeleton__row,
+ .van-skeleton__title {
+ border-radius: @border-radius-max;
+ }
+ }
+}
+
+@keyframes van-skeleton-blink {
+ 50% {
+ opacity: 0.6;
+ }
+}
diff --git a/src-next/skeleton/test/__snapshots__/demo.spec.js.snap b/src-next/skeleton/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..785a20ad3
--- /dev/null
+++ b/src-next/skeleton/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+`;
diff --git a/src-next/skeleton/test/__snapshots__/index.spec.js.snap b/src-next/skeleton/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..0ffcccc95
--- /dev/null
+++ b/src-next/skeleton/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`avatar shape 1`] = `
+
+`;
+
+exports[`disable animate 1`] = `
+
+`;
+
+exports[`render chidren 1`] = `Content
`;
+
+exports[`round prop 1`] = `
+
+`;
+
+exports[`row-width array 1`] = `
+
+`;
diff --git a/src-next/skeleton/test/demo.spec.js b/src-next/skeleton/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/skeleton/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/skeleton/test/index.spec.js b/src-next/skeleton/test/index.spec.js
new file mode 100644
index 000000000..d46140c9d
--- /dev/null
+++ b/src-next/skeleton/test/index.spec.js
@@ -0,0 +1,56 @@
+import { mount } from '../../../test';
+import Skeleton from '..';
+
+test('row-width array', () => {
+ const wrapper = mount(Skeleton, {
+ propsData: {
+ row: 4,
+ rowWidth: ['100%', 30, '5rem'],
+ },
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('render chidren', () => {
+ const wrapper = mount({
+ template: `
+
+ Content
+
+ `,
+ components: { Skeleton },
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('avatar shape', () => {
+ const wrapper = mount(Skeleton, {
+ propsData: {
+ avatar: true,
+ avatarSize: 20,
+ avatarShape: 'square',
+ },
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('round prop', () => {
+ const wrapper = mount(Skeleton, {
+ propsData: {
+ title: true,
+ round: true,
+ avatar: true,
+ },
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('disable animate', () => {
+ const wrapper = mount(Skeleton, {
+ propsData: {
+ row: 1,
+ aniamte: false,
+ },
+ });
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/vant.config.js b/vant.config.js
index 71e7cf38f..208884089 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -261,10 +261,10 @@ module.exports = {
// path: 'progress',
// title: 'Progress 进度条',
// },
- // {
- // path: 'skeleton',
- // title: 'Skeleton 骨架屏',
- // },
+ {
+ path: 'skeleton',
+ title: 'Skeleton 骨架屏',
+ },
// {
// path: 'steps',
// title: 'Steps 步骤条',
@@ -595,10 +595,10 @@ module.exports = {
// path: 'progress',
// title: 'Progress',
// },
- // {
- // path: 'skeleton',
- // title: 'Skeleton',
- // },
+ {
+ path: 'skeleton',
+ title: 'Skeleton',
+ },
// {
// path: 'steps',
// title: 'Steps',