Repository: IduxFE/components (ts)

Issue:
[comp: Checkbox ] add Checkbox Component
- [x] I have searched the [issues](https://github.com/IduxFE/components/issues) of this repository and believe that this is not a duplicate.

### What problem does this feature solve?
<!-- 这个功能解决了什么问题？ -->
<!-- 请尽可能详尽地说明这个需求的用例和场景。最重要的是：解释清楚是怎样的用户体验需求催生了这个功能上的需求。 -->
多选框

### What does the proposed API look like?
<!-- 你期望的 API 是怎样的？-->
<!-- 描述一下你期望这个新功能的 API 是如何使用的，并提供一些代码示例。请用 Markdown 格式化你的代码片段。 -->

**Checkbox**

#### Props

支持原生input属性, 例如`value, name, id`等
| 参数 | 说明 |  类型  | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
|`checked` | 指定当前勾选框是否选中 |  `boolean`  | `false` | - | 支持双向绑定，使用时按照v-model:checked = 形式 |
|`disabled` | 禁用状态 |`boolean`| `false` | - | - |
| `indeterminate`| 是否处于不确定状态 | `boolean` | `false`| - | 当值为true时，按钮样式处于半选状态，且不受checked影响 |
| `trueValue`| 勾选框选中时返回的值 |  `string \| number`  | `true`| - | - |
| `falseValue`| 勾选框不选中时返回的值 | `string \| number` | `false`| - | - |

#### Slots

|名称 | 说明 | 参数类型 | 备注 |
| --- | --- | --- | --- |
|`defalut` | 文本区域 | - | - |

#### Emits

| 名称 | 说明 | 参数类型 | 备注 |
| --- | --- | --- | --- |
| `change` | 点击勾选框时触发 | `Function(checkValue:boolean \| string \| number)` | - |

**Checkbox Group**
#### Group Props
| 名称 | 说明 | 类型  | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |value
| `value` | 指定选中的选项 |  `string[]`  | `[]` | - | 支持双向绑定，使用时按照v-model:value = 形式 |
| `disabled` | 子`ix-checkbox`禁用状态 | `boolean` | `false` | - |- |
| `name` | 子`ix-checkbox` 的 name 属性 | `string` | - | - |- |

#### Group Slots
|名称 | 说明 | 参数类型 | 备注 |
| --- | --- | --- | --- |
|`defalut` | `ix-checkbox`区域 | - | - |

### Group Emits
| 名称 | 说明 | 参数类型 | 备注 |
| --- | --- | --- | --- |
| `change` | `value`变化时的回调函数 | `Function(checkValue:string[])` | - |



Pull request description:
feat(comp: checkbox): add checkbox component
fix #120

## PR Checklist

Please check if your PR fulfills the following requirements:

- [x] The commit message follows [our guidelines](https://github.com/IduxFE/components/blob/main/docs/contributing.zh.md#commit)
- [x] Tests for the changes have been added/updated or not needed
- [x] Docs and demo have been added/updated or not needed

## PR Type

What kind of change does this PR introduce?

- [ ] Bugfix
- [x] Feature
- [ ] Component style update
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Application (the showcase website) / infrastructure changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

## What is the new behavior?

## Other information


@danranVm @LaamGinghong 

Patch:
diff --git a/packages/cdk/utils/dom.ts b/packages/cdk/utils/dom.ts
new file mode 100644
index 00000000..bba0a9a7
--- /dev/null
+++ b/packages/cdk/utils/dom.ts
@@ -0,0 +1,87 @@
+/* istanbul ignore file */
+
+const trim = (s: string) => (s || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
+
+export function on(
+  el: HTMLElement | Document | Window,
+  type: string,
+  listener: EventListenerOrEventListenerObject,
+  options?: boolean | EventListenerOptions | undefined,
+): void {
+  if (el && type && listener) {
+    el.addEventListener(type, listener, options ?? true)
+  }
+}
+
+export function off(
+  el: HTMLElement | Document | Window,
+  type: string,
+  listener: EventListenerOrEventListenerObject,
+  options?: boolean | EventListenerOptions | undefined,
+): void {
+  if (el && type && listener) {
+    el.removeEventListener(type, listener, options ?? true)
+  }
+}
+
+export function hasClass(el: HTMLElement, cls: string): boolean {
+  if (!el || !cls) {
+    return false
+  }
+  if (cls.indexOf(' ') !== -1) {
+    throw new Error('className should not contain space.')
+  }
+  if (el.classList) {
+    return el.classList.contains(cls)
+  } else {
+    return ` ${el.className} `.indexOf(` ${cls} `) > -1
+  }
+}
+
+export function addClass(el: HTMLElement, cls: string): void {
+  if (!el) {
+    return
+  }
+  let curClass = el.className
+  const classes = (cls || '').split(' ')
+
+  for (let i = 0, j = classes.length; i < j; i++) {
+    const clsName = classes[i]
+    if (!clsName) {
+      continue
+    }
+
+    if (el.classList) {
+      el.classList.add(clsName)
+    } else if (!hasClass(el, clsName)) {
+      curClass += ' ' + clsName
+    }
+  }
+  if (!el.classList) {
+    el.className = curClass
+  }
+}
+
+export function removeClass(el: HTMLElement, cls: string): void {
+  if (!el || !cls) {
+    return
+  }
+  const classes = cls.split(' ')
+  let curClass = ` ${el.className} `
+
+  for (let i = 0, j = classes.length; i < j; i++) {
+    const clsName = classes[i]
+    if (!clsName) {
+      continue
+    }
+
+    if (el.classList) {
+      el.classList.remove(clsName)
+    } else if (hasClass(el, clsName)) {
+      curClass = curClass.replace(` ${clsName} `, ' ')
+    }
+  }
+  if (!el.classList) {
+    el.className = trim(curClass)
+  }
+}
diff --git a/packages/cdk/utils/index.ts b/packages/cdk/utils/index.ts
index c54a87d9..585328bb 100644
--- a/packages/cdk/utils/index.ts
+++ b/packages/cdk/utils/index.ts
@@ -2,3 +2,4 @@ export * from './convert'
 export * from './propTypes'
 export * from './typeof'
 export * from './vNode'
+export * from './dom'
diff --git a/packages/components/checkbox/demo/Basic.vue b/packages/components/checkbox/demo/Basic.vue
new file mode 100644
index 00000000..4ef8ab93
--- /dev/null
+++ b/packages/components/checkbox/demo/Basic.vue
@@ -0,0 +1,20 @@
+<template>
+  <div>
+    <ix-checkbox v-model:checked="checked"> option </ix-checkbox>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+
+export default defineComponent({
+  setup() {
+    const checked = ref(true)
+
+    return {
+      checked,
+    }
+  },
+})
+</script>
+<style lang="less" scoped></style>
diff --git a/packages/components/checkbox/demo/Disabled.vue b/packages/components/checkbox/demo/Disabled.vue
new file mode 100644
index 00000000..db513bb1
--- /dev/null
+++ b/packages/components/checkbox/demo/Disabled.vue
@@ -0,0 +1,48 @@
+<template>
+  <div>
+    <ix-checkbox v-model:checked="checked" :disabled="disabled" @change="onChange"> option </ix-checkbox>
+    <div class="operation-area">
+      <ix-button mode="primary" @click="changeChecked()">checked: {{ checked }}</ix-button>
+      <ix-button mode="primary" @click="changeDisabled()">disabled: {{ disabled }}</ix-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+
+export default defineComponent({
+  setup() {
+    const checked = ref(true)
+    const disabled = ref(true)
+
+    const changeChecked = () => {
+      checked.value = !checked.value
+    }
+
+    const changeDisabled = () => {
+      disabled.value = !disabled.value
+    }
+
+    const onChange = () => {
+      console.log('onChange')
+    }
+
+    return {
+      checked,
+      disabled,
+      changeChecked,
+      changeDisabled,
+      onChange,
+    }
+  },
+})
+</script>
+<style lang="less" scoped>
+.operation-area {
+  margin-top: 30px;
+  .ix-button {
+    margin-right: 10px;
+  }
+}
+</style>
diff --git a/packages/components/checkbox/demo/Group.vue b/packages/components/checkbox/demo/Group.vue
new file mode 100644
index 00000000..a59676dc
--- /dev/null
+++ b/packages/components/checkbox/demo/Group.vue
@@ -0,0 +1,24 @@
+<template>
+  <div>
+    <ix-checkbox-group v-model:value="value">
+      <ix-checkbox value="option1"> option1 </ix-checkbox>
+      <ix-checkbox value="option2"> option2 </ix-checkbox>
+      <ix-checkbox value="option3"> option3 </ix-checkbox>
+    </ix-checkbox-group>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+
+export default defineComponent({
+  setup() {
+    const value = ref(['option1', 'option2', 'option3'])
+
+    return {
+      value,
+    }
+  },
+})
+</script>
+<style lang="less" scoped></style>
diff --git a/packages/components/checkbox/demo/Indeterminate.vue b/packages/components/checkbox/demo/Indeterminate.vue
new file mode 100644
index 00000000..cac0b42d
--- /dev/null
+++ b/packages/components/checkbox/demo/Indeterminate.vue
@@ -0,0 +1,48 @@
+<template>
+  <div>
+    <ix-checkbox v-model:checked="checkAll" :indeterminate="indeterminate" @change="onCheckAllChange">
+      CheckAll
+    </ix-checkbox>
+    <ix-checkbox-group v-model:value="value">
+      <ix-checkbox value="option1"> option1 </ix-checkbox>
+      <ix-checkbox value="option2"> option2 </ix-checkbox>
+      <ix-checkbox value="option3"> option3 </ix-checkbox>
+    </ix-checkbox-group>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref, watch } from 'vue'
+
+const valueList = ['option1', 'option2', 'option3']
+export default defineComponent({
+  setup() {
+    const checkAll = ref(false)
+    const indeterminate = ref(true)
+
+    const value = ref(['option1', 'option2'])
+
+    watch(
+      () => value.value,
+      checkValue => {
+        indeterminate.value = !!checkValue.length && checkValue.length < valueList.length
+        checkAll.value = checkValue.length === valueList.length
+      },
+    )
+
+    const onCheckAllChange = (checkValue: boolean) => {
+      const isCheckAll = checkValue ? valueList : []
+      value.value = isCheckAll
+      indeterminate.value = false
+    }
+
+    return {
+      checkAll,
+      indeterminate,
+      onCheckAllChange,
+      value,
+    }
+  },
+})
+</script>
+<style lang="less" scoped></style>
diff --git a/packages/components/checkbox/demo/TrueFalse.vue b/packages/components/checkbox/demo/TrueFalse.vue
new file mode 100644
index 00000000..08f8f42f
--- /dev/null
+++ b/packages/components/checkbox/demo/TrueFalse.vue
@@ -0,0 +1,20 @@
+<template>
+  <div>
+    <ix-checkbox v-model:checked="checked" trueValue="yes" falseValue="no"> {{ checked }} </ix-checkbox>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+
+export default defineComponent({
+  setup() {
+    const checked = ref('yes')
+
+    return {
+      checked,
+    }
+  },
+})
+</script>
+<style lang="less" scoped></style>
diff --git a/packages/components/checkbox/demo/basic.md b/packages/components/checkbox/demo/basic.md
new file mode 100644
index 00000000..5063d6cd
--- /dev/null
+++ b/packages/components/checkbox/demo/basic.md
@@ -0,0 +1,14 @@
+---
+order: 0
+title:
+  zh: 基本使用
+  en: Basic usage
+---
+
+## zh
+
+`ix-checkbox` 的基础用法。
+
+## en
+
+The basic usage of `ix-checkbox`.
diff --git a/packages/components/checkbox/demo/disabled.md b/packages/components/checkbox/demo/disabled.md
new file mode 100644
index 00000000..8e86a17c
--- /dev/null
+++ b/packages/components/checkbox/demo/disabled.md
@@ -0,0 +1,14 @@
+---
+order: 2
+title:
+  zh: Disabled 基本使用
+  en: Disabled usage
+---
+
+## zh
+
+用来控制`ix-checkbox`的禁用状态，与`checked`独立。
+
+## en
+
+It is used to control the disabled state of the checkbox and is independent of checked.
diff --git a/packages/components/checkbox/demo/group.md b/packages/components/checkbox/demo/group.md
new file mode 100644
index 00000000..9bba4e99
--- /dev/null
+++ b/packages/components/checkbox/demo/group.md
@@ -0,0 +1,14 @@
+---
+order: 3
+title:
+  zh: CheckboxGroup 使用
+  en: CheckboxGroup usage
+---
+
+## zh
+
+`ix-checkboxGroup` 基本使用
+
+## en
+
+The basic usage of `ix-checkboxGroup`.
diff --git a/packages/components/checkbox/demo/indeterminate.md b/packages/components/checkbox/demo/indeterminate.md
new file mode 100644
index 00000000..dd733f64
--- /dev/null
+++ b/packages/components/checkbox/demo/indeterminate.md
@@ -0,0 +1,14 @@
+---
+order: 4
+title:
+  zh: indeterminate 使用
+  en: indeterminate usage
+---
+
+## zh
+
+`indeterminate` 属性用以表示 `ix-checkbox` 的不确定状态，用于实现全选的效果。
+
+## en
+
+The attribute `indeterminate` is used to represent the uncertain state of the `ix-checkbox` and to achieve the effect of selecting all.
diff --git a/packages/components/checkbox/demo/trueFalse.md b/packages/components/checkbox/demo/trueFalse.md
new file mode 100644
index 00000000..0885a642
--- /dev/null
+++ b/packages/components/checkbox/demo/trueFalse.md
@@ -0,0 +1,14 @@
+---
+order: 1
+title:
+  zh: trueValue 和 falseValue 使用
+  en: trueValue and falseValue usage
+---
+
+## zh
+
+控制`ix-checkbox`是否选中的返回值。
+
+## en
+
+The return value that controls whether the `ix-checkbox` is selected.
diff --git a/packages/components/checkbox/docs/index.zh.md b/packages/components/checkbox/docs/index.zh.md
new file mode 100644
index 00000000..bcc00744
--- /dev/null
+++ b/packages/components/checkbox/docs/index.zh.md
@@ -0,0 +1,88 @@
+---
+category: components
+type: 通用
+title: Checkbox
+subtitle: 多选框
+cover:
+---
+
+## 何时使用
+
+单独使用可以表示两种状态之间的切换
+在一组可选项中进行多项选择
+
+## API
+
+### `ix-checkbox`
+
+#### Props
+
+除以下表格之外还支持原生 <input type="checkbox" /> 元素的所有属性。
+| 参数 | 说明 |  类型  | 默认值 | 全局配置 | 备注 |
+| --- | --- | --- | --- | --- | --- |
+| `v-model:checked` | 指定当前勾选框是否选中 |  `boolean`  | `false` | - | - |
+| `disabled` | 禁用状态 |`boolean`| `false` | - | - |
+| `indeterminate` | 是否处于不确定状态 | `boolean` | `false`| - | 当值为true时，按钮样式处于半选状态，且不受checked影响 |
+| `trueValue` | 勾选框选中时返回的值 |  `string \| number`  | `true`| - | - |
+| `falseValue` | 勾选框不选中时返回的值 | `string \| number` | `false`| - | - |
+
+#### Slots
+
+|名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+|`defalut` | 文本区域 | - | - |
+
+#### Emits
+
+| 名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+| `change` | 点击勾选框时触发 | `Function(checkValue:boolean \| string \| number)` | - |
+
+### `ix-checkbox-group`
+
+#### Group Props
+
+| 名称 | 说明 | 类型  | 默认值 | 全局配置 | 备注 |
+| --- | --- | --- | --- | --- | --- |value
+| `v-model:value` | 指定当前勾选框是否选中 |  `boolean`  | `false` | - | - |
+| `disabled` | 子`ix-checkbox`禁用状态 | `boolean` | `false` | - |- |
+| `name` | 子`ix-checkbox` 的 name 属性 | `string` | - | - |- |
+
+#### Group Slots
+
+|名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+|`defalut` | `ix-checkbox`区域 | - | - |
+
+#### Group Emits
+
+| 名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+| `change` | `value`变化时的回调函数 | `Function(checkValue:string[])` | - |
+
+### 主题变量
+
+| 变量名 | default 主题| 说明 |
+| --- | --- | --- |
+|@checkbox-border-style| @border-size-sm solid @grey-d10| -|
+|@checkbox-border-radius| @border-radius-sm|-|
+|@checkbox-bg-color| @white|-|
+|@checkbox-tick-border-style| @border-size-md solid @white| -|
+|@checkbox-tick-width| 4px|-|
+|@checkbox-tick-height| 8px|-|
+|@checkbox-indeterminate-width| 8px|-|
+|@checkbox-indeterminate-height| 8px|-|
+|@checkbox-bg-indeterminate-color| @primary-color|-|
+|@checkbox-border-checked-color| @primary-color|-|
+|@checkbox-bg-checked-color| @primary-color|-|
+|@checkbox-border-disabled-color| @grey|-|
+|@checkbox-tick-disabled-color| @grey|-|
+|@checkbox-bg-disabled-color| @white|-|
+|@checkbox-bg-indeterminate-disabled-color| @grey|-|
+|@checkbox-label-disabled-color| @grey|-|
+|@checkbox-font-size| @font-size-md|-|
+|@checkbox-group-line-height| @line-height-base|-|
+|@checkbox-height| 16px|-|
+|@checkbox-width| 16px|-|
+|@checkbox-group-checkbox-margin-right| @margin-sm | -|
+|@checkbox-label-padding| 0 @spacing-gutter|-|
diff --git a/packages/components/checkbox/index.ts b/packages/components/checkbox/index.ts
new file mode 100644
index 00000000..fbd32e80
--- /dev/null
+++ b/packages/components/checkbox/index.ts
@@ -0,0 +1,9 @@
+import { installComponent } from '@idux/components/core/utils'
+import IxCheckbox from './src/Checkbox.vue'
+import IxCheckboxGroup from './src/CheckboxGroup.vue'
+
+IxCheckbox.install = installComponent(IxCheckbox)
+IxCheckboxGroup.install = installComponent(IxCheckboxGroup)
+
+export { IxCheckbox, IxCheckboxGroup }
+export * from './src/types'
diff --git a/packages/components/checkbox/src/Checkbox.vue b/packages/components/checkbox/src/Checkbox.vue
new file mode 100644
index 00000000..a9eb21d0
--- /dev/null
+++ b/packages/components/checkbox/src/Checkbox.vue
@@ -0,0 +1,46 @@
+<template>
+  <label
+    class="ix-checkbox"
+    role="checkbox"
+    :ariaChecked="isChecked"
+    :ariaDisabled="isDisabled"
+    :class="[classes, $attrs.class]"
+    :style="$attrs.style"
+  >
+    <span class="ix-checkbox-input-wrapper">
+      <span class="ix-checkbox-inner" :tabindex="$attrs.tabindex"></span>
+      <input
+        type="checkbox"
+        class="ix-checkbox-input"
+        :name="inputName"
+        :checked="isChecked"
+        :disabled="isDisabled"
+        :trueValue="trueValue"
+        :falseValue="falseValue"
+        :value="value"
+        v-bind="attrs"
+        @change="handleChange"
+      />
+    </span>
+    <span v-if="hasDefaultSlot" class="ix-checkbox-label"><slot></slot></span>
+  </label>
+</template>
+<script lang="ts">
+import { defineComponent } from 'vue'
+import { PropTypes, withUndefined } from '@idux/cdk/utils'
+import { setup } from './checkboxSetup'
+export default defineComponent({
+  name: 'IxCheckbox',
+  inheritAttrs: false,
+  props: {
+    checked: withUndefined(PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])),
+    disabled: PropTypes.bool.def(false),
+    indeterminate: PropTypes.bool.def(false),
+    trueValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]).def(true),
+    falseValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]).def(false),
+    value: PropTypes.string,
+  },
+  emits: ['update:checked', 'change'],
+  setup,
+})
+</script>
diff --git a/packages/components/checkbox/src/CheckboxGroup.vue b/packages/components/checkbox/src/CheckboxGroup.vue
new file mode 100644
index 00000000..bd652bc0
--- /dev/null
+++ b/packages/components/checkbox/src/CheckboxGroup.vue
@@ -0,0 +1,58 @@
+<template>
+  <div class="ix-checkbox-group" role="group" aria-label="checkbox-group">
+    <slot></slot>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, provide, watch } from 'vue'
+import { PropTypes } from '@idux/cdk/utils'
+import { checkboxGroupInjectionKey, subjectInjectKey, SubjectType } from './checkbox'
+import type { CheckboxGroupProps } from './types'
+import { Subject } from '@idux/cdk/subject'
+
+export default defineComponent({
+  name: 'IxCheckboxGroup',
+  props: {
+    value: PropTypes.arrayOf(PropTypes.string),
+    disabled: PropTypes.bool.def(false),
+    name: PropTypes.string,
+  },
+  emits: ['update:value', 'change'],
+  setup(props: CheckboxGroupProps, { emit }) {
+    provide(checkboxGroupInjectionKey, props)
+
+    const subject = new Subject<SubjectType>()
+    provide(subjectInjectKey, subject)
+
+    let groupCheckValue = props.value ?? []
+
+    watch(
+      () => props.value,
+      value => {
+        groupCheckValue = value as string[]
+      },
+    )
+
+    const handleChange = (checkValue: SubjectType) => {
+      const checkValueIndex = groupCheckValue.indexOf(checkValue)
+      const value = [...groupCheckValue]
+      if (checkValueIndex === -1) {
+        value.push(checkValue)
+      } else {
+        value.splice(checkValueIndex, 1)
+      }
+      // no value props passed
+      if (props.value === void 0) {
+        groupCheckValue = value
+      }
+      emit('update:value', value)
+      emit('change', value)
+    }
+
+    subject.subscribe(handleChange)
+
+    return {}
+  },
+})
+</script>
diff --git a/packages/components/checkbox/src/checkbox.ts b/packages/components/checkbox/src/checkbox.ts
new file mode 100644
index 00000000..e48d976c
--- /dev/null
+++ b/packages/components/checkbox/src/checkbox.ts
@@ -0,0 +1,11 @@
+import type { CheckboxGroupProps } from './types'
+
+import { InjectionKey } from 'vue'
+
+import { Subject } from '@idux/cdk/subject'
+
+export const checkboxGroupInjectionKey: InjectionKey<CheckboxGroupProps> = Symbol()
+
+export type SubjectType = string
+
+export const subjectInjectKey: InjectionKey<Subject<SubjectType>> = Symbol()
diff --git a/packages/components/checkbox/src/checkboxSetup.ts b/packages/components/checkbox/src/checkboxSetup.ts
new file mode 100644
index 00000000..fb1a79ef
--- /dev/null
+++ b/packages/components/checkbox/src/checkboxSetup.ts
@@ -0,0 +1,109 @@
+import { computed, ComputedRef, inject, Ref, ref, watch, getCurrentInstance, SetupContext } from 'vue'
+import { isArray, hasSlot } from '@idux/cdk/utils'
+import { useAttrs } from '@idux/components/core/utils/'
+import { checkboxGroupInjectionKey, subjectInjectKey } from './checkbox'
+import type { CheckboxProps, CheckboxBindings } from './types'
+
+type CheckValue = number | string | boolean
+
+export const setup = (props: CheckboxProps, { slots }: SetupContext): CheckboxBindings => {
+  const hasDefaultSlot = computed(() => hasSlot(slots))
+
+  const isDisabled = useDisabled()
+
+  const { checkeValue, handleChange } = useCheckValue()
+
+  const isChecked = useChecked(checkeValue as Ref<CheckValue>)
+
+  const classes = useClasses(isDisabled, isChecked)
+
+  const inputName = useName()
+
+  const attrs = useAttrs({ keys: ['type', 'tabindex'] })
+
+  return {
+    isChecked,
+    isDisabled,
+    hasDefaultSlot,
+    handleChange,
+    classes,
+    inputName,
+    attrs,
+  }
+}
+
+const useCheckValue = () => {
+  //eslint-disable-next-line  @typescript-eslint/no-non-null-assertion
+  const { props, emit } = getCurrentInstance()!
+  const groupSubject = inject(subjectInjectKey, null)
+  const checkeValue = ref(props.checked ?? props.falseValue)
+
+  watch(
+    () => props.checked,
+    v => {
+      checkeValue.value = v
+    },
+  )
+
+  const handleChange = (e: Event) => {
+    const targetChecked = (e.target as EventTarget).checked
+    const targetCheckValue = targetChecked ? props.trueValue : props.falseValue
+    // no value props passed
+    if (props.checked === void 0) {
+      checkeValue.value = targetCheckValue
+    }
+    emit('update:checked', targetCheckValue)
+    emit('change', targetCheckValue)
+    groupSubject?.dispatch((props.value ?? targetCheckValue) as string)
+  }
+
+  return {
+    checkeValue,
+    handleChange,
+  }
+}
+
+const useChecked = (checkValue: Ref<CheckValue>) => {
+  const groupProps = inject(checkboxGroupInjectionKey, {})
+  //eslint-disable-next-line  @typescript-eslint/no-non-null-assertion
+  const { props } = getCurrentInstance()!
+
+  return computed(() => {
+    const groupValue = groupProps.value
+    if (groupValue && isArray(groupValue)) {
+      return groupValue.includes((props.value ?? checkValue.value) as string)
+    }
+
+    return checkValue.value === props.trueValue
+  })
+}
+
+const useDisabled = () => {
+  const groupProps = inject(checkboxGroupInjectionKey, {})
+  //eslint-disable-next-line  @typescript-eslint/no-non-null-assertion
+  const { props } = getCurrentInstance()!
+  return computed(() => {
+    return !!(groupProps.disabled || props.disabled)
+  })
+}
+
+const useClasses = (isDisabled: ComputedRef<boolean>, isChecked: ComputedRef<boolean>) => {
+  //eslint-disable-next-line  @typescript-eslint/no-non-null-assertion
+  const { props } = getCurrentInstance()!
+
+  return computed(() => {
+    const disabled = isDisabled.value
+    const checked = isChecked.value
+    const indeterminate = props.indeterminate
+    return {
+      'ix-checkbox-disabled': !!disabled,
+      'ix-checkbox-indeterminate': !!indeterminate,
+      'ix-checkbox-checked': !indeterminate && checked,
+    }
+  })
+}
+
+const useName = () => {
+  const groupProps = inject(checkboxGroupInjectionKey, {})
+  return computed(() => groupProps.name as string)
+}
diff --git a/packages/components/checkbox/src/types.ts b/packages/components/checkbox/src/types.ts
new file mode 100644
index 00000000..319cb5e2
--- /dev/null
+++ b/packages/components/checkbox/src/types.ts
@@ -0,0 +1,38 @@
+import type { ComputedRef, DefineComponent, Ref } from 'vue'
+
+interface CheckboxOriginalProps {
+  checked?: string | number | boolean
+  disabled?: boolean
+  indeterminate?: boolean
+  trueValue?: string | number | boolean
+  falseValue?: string | number | boolean
+  value?: string
+}
+
+interface CheckboxGroupOriginalProps {
+  value?: string[]
+  disabled?: boolean
+  name?: string
+}
+
+export interface CheckboxBindings {
+  isChecked: ComputedRef<boolean>
+  isDisabled: ComputedRef<boolean>
+  hasDefaultSlot: ComputedRef<boolean>
+  handleChange: (e: Event) => void
+  classes: ComputedRef<{
+    'ix-checkbox-disabled': boolean
+    'ix-checkbox-indeterminate': boolean
+    'ix-checkbox-checked': boolean
+  }>
+  inputName: ComputedRef<string>
+  attrs: Ref<Record<string, unknown>>
+}
+
+export type CheckboxProps = Readonly<CheckboxOriginalProps>
+
+export type CheckboxGroupProps = Readonly<CheckboxGroupOriginalProps>
+
+export type CheckboxComponent = InstanceType<DefineComponent<CheckboxProps, CheckboxBindings>>
+
+export type CheckboxGroupComponent = InstanceType<DefineComponent<CheckboxGroupProps>>
diff --git a/packages/components/checkbox/style/index.less b/packages/components/checkbox/style/index.less
new file mode 100644
index 00000000..7c405f61
--- /dev/null
+++ b/packages/components/checkbox/style/index.less
@@ -0,0 +1,142 @@
+@import '../../style/default.less';
+
+@checkbox-prefix: ~'@{idux-prefix}-checkbox';
+@checkbox-group-prefix: ~'@{idux-prefix}-checkbox-group';
+
+.@{checkbox-prefix} {
+  box-sizing: border-box;
+  font-size: @checkbox-font-size;
+  line-height: 1;
+  position: relative;
+  display: inline-block;
+  white-space: nowrap;
+  vertical-align: middle;
+  outline: none;
+  cursor: pointer;
+
+  &-input-wrapper {
+    box-sizing: border-box;
+    list-style: none;
+    position: relative;
+    display: inline-block;
+    white-space: nowrap;
+    vertical-align: middle;
+    outline: none;
+    cursor: pointer;
+  }
+
+  &-inner {
+    display: inline-block;
+    position: relative;
+    border: @checkbox-border-style;
+    border-radius: @checkbox-border-radius;
+    box-sizing: border-box;
+    width: @checkbox-width;
+    height: @checkbox-height;
+    background-color: @checkbox-bg-color;
+    z-index: 1;
+    transition: background-color ease .25s;
+
+    &::after {
+      position: absolute;
+      top: 50%;
+      left: 22%;
+      display: table;
+      width: @checkbox-tick-width;
+      height: @checkbox-tick-height;
+      border: @checkbox-tick-border-style;
+      border-top: 0;
+      border-left: 0;
+      transform: rotate(45deg) scale(0) translate(-50%, -50%);
+      transition: transform .25s ease;
+      opacity: 0;
+      content: " ";
+    }
+  }
+
+  &-input {
+    opacity: 0;
+    outline: none;
+    position: absolute;
+    width: 0;
+    height: 0;
+    z-index: -1;
+  }
+
+  &-label {
+    padding: @checkbox-label-padding;
+  }
+
+  &-checked {
+    .@{checkbox-prefix} {
+      &-inner {
+        background-color: @checkbox-bg-checked-color;
+        border-color: @checkbox-border-checked-color;
+
+        &::after {
+          opacity: 1;
+          transform: rotate(45deg) scale(1) translate(-50%, -50%);
+        }
+      }
+    }
+  }
+
+  &-indeterminate {
+    .@{checkbox-prefix} {
+      &-inner {
+
+        &::after {
+          left: 50%;
+          width: @checkbox-indeterminate-width;
+          height: @checkbox-indeterminate-height;
+          background-color: @checkbox-bg-indeterminate-color;
+          border: 0;
+          transform: translate(-50%, -50%) scale(1);
+          opacity: 1;
+          content: " "
+        }
+      }
+    }
+  }
+
+  &-disabled {
+    &.@{checkbox-prefix}-checked {
+      .@{checkbox-prefix}-inner::after {
+        border-color: @checkbox-tick-disabled-color;
+      }
+    }
+
+    &.@{checkbox-prefix}-indeterminate {
+      .@{checkbox-prefix}-inner::after {
+        background-color: @checkbox-bg-indeterminate-disabled-color;
+      }
+    }
+
+    .@{checkbox-prefix} {
+      &-inner {
+        background-color: @checkbox-bg-disabled-color;
+        border-color: @checkbox-border-disabled-color !important;
+      }
+
+      &-input {
+        cursor: not-allowed;
+      }
+
+      &-label {
+        color: @checkbox-label-disabled-color;
+        cursor: not-allowed;
+      }
+    }
+  }
+}
+
+.@{checkbox-group-prefix} {
+  box-sizing: border-box;
+  line-height: @checkbox-group-line-height;
+  list-style: none;
+  display: inline-block;
+
+  .@{checkbox-prefix} {
+    margin-right: @checkbox-group-checkbox-margin-right;
+  }
+}
diff --git a/packages/components/components.less b/packages/components/components.less
index df3b059a..4167dc8e 100644
--- a/packages/components/components.less
+++ b/packages/components/components.less
@@ -9,3 +9,4 @@
 @import './result/style/index.less';
 @import "./typography/style/index.less";
 @import './rate/style/index.less';
