Checkbox Group
One value for a set of checkboxes, with a parent checkbox that ticks or clears them all.
Checkbox Group collects a set of checkboxes into a single value, an array of the
ones that are ticked, and takes part in forms as one control. Useful when the
answer is “any of these” rather than “one of these”, which is where
Radio belongs instead.
<template>
<Checkbox.Group
v-model="selected"
:all-values="allValues"
aria-label="Notifications"
class="flex flex-col gap-3"
:options="{ name: 'notifications' }"
>
<div class="flex items-start gap-3">
<Checkbox.Root
id="notifications-all"
:class="box"
:options="{ parent: true }"
>
<Checkbox.Indicator
v-slot="{ indeterminate: mixed }"
class="block size-[0.9375rem]"
>
<svg viewBox="0 0 18 18" fill="currentColor" aria-hidden="true">
<path v-if="mixed" :d="indeterminate" />
<path v-else :d="check" />
</svg>
</Checkbox.Indicator>
</Checkbox.Root>
<Label :class="label" for="notifications-all">
All notifications
</Label>
</div>
<div
v-for="channel in channels"
:key="channel.value"
class="flex items-start gap-3 pl-8"
>
<Checkbox.Root
:id="`notifications-${channel.value}`"
:class="box"
:options="{ value: channel.value }"
>
<Checkbox.Indicator class="block size-[0.9375rem]">
<svg viewBox="0 0 18 18" fill="currentColor" aria-hidden="true">
<path :d="check" />
</svg>
</Checkbox.Indicator>
</Checkbox.Root>
<Label :class="label" :for="`notifications-${channel.value}`">
{{ channel.label }}
</Label>
</div>
</Checkbox.Group>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox } from '@maas/mirror/vue/Checkbox'
import { Label } from '@maas/mirror/vue/Label'
const box = [
'flex size-5 items-center justify-center border-2 border-surface',
'rounded-component-compact-md text-accent-on-solid',
'transition-all duration-100 ease-linear',
'outline-4 outline-transparent focus-visible:focus-ring',
'active:border-primary-subtle',
'data-[state=checked]:bg-accent-solid data-[state=indeterminate]:bg-accent-solid',
'data-[state=checked]:border-[transparent] data-[state=indeterminate]:border-[transparent]',
'data-[state=checked]:active:bg-accent-solid-active',
'data-[state=indeterminate]:active:bg-accent-solid-active',
'data-[disabled=true]:border-disabled-subtle',
'data-[disabled=true]:data-[state=checked]:bg-disabled-solid',
'data-[disabled=true]:text-disabled-on-solid',
].join(' ')
const label = [
'type-component-xs text-surface translate-y-[0.1rem] [--mirror-label-cursor:pointer]',
'data-[disabled=true]:text-disabled-solid',
].join(' ')
const check =
'M13.6875 5.1875C13.875 5 14.1042 4.90625 14.375 4.90625C14.6458 4.90625 14.875 5 15.0625 5.1875C15.25 5.375 15.3438 5.60417 15.3438 5.875C15.3438 6.14583 15.25 6.375 15.0625 6.5625L7.96875 13.6562C7.63542 13.9896 7.22917 14.1562 6.75 14.1562C6.27083 14.1562 5.86458 13.9896 5.53125 13.6562L2.9375 11.0625C2.75 10.875 2.65625 10.6458 2.65625 10.375C2.65625 10.1042 2.75 9.875 2.9375 9.6875C3.125 9.5 3.35417 9.40625 3.625 9.40625C3.89583 9.40625 4.125 9.5 4.3125 9.6875L6.90625 12.2812C6.86458 12.2396 6.8125 12.2188 6.75 12.2188C6.6875 12.2188 6.63542 12.2396 6.59375 12.2812L13.6875 5.1875Z'
const indeterminate =
'M14.0313 8.03125C14.3021 8.03125 14.5312 8.125 14.7187 8.3125C14.9062 8.5 15 8.72917 15 9C15 9.27083 14.9062 9.5 14.7187 9.6875C14.5312 9.875 14.3021 9.96875 14.0313 9.96875H3.96875C3.69792 9.96875 3.46875 9.875 3.28125 9.6875C3.09375 9.5 3 9.27083 3 9C3 8.72917 3.09375 8.5 3.28125 8.3125C3.46875 8.125 3.69792 8.03125 3.96875 8.03125H14.0313Z'
const channels = [
{ value: 'email', label: 'Email' },
{ value: 'sms', label: 'Text message' },
{ value: 'push', label: 'Push' },
]
const allValues = channels.map((channel) => channel.value)
const selected = ref(['email'])
</script>
Usage guidelines
- Every checkbox in the group needs
options.value, because that string is what goes into the array and what the form submits.must - The parent checkbox needs the group’s
allValues, since nothing else tells it what “everything” means. Members that have not mounted yet still count.must - The group is one control as far as a
Fieldis concerned, so validation runs on the array and the members read the result rather than registering themselves.may - Give every per-member
Labelits ownid, or inside aFieldthey share the field’s label ID and all register as its label.must