Skip to content

CheckboxGroup

CheckboxGroup groups related checkboxes and manages their selected values.

Import

vue
<script setup lang="ts">
import { Checkbox, CheckboxGroup } from '@heroui-vue/vue'
</script>

Usage

Basic

Choose all that apply

<template>
  <CheckboxGroup v-model="selectedInterests" name="interests">
    <Label>Select your interests</Label>
    <Description>Choose all that apply</Description>

    <Checkbox value="coding">
      <Label>Coding</Label>
      <Description>Love building software</Description>
    </Checkbox>

    <Checkbox value="design">
      <Label>Design</Label>
      <Description>Enjoy creating beautiful interfaces</Description>
    </Checkbox>

    <Checkbox value="writing">
      <Label>Writing</Label>
      <Description>Passionate about content creation</Description>
    </Checkbox>
  </CheckboxGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox, CheckboxGroup, Description, Label } from '@heroui-vue/vue'

const selectedInterests = ref<string[]>(['coding'])
</script>

In Surface

When used inside a Surface component, use variant="secondary" on the checkbox group to apply the lower emphasis checkbox controls.

Choose all that apply

<template>
  <Surface class="demo-checkbox-group-surface">
    <CheckboxGroup v-model="selectedInterests" name="interests" variant="secondary">
      <Label>Select your interests</Label>
      <Description>Choose all that apply</Description>

      <Checkbox value="coding">
        <Label>Coding</Label>
        <Description>Love building software</Description>
      </Checkbox>

      <Checkbox value="design">
        <Label>Design</Label>
        <Description>Enjoy creating beautiful interfaces</Description>
      </Checkbox>

      <Checkbox value="writing">
        <Label>Writing</Label>
        <Description>Passionate about content creation</Description>
      </Checkbox>
    </CheckboxGroup>
  </Surface>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox, CheckboxGroup, Description, Label, Surface } from '@heroui-vue/vue'

const selectedInterests = ref<string[]>(['coding'])
</script>

<style lang="less">
.demo-checkbox-group-surface {
  width: min(100%, 28rem);
  padding: 1.5rem;
  border-radius: 1.5rem;
  text-align: left;
}
</style>

With Custom Indicator

Select the features you want

<template>
  <CheckboxGroup v-model="selectedFeatures" name="features">
    <Label>Features</Label>
    <Description>Select the features you want</Description>

    <Checkbox value="notifications">
      <template #indicator="{ isSelected }">
        <svg
          v-if="isSelected"
          aria-hidden="true"
          fill="none"
          stroke="currentColor"
          stroke-linecap="round"
          stroke-width="2"
          viewBox="0 0 24 24"
        >
          <path d="M6 18L18 6M6 6l12 12" />
        </svg>
      </template>
      <Label>Email notifications</Label>
      <Description>Receive updates via email</Description>
    </Checkbox>

    <Checkbox value="newsletter">
      <template #indicator="{ isSelected }">
        <svg
          v-if="isSelected"
          aria-hidden="true"
          fill="none"
          stroke="currentColor"
          stroke-linecap="round"
          stroke-width="2"
          viewBox="0 0 24 24"
        >
          <path d="M6 18L18 6M6 6l12 12" />
        </svg>
      </template>
      <Label>Newsletter</Label>
      <Description>Get weekly newsletters</Description>
    </Checkbox>
  </CheckboxGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox, CheckboxGroup, Description, Label } from '@heroui-vue/vue'

const selectedFeatures = ref<string[]>(['newsletter'])
</script>

Indeterminate

<template>
  <div class="demo-checkbox-group-indeterminate">
    <Checkbox
      value="all"
      :is-indeterminate="selectedValues.length > 0 && selectedValues.length < allOptions.length"
      :is-selected="selectedValues.length === allOptions.length"
      @change="handleSelectAll"
    >
      <Label>Select all</Label>
    </Checkbox>

    <CheckboxGroup v-model="selectedValues" class="demo-checkbox-group-nested">
      <Checkbox value="coding">
        <Label>Coding</Label>
      </Checkbox>
      <Checkbox value="design">
        <Label>Design</Label>
      </Checkbox>
      <Checkbox value="writing">
        <Label>Writing</Label>
      </Checkbox>
    </CheckboxGroup>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox, CheckboxGroup, Label } from '@heroui-vue/vue'

const allOptions = ['coding', 'design', 'writing']
const selectedValues = ref<string[]>(['coding'])

const handleSelectAll = (isSelected: boolean) => {
  selectedValues.value = isSelected ? [...allOptions] : []
}
</script>