+@import './checkbox/style/index.less';
diff --git a/packages/components/core/utils/index.ts b/packages/components/core/utils/index.ts
index ed661eff..b6a32d54 100644
--- a/packages/components/core/utils/index.ts
+++ b/packages/components/core/utils/index.ts
@@ -1,2 +1,3 @@
 export * from './installComponent'
 export * from './isDevMode'
+export * from './useAttrs'
diff --git a/packages/components/index.ts b/packages/components/index.ts
index 8352b13a..a2b96bab 100644
--- a/packages/components/index.ts
+++ b/packages/components/index.ts
@@ -10,6 +10,7 @@ import { IxEmpty } from './empty'
 import { IxResult } from './result'
 import { IxTypography } from './typography'
 import { IxRate } from './rate'
+import { IxCheckbox, IxCheckboxGroup } from './checkbox'
 
 const components = [
   IxButton,
@@ -23,6 +24,8 @@ const components = [
   IxEmpty,
   IxResult,
   IxRate,
+  IxCheckbox,
+  IxCheckboxGroup,
 ]
 
 const directives: Record<string, Directive> = {
@@ -59,3 +62,4 @@ export { IxEmpty }
 export { IxResult }
 export { IxTypography }
 export { IxRate }
+export { IxCheckbox, IxCheckboxGroup }
diff --git a/packages/components/style/default.less b/packages/components/style/default.less
index 159118d8..bf7ee429 100644
--- a/packages/components/style/default.less
+++ b/packages/components/style/default.less
@@ -165,3 +165,26 @@
 @rate-icon-margin: 8px;
 @rate-stroke-color: #f7ba2a;
 @rate-background-color: #c6d1de;
+/* checkbox -------------------------- */
+@checkbox-border-style: @border-size-sm solid @grey-d10;
+@checkbox-border-radius: @border-radius-sm;
+@checkbox-bg-color: @white;
+@checkbox-tick-border-style: @border-size-md solid @white;
+@checkbox-tick-width: 4px;
+@checkbox-tick-height: 8px;
+@checkbox-bg-indeterminate-color: @primary-color;
+@checkbox-indeterminate-width: 8px;
+@checkbox-indeterminate-height: 8px;
+@checkbox-border-checked-color: @primary-color;
+@checkbox-bg-checked-color: @primary-color;
+@checkbox-border-disabled-color: @grey;
+@checkbox-tick-disabled-color: @grey;
+@checkbox-bg-disabled-color: @white;
+@checkbox-bg-indeterminate-disabled-color: @grey;
+@checkbox-label-disabled-color: @grey;
+@checkbox-font-size: @font-size-md;
+@checkbox-height: 16px;
+@checkbox-width: 16px;
+@checkbox-group-line-height: @line-height-base;
+@checkbox-group-checkbox-margin-right: @margin-sm;
+@checkbox-label-padding: 0 @spacing-gutter;
\ No newline at end of file


Test patch:
diff --git a/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap b/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap
new file mode 100644
index 00000000..c047ff4a
--- /dev/null
+++ b/packages/components/checkbox/__tests__/__snapshots__/checkbox.spec.ts.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Checkbox.vue render work 1`] = `"<label class=\\"ix-checkbox\\" role=\\"checkbox\\" ariachecked=\\"false\\" ariadisabled=\\"false\\"><span class=\\"ix-checkbox-input-wrapper\\"><span class=\\"ix-checkbox-inner\\"></span><input type=\\"checkbox\\" class=\\"ix-checkbox-input\\" truevalue=\\"true\\" falsevalue=\\"false\\" value=\\"\\"></span><span class=\\"ix-checkbox-label\\">option</span></label>"`;
diff --git a/packages/components/checkbox/__tests__/__snapshots__/checkboxGroup.spec.ts.snap b/packages/components/checkbox/__tests__/__snapshots__/checkboxGroup.spec.ts.snap
new file mode 100644
index 00000000..39e0d9c1
--- /dev/null
+++ b/packages/components/checkbox/__tests__/__snapshots__/checkboxGroup.spec.ts.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckboxGroup.vue and Checkbox.vue render work 1`] = `"<div class=\\"ix-checkbox-group\\" role=\\"group\\" aria-label=\\"checkbox-group\\"><label class=\\"ix-checkbox\\" role=\\"checkbox\\" ariachecked=\\"false\\" ariadisabled=\\"false\\"><span class=\\"ix-checkbox-input-wrapper\\"><span class=\\"ix-checkbox-inner\\"></span><input type=\\"checkbox\\" class=\\"ix-checkbox-input\\" truevalue=\\"true\\" falsevalue=\\"false\\" value=\\"option1\\"></span><span class=\\"ix-checkbox-label\\"> option1 </span></label><label class=\\"ix-checkbox\\" role=\\"checkbox\\" ariachecked=\\"false\\" ariadisabled=\\"false\\"><span class=\\"ix-checkbox-input-wrapper\\"><span class=\\"ix-checkbox-inner\\"></span><input type=\\"checkbox\\" class=\\"ix-checkbox-input\\" truevalue=\\"true\\" falsevalue=\\"false\\" value=\\"option2\\"></span><span class=\\"ix-checkbox-label\\"> option2 </span></label><label class=\\"ix-checkbox\\" role=\\"checkbox\\" ariachecked=\\"false\\" ariadisabled=\\"false\\"><span class=\\"ix-checkbox-input-wrapper\\"><span class=\\"ix-checkbox-inner\\"></span><input type=\\"checkbox\\" class=\\"ix-checkbox-input\\" truevalue=\\"true\\" falsevalue=\\"false\\" value=\\"option3\\"></span><span class=\\"ix-checkbox-label\\"> option3 </span></label></div>"`;
diff --git a/packages/components/checkbox/__tests__/checkbox.spec.ts b/packages/components/checkbox/__tests__/checkbox.spec.ts
new file mode 100644
index 00000000..d9d1cbe5
--- /dev/null
+++ b/packages/components/checkbox/__tests__/checkbox.spec.ts
@@ -0,0 +1,164 @@
+import { mount } from '@vue/test-utils'
+import { ref, nextTick } from 'vue'
+import IxCheckbox from '../src/Checkbox.vue'
+
+describe('Checkbox.vue', () => {
+  test('render work', () => {
+    const checked = ref(false)
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox v-model:checked="checked">option</ix-checkbox>`,
+      setup() {
+        return { checked }
+      },
+    })
+    expect(wrapper.html()).toMatchSnapshot()
+  })
+
+  test('checked(v-model) work', async () => {
+    const checked = ref(false)
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox v-model:checked="checked">option</ix-checkbox>`,
+      setup() {
+        return { checked }
+      },
+    })
+
+    expect(wrapper.find('.ix-checkbox-checked').exists()).toBe(false)
+
+    checked.value = true
+
+    await nextTick()
+    expect(wrapper.find('.ix-checkbox-checked').exists()).toBe(true)
+
+    await wrapper.trigger('click')
+
+    expect(checked.value).toBe(false)
+    expect(wrapper.find('.ix-checkbox-checked').exists()).toBe(false)
+  })
+
+  test('no checked(v-model) work', async () => {
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox>option</ix-checkbox>`,
+    })
+
+    expect(wrapper.find('.ix-checkbox-checked').exists()).toBe(false)
+
+    await wrapper.trigger('click')
+
+    expect(wrapper.find('.ix-checkbox-checked').exists()).toBe(true)
+  })
+
+  test('trueValue and falseValue work', async () => {
+    const checked = ref('yes')
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox v-model:checked="checked" trueValue="yes" falseValue="no">option</ix-checkbox>`,
+      setup() {
+        return { checked }
+      },
+    })
+
+    await wrapper.trigger('click')
+
+    expect(checked.value).toBe('no')
+  })
+
+  test('disabled work', async () => {
+    const checked = ref(true)
+    const disabled = ref(true)
+    const mockFn = jest.fn()
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox v-model:checked="checked" :disabled="disabled" @change="mockFn">option</ix-checkbox>`,
+      setup() {
+        return { checked, disabled, mockFn }
+      },
+    })
+
+    expect(wrapper.classes()).toContain('ix-checkbox-disabled')
+
+    await wrapper.trigger('click')
+
+    expect(wrapper.classes()).toContain('ix-checkbox-disabled')
+    expect(checked.value).toBe(true)
+    expect(mockFn).toBeCalledTimes(0)
+
+    disabled.value = false
+
+    await nextTick()
+
+    expect(wrapper.classes()).not.toContain('ix-checkbox-disabled')
+
+    await wrapper.trigger('click')
+
+    expect(checked.value).toBe(false)
+    expect(mockFn).toBeCalledTimes(1)
+  })
+
+  test('indeterminate work', async () => {
+    const checked = ref(true)
+    const indeterminate = ref(true)
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox v-model:checked="checked" :indeterminate="indeterminate">option</ix-checkbox>`,
+      setup() {
+        return { checked, indeterminate }
+      },
+    })
+
+    expect(wrapper.classes()).toContain('ix-checkbox-indeterminate')
+    expect(wrapper.classes()).not.toContain('ix-checkbox-checked')
+
+    indeterminate.value = false
+
+    await nextTick()
+
+    expect(wrapper.classes()).not.toContain('ix-checkbox-indeterminate')
+    expect(wrapper.classes()).toContain('ix-checkbox-checked')
+  })
+
+  test('change event work', async () => {
+    const checked = ref(true)
+    const mockFn = jest.fn()
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox v-model:checked="checked" @change="mockFn">option</ix-checkbox>`,
+      setup() {
+        return { checked, mockFn }
+      },
+    })
+
+    expect(mockFn).toBeCalledTimes(0)
+
+    await wrapper.trigger('click')
+
+    expect(mockFn).toBeCalledTimes(1)
+  })
+
+  test('original checkbox attributes work', async () => {
+    const wrapper = mount({
+      components: { IxCheckbox },
+      template: `<ix-checkbox name="checkboxName" value="checkboxValue" tabindex="1" class="checkox" style="color: red;">option</ix-checkbox>`,
+    })
+
+    const checkboxWrapper = wrapper.find('.ix-checkbox')
+    const originalInput = wrapper.find("input[type='checkbox']")
+    const input = wrapper.find('.ix-checkbox-inner')
+
+    expect(checkboxWrapper.classes()).toContain('checkox')
+    expect(checkboxWrapper.attributes()['style']).toEqual('color: red;')
+    expect(checkboxWrapper.attributes()['name']).not.toBeDefined()
+    expect(checkboxWrapper.attributes()['value']).not.toBeDefined()
+    expect(checkboxWrapper.attributes()['tabindex']).not.toBeDefined()
+
+    expect(originalInput.attributes()['name']).toEqual('checkboxName')
+    expect(originalInput.attributes()['value']).toEqual('checkboxValue')
+    expect(originalInput.classes()).not.toContain('checkox')
+    expect(originalInput.attributes()['style']).not.toEqual('color: red;')
+
+    expect(input.attributes()['tabindex']).toEqual('1')
+  })
+})
diff --git a/packages/components/checkbox/__tests__/checkboxGroup.spec.ts b/packages/components/checkbox/__tests__/checkboxGroup.spec.ts
new file mode 100644
index 00000000..1334c28d
--- /dev/null
+++ b/packages/components/checkbox/__tests__/checkboxGroup.spec.ts
@@ -0,0 +1,148 @@
+import { mount } from '@vue/test-utils'
+import CheckboxGroup from '../src/CheckboxGroup.vue'
+import Checkbox from '../src/Checkbox.vue'
+import { nextTick, Ref, ref } from 'vue'
+
+describe('CheckboxGroup.vue and Checkbox.vue', () => {
+  test('render work', () => {
+    const wrapper = mount({
+      components: { CheckboxGroup, Checkbox },
+      template: `
+      <checkbox-group>
+        <checkbox value="option1"> option1 </checkbox>
+        <checkbox value="option2"> option2 </checkbox>
+        <checkbox value="option3"> option3 </checkbox>
+      </checkbox-group>
+      `,
+    })
+    expect(wrapper.findAll('.ix-checkbox').length).toBe(3)
+    expect(wrapper.html()).toMatchSnapshot()
+  })
+
+  test('value(v-model) work', async () => {
+    const value = ref([]) as Ref<string[]>
+    const wrapper = mount({
+      components: { CheckboxGroup, Checkbox },
+      template: `
+      <checkbox-group v-model:value="value">
+        <checkbox value="option1"> option1 </checkbox>
+        <checkbox value="option2"> option2 </checkbox>
+        <checkbox value="option3"> option3 </checkbox>
+      </checkbox-group>
+      `,
+      setup() {
+        return { value }
+      },
+    })
+
+    expect(wrapper.findAll('.ix-checkbox-checked').length).toBe(0)
+
+    value.value = ['option1']
+
+    await nextTick()
+
+    expect(wrapper.findAllComponents({ name: 'IxCheckbox' })[0].classes()).toContain('ix-checkbox-checked')
+
+    await wrapper.findAllComponents({ name: 'IxCheckbox' })[0].trigger('click')
+
+    expect(value.value).toEqual([])
+  })
+
+  test('no value(v-model) work', async () => {
+    const wrapper = mount({
+      components: { CheckboxGroup, Checkbox },
+      template: `
+      <checkbox-group>
+        <checkbox value="option1"> option1 </checkbox>
+        <checkbox value="option2"> option2 </checkbox>
+        <checkbox value="option3"> option3 </checkbox>
+      </checkbox-group>
+      `,
+      setup() {
+        return {}
+      },
+    })
+
+    expect(wrapper.findAll('.ix-checkbox-checked').length).toBe(0)
+
+    await wrapper.findAllComponents({ name: 'IxCheckbox' })[0].trigger('click')
+
+    expect(wrapper.findAllComponents({ name: 'IxCheckbox' })[0].classes()).toContain('ix-checkbox-checked')
+  })
+
+  test('change event work', async () => {
+    const mockFn = jest.fn()
+    const wrapper = mount({
+      components: { CheckboxGroup, Checkbox },
+      template: `
+      <checkbox-group @change="mockFn">
+        <checkbox value="option1"> option1 </checkbox>
+        <checkbox value="option2"> option2 </checkbox>
+        <checkbox value="option3"> option3 </checkbox>
+      </checkbox-group>
+      `,
+      setup() {
+        return { mockFn }
+      },
+    })
+
+    expect(mockFn).toBeCalledTimes(0)
+
+    await wrapper.findAllComponents({ name: 'IxCheckbox' })[0].trigger('click')
+
+    expect(mockFn).toBeCalledTimes(1)
+  })
+
+  test('disabled work', async () => {
+    const value = ref([]) as Ref<string[]>
+    const disabled = ref(true)
+    const mockFn = jest.fn()
+    const wrapper = mount({
+      components: { CheckboxGroup, Checkbox },
+      template: `
+      <checkbox-group v-model:value="value" :disabled="disabled" @change="mockFn">
+        <checkbox value="option1"> option1 </checkbox>
+        <checkbox value="option2"> option2 </checkbox>
+        <checkbox value="option3"> option3 </checkbox>
+      </checkbox-group>
+      `,
+      setup() {
+        return { value, disabled, mockFn }
+      },
+    })
+
+    expect(wrapper.findAll('.ix-checkbox-disabled').length).toBe(3)
+    await wrapper.findAllComponents({ name: 'IxCheckbox' })[0].trigger('click')
+    expect(value.value).toEqual([])
+    expect(mockFn).toBeCalledTimes(0)
+
+    disabled.value = false
+
+    await nextTick()
+
+    expect(wrapper.findAll('.ix-checkbox-disabled').length).toBe(0)
+
+    await wrapper.findAllComponents({ name: 'IxCheckbox' })[0].trigger('click')
+    expect(value.value).toEqual(['option1'])
+    expect(mockFn).toBeCalledTimes(1)
+  })
+
+  test('name work', async () => {
+    const wrapper = mount({
+      components: { CheckboxGroup, Checkbox },
+      template: `
+      <checkbox-group name="group">
+        <checkbox value="option1" name="child"> option1 </checkbox>
+        <checkbox value="option2"> option2 </checkbox>
+        <checkbox value="option3"> option3 </checkbox>
+      </checkbox-group>
+      `,
+      setup() {
+        return {}
+      },
+    })
+
+    expect(wrapper.findAll('input[name=group]').length).toBe(2)
+    expect(wrapper.findAll('input[name=child]').length).toBe(1)
+  })
+})