diff --git a/packages/varlet-ui/src/card/Card.vue b/packages/varlet-ui/src/card/Card.vue
new file mode 100644
index 00000000000..29a2c5c2caa
--- /dev/null
+++ b/packages/varlet-ui/src/card/Card.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
{{ title }}
+
{{ subtitle }}
+
+ {{ desc }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/varlet-ui/src/card/__tests__/index.spec.js b/packages/varlet-ui/src/card/__tests__/index.spec.js
new file mode 100644
index 00000000000..d420f9dbe50
--- /dev/null
+++ b/packages/varlet-ui/src/card/__tests__/index.spec.js
@@ -0,0 +1,14 @@
+import example from '../example'
+import Card from '..'
+import { mount } from '@vue/test-utils'
+import { createApp } from 'vue'
+
+test('test card example', () => {
+ const wrapper = mount(example)
+ expect(wrapper.html()).toMatchSnapshot()
+})
+
+test('test card plugin', () => {
+ const app = createApp({}).use(Card)
+ expect(app.component(Card.name)).toBeTruthy()
+})
diff --git a/packages/varlet-ui/src/card/card.less b/packages/varlet-ui/src/card/card.less
new file mode 100644
index 00000000000..83869eeb661
--- /dev/null
+++ b/packages/varlet-ui/src/card/card.less
@@ -0,0 +1,55 @@
+@import '../styles/var';
+@import '../styles/elevation';
+
+@card-title-font-size: 20px;
+@card-title-font-weight: 700;
+@card-subtitle-font-size: 14px;
+@card-subtitle-font-weight: normal;
+@card-desc-font-size: 16px;
+@card-subtitle-color: rgba(0, 0, 0, 0.6);
+@card-desc-color: #333;
+@card-padding: 10px 12px;
+@card-row-spacing: 10px;
+@card-border-color: #f0f0f0;
+@card-border-left: 12px;
+@card-border-right: 12px;
+@card-border-radius: 4px;
+@card-line-height: 30px;
+@card-image-width: 100%;
+@card-image-height-default: 200px;
+@card-padding-bottom: 10px;
+.var-card {
+ border: 1px solid @card-border-color;
+ border-radius: @card-border-radius;
+ overflow: hidden;
+ padding-bottom: @card-padding-bottom;
+ &__image {
+ width: @card-image-width;
+ height: @card-image-height-default;
+ display: block;
+ }
+ &__content {
+ display: flex;
+ flex-direction: column;
+ padding: @card-padding;
+ }
+ &__title {
+ position: relative;
+ font-size: @card-title-font-size;
+ font-weight: @card-title-font-weight;
+ }
+ &__subtitle {
+ font-size: @card-subtitle-font-size;
+ font-weight: @card-subtitle-font-weight;
+ color: @card-subtitle-color;
+ margin: @card-row-spacing 0;
+ }
+ &__desc {
+ font-size: @card-desc-font-size;
+ color: @card-desc-color;
+ margin-top: @card-row-spacing;
+ }
+ &__footer {
+ padding: @card-padding;
+ }
+}
diff --git a/packages/varlet-ui/src/card/docs/en-US.md b/packages/varlet-ui/src/card/docs/en-US.md
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/varlet-ui/src/card/docs/zh-CN.md b/packages/varlet-ui/src/card/docs/zh-CN.md
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/varlet-ui/src/card/example/index.vue b/packages/varlet-ui/src/card/example/index.vue
new file mode 100644
index 00000000000..de543fc22cd
--- /dev/null
+++ b/packages/varlet-ui/src/card/example/index.vue
@@ -0,0 +1,48 @@
+
+ {{ pack.basicUsage }}
+
+ {{ pack.showSubtitle }}
+
+ {{ pack.showImage }}
+
+ {{ pack.useSlot }}
+
+
+ {{ pack.button }}
+
+
+
+
+
+
+
diff --git a/packages/varlet-ui/src/card/example/locale/en-US.ts b/packages/varlet-ui/src/card/example/locale/en-US.ts
new file mode 100644
index 00000000000..d2d86282e89
--- /dev/null
+++ b/packages/varlet-ui/src/card/example/locale/en-US.ts
@@ -0,0 +1,10 @@
+export default {
+ basicUsage: 'Basic Usage',
+ title: 'Title',
+ showSubtitle: 'Show Subtitle',
+ subtitle: 'Subtitle',
+ description: 'Description',
+ showImage: 'Show Image',
+ useSlot: 'Use Slot',
+ button: 'Use Button',
+}
diff --git a/packages/varlet-ui/src/card/example/locale/index.ts b/packages/varlet-ui/src/card/example/locale/index.ts
new file mode 100644
index 00000000000..f8c3b2643a5
--- /dev/null
+++ b/packages/varlet-ui/src/card/example/locale/index.ts
@@ -0,0 +1,23 @@
+// lib
+import _zhCN from '../../../locale/zh-CN'
+import _enCN from '../../../locale/en-US'
+// mobile example doc
+import zhCN from './zh-CN'
+import enUS from './en-US'
+import { useLocale, add as _add, use as _use } from '../../../locale'
+
+const { add, use: exampleUse, pack, packs, merge } = useLocale()
+
+const use = (lang: string) => {
+ _use(lang)
+ exampleUse(lang)
+}
+
+export { add, pack, packs, merge, use }
+
+// lib
+_add('zh-CN', _zhCN)
+_add('en-US', _enCN)
+// mobile example doc
+add('zh-CN', zhCN)
+add('en-US', enUS)
diff --git a/packages/varlet-ui/src/card/example/locale/zh-CN.ts b/packages/varlet-ui/src/card/example/locale/zh-CN.ts
new file mode 100644
index 00000000000..0f81c402aca
--- /dev/null
+++ b/packages/varlet-ui/src/card/example/locale/zh-CN.ts
@@ -0,0 +1,10 @@
+export default {
+ basicUsage: '基本使用',
+ title: '这是标题',
+ showSubtitle: '显示副标题',
+ subtitle: '这是副标题',
+ description: '这是描述',
+ showImage: '显示图片',
+ useSlot: '使用插槽',
+ button: '添加按钮',
+}
diff --git a/packages/varlet-ui/src/card/index.ts b/packages/varlet-ui/src/card/index.ts
new file mode 100644
index 00000000000..fd96de54b83
--- /dev/null
+++ b/packages/varlet-ui/src/card/index.ts
@@ -0,0 +1,8 @@
+import { App } from 'vue'
+import Card from './Card.vue'
+
+Card.install = function (app: App) {
+ app.component(Card.name, Card)
+}
+
+export default Card
diff --git a/packages/varlet-ui/src/card/props.ts b/packages/varlet-ui/src/card/props.ts
new file mode 100644
index 00000000000..e58bc9dc53e
--- /dev/null
+++ b/packages/varlet-ui/src/card/props.ts
@@ -0,0 +1,34 @@
+import { PropType } from 'vue'
+
+function fitValidator(fit: string) {
+ return ['fill', 'contain', 'cover', 'none', 'scale-down'].includes(fit)
+}
+
+export const props = {
+ src: {
+ type: String,
+ },
+ fit: {
+ type: String as PropType<'fill' | 'contain' | 'cover' | 'none' | 'scale-down'>,
+ validator: fitValidator,
+ default: 'cover',
+ },
+ height: {
+ type: [String, Number],
+ },
+ alt: {
+ type: String,
+ },
+ title: {
+ type: [Number, String],
+ },
+ subtitle: {
+ type: String,
+ },
+ desc: {
+ type: String,
+ },
+ elevation: {
+ type: [Number, String],
+ },
+}