<style lang="less">
.demo-checkbox-group-indeterminate {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.demo-checkbox-group-nested {
  margin-left: 1.5rem;
}
</style>

Controlled

<template>
  <CheckboxGroup v-model="selectedSkills" class="demo-checkbox-group-controlled" name="skills">
    <Label>Your skills</Label>

    <Checkbox value="coding">
      <Label>Coding</Label>
    </Checkbox>

    <Checkbox value="design">
      <Label>Design</Label>
    </Checkbox>

    <Checkbox value="writing">
      <Label>Writing</Label>
    </Checkbox>

    <Label class="demo-checkbox-group-selected">
      Selected: {{ selectedSkills.join(', ') || 'None' }}
    </Label>
  </CheckboxGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox, CheckboxGroup, Label } from '@heroui-vue/vue'

const selectedSkills = ref<string[]>(['coding', 'design'])
</script>

<style lang="less">
.demo-checkbox-group-controlled {
  min-width: 20rem;
}

.demo-checkbox-group-selected {
  margin-block: 1rem;
  color: var(--color-muted);
  font-size: 0.875rem;
}
</style>

Validation

<template>
  <form class="demo-checkbox-group-form" @submit.prevent="handleSubmit">
    <CheckboxGroup
      v-model="selectedPreferences"
      name="preferences"
      is-required
      @invalid="showError = true"
    >
      <Label>Preferences</Label>

      <Checkbox value="email">
        <Label>Email notifications</Label>
      </Checkbox>
      <Checkbox value="sms">
        <Label>SMS notifications</Label>
      </Checkbox>
      <Checkbox value="push">
        <Label>Push notifications</Label>
      </Checkbox>

      <FieldError v-if="showError">
        Please select at least one notification method.
      </FieldError>
    </CheckboxGroup>

    <Button type="submit">Submit</Button>
  </form>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { Button, Checkbox, CheckboxGroup, FieldError, Label } from '@heroui-vue/vue'

const selectedPreferences = ref<string[]>([])
const showError = ref(false)

const handleSubmit = () => {
  showError.value = selectedPreferences.value.length === 0
  if (!showError.value) {
    window.alert(`Selected preferences: ${selectedPreferences.value.join(', ')}`)
  }
}

watch(selectedPreferences, (value) => {
  if (value.length > 0) {
    showError.value = false
  }
})
</script>

<style lang="less">
.demo-checkbox-group-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  padding: 1rem;
}
</style>

Disabled

Feature selection is temporarily disabled

<template>
  <CheckboxGroup is-disabled name="disabled-features">
    <Label>Features</Label>
    <Description>Feature selection is temporarily disabled</Description>

    <Checkbox value="feature1">
      <Label>Feature 1</Label>
      <Description>This feature is coming soon</Description>
    </Checkbox>

    <Checkbox value="feature2">
      <Label>Feature 2</Label>
      <Description>This feature is coming soon</Description>
    </Checkbox>
  </CheckboxGroup>
</template>

<script setup lang="ts">
import { Checkbox, CheckboxGroup, Description, Label } from '@heroui-vue/vue'
</script>

Features and Add-ons Example

Choose how you want to receive updates

<template>
  <div class="demo-checkbox-addons">
    <CheckboxGroup name="notification-preferences">
      <Label>Notification preferences</Label>
      <Description>Choose how you want to receive updates</Description>

      <div class="demo-checkbox-addons__list">
        <Checkbox
          v-for="addon in addOns"
          :key="addon.value"
          :value="addon.value"
          class="demo-checkbox-addons__item"
          variant="secondary"
        >
          <span class="demo-checkbox-addons__icon" aria-hidden="true">
            <component :is="addon.icon" />
          </span>
          <span class="demo-checkbox-addons__content">
            <Label>{{ addon.title }}</Label>
            <Description>{{ addon.description }}</Description>
          </span>
        </Checkbox>
      </div>
    </CheckboxGroup>
  </div>
</template>

<script setup lang="ts">
import { h } from 'vue'
import { Checkbox, CheckboxGroup, Description, Label } from '@heroui-vue/vue'

const iconAttrs = {
  fill: 'none',
  viewBox: '0 0 24 24',
  stroke: 'currentColor',
  'stroke-width': 2.2,
}

const MailIcon = () =>
  h('svg', iconAttrs, [
    h('path', {
      d: 'M4 7.5h16v9H4z',
      'stroke-linejoin': 'round',
    }),
    h('path', {
      d: 'm5 8 7 5 7-5',
      'stroke-linecap': 'round',
      'stroke-linejoin': 'round',
    }),
  ])

const ChatIcon = () =>
  h('svg', iconAttrs, [
    h('path', {
      d: 'M5 6.5h14v8H9l-4 3v-11Z',
      'stroke-linecap': 'round',
      'stroke-linejoin': 'round',
    }),
  ])

const BellIcon = () =>
  h('svg', iconAttrs, [
    h('path', {
      d: 'M8 17h8M10 20h4M6.5 15.5h11l-1.2-2V10a4.3 4.3 0 0 0-8.6 0v3.5l-1.2 2Z',
      'stroke-linecap': 'round',
      'stroke-linejoin': 'round',
    }),
  ])

const addOns = [
  {
    description: 'Receive updates via email',
    icon: MailIcon,
    title: 'Email Notifications',
    value: 'email',
  },
  {
    description: 'Get instant SMS notifications',
    icon: ChatIcon,
    title: 'SMS Alerts',
    value: 'sms',
  },
  {
    description: 'Browser and mobile push alerts',
    icon: BellIcon,
    title: 'Push Notifications',
    value: 'push',
  },
]
</script>

<style lang="less">
.demo-checkbox-addons {
  display: flex;
  width: 100%;
  min-width: 20rem;
  flex-direction: column;
  align-items: center;
  gap: 2.5rem;
  padding: 2rem 1rem;
}

.demo-checkbox-addons__list {
  display: flex;
  width: 100%;
  min-width: 20rem;
  flex-direction: column;
  gap: 0.5rem;
}

.demo-checkbox-addons__item {
  position: relative;
  flex-direction: column;
  gap: 1rem;
  border-radius: 1.5rem;
  background: var(--color-surface);
  padding: 1rem 1.25rem;
  transition: background-color 150ms var(--ease-out), transform 150ms var(--ease-out);
}

.demo-checkbox-addons__item[data-selected='true'] {
  background: color-mix(in oklab, var(--color-accent) 10%, transparent);
}

.demo-checkbox-addons__item [data-slot='checkbox-control'] {
  position: absolute;
  top: 0.75rem;
  right: 1rem;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 999px;
}

.demo-checkbox-addons__item [data-slot='checkbox-control']::before {
  border-radius: 999px;
}

.demo-checkbox-addons__item [data-slot='checkbox-content'] {
  flex-direction: row;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 1rem;
  padding-right: 1.75rem;
}

.demo-checkbox-addons__icon {
  display: inline-flex;
  width: 1.35rem;
  height: 1.35rem;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  color: var(--color-accent);
}

.demo-checkbox-addons__icon svg {
  width: 1.35rem;
  height: 1.35rem;
}

.demo-checkbox-addons__content {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 0.25rem;
}
</style>

Custom Render Function

Choose all that apply
Rendered with scoped slot value: coding

<template>
  <CheckboxGroup
    v-slot="{ value }"
    v-model="selectedInterests"
    class="demo-checkbox-group-render"
    name="interests"
  >
    <Label>Select your interests</Label>
    <Description>Choose all that apply</Description>

    <Checkbox value="coding">
      <Label>Coding</Label>
      <Description>Love building software</Description>
    </Checkbox>

    <Checkbox value="design">
      <Label>Design</Label>
      <Description>Enjoy creating beautiful interfaces</Description>
    </Checkbox>

    <Checkbox value="writing">
      <Label>Writing</Label>
      <Description>Passionate about content creation</Description>
    </Checkbox>

    <Description>Rendered with scoped slot value: {{ value.join(', ') || 'None' }}</Description>
  </CheckboxGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox, CheckboxGroup, Description, Label } from '@heroui-vue/vue'

const selectedInterests = ref<string[]>(['coding'])
</script>

<style lang="less">
.demo-checkbox-group-render {
  min-width: 20rem;
}
</style>

Anatomy

vue
<template>
  <CheckboxGroup v-model="values" name="interests">
    <Label>Select your interests</Label>
    <Description>Choose all that apply</Description>
    <Checkbox value="coding">
      <Label>Coding</Label>
    </Checkbox>
  </CheckboxGroup>
</template>

API

CheckboxGroup Props

PropTypeDefaultDescription
modelValuestring[]undefinedControlled selected values.
valuestring[]undefinedAlternative controlled selected values.
defaultValuestring[][]Initial uncontrolled selected values.
namestringundefinedName passed to child checkboxes.
variant'primary' | 'secondary''primary'Variant inherited by child checkboxes.
isDisabledbooleanfalseDisable all child checkboxes.
isInvalidbooleanfalseMark the group and child checkboxes invalid.
isRequiredbooleanfalseMark the group and child checkboxes required.

Events

EventPayloadDescription
update:modelValuestring[]Emitted when selected values change.
update:valuestring[]Emitted for value-style controlled usage.
changestring[]Emitted after any child checkbox toggles.

Released under the Apache-2.0 License.