GrCheckboxGroup
Берут, когда значений несколько из короткого набора.
Когда брать
- значений несколько из короткого набора — права, теги, дни недели, каналы уведомлений;
- все варианты должны быть видны — выбор виден целиком, без раскрытия панели;
- группа — часть формы — общая модель
string[], общиеdisabled,readonlyиinvalid; - вариантов до десятка — дальше список становится длиннее экрана.
Когда взять другое
| Нужно | Берите |
|---|---|
| Вариантов много | GrSelect с multiple |
| Вариантов много и их ищут вводом | GrAutocomplete |
| Можно выбрать только один | GrRadioGroup |
| Чекбокс один | GrCheckbox |
| Варианты вложены | GrTreeSelect |
Модель и контекст
Группа раздаёт вложенным чекбоксам выбранные значения, name, size и
состояния disabled/readonly/invalid, поэтому собственный v-model им не
нужен — достаточно value:
<GrCheckboxGroup v-model="channels" name="channels" :options="options" />
<GrCheckboxGroup v-model="channels" direction="horizontal">
<GrCheckbox value="sms">SMS</GrCheckbox>
<GrCheckbox value="email">Email</GrCheckbox>
</GrCheckboxGroup>
name уходит на каждый отмеченный чекбокс, поэтому нативная форма получает
повторяющееся поле: new FormData(form).getAll('channels').
disabled отдельной опции сильнее доступной группы. Значение в модели, которого
нет среди options, группа не теряет: снятие соседнего чекбокса его сохраняет.
Роль и ARIA
role="group", а не radiogroup: у чекбоксов нет roving tabindex и переезда
выбора стрелками — каждый остаётся собственной остановкой Tab, ровно как набор
нативных <input type="checkbox">.
role="group" не поддерживает aria-required и aria-readonly (axe роняет это
как critical aria-allowed-attr), поэтому оба состояния объявляют сами
чекбоксы. aria-invalid, наоборот, висит только на группе: продублированный на
каждом пункте, он заставил бы диктор повторить «неверное значение» столько раз,
сколько в группе чекбоксов, — пункты при ошибке только перекрашиваются.
Обязательность
Как и у одиночного чекбокса, required — объявление; проверяет правило формы.
Для группы работает встроенное required: пустой массив считается пустым
значением.
const rules: GrFormRules = {
channels: [{ required: true, message: 'Выберите хотя бы один канал' }],
}Playground 7
Загружается…
<GrCheckboxGroup />Установка
npm i @feugene/granularityИмпорт
import { GrCheckboxGroup } from '@feugene/granularity/components/GrCheckboxGroup'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
options | GrCheckboxGroupOption[] | undefined | undefined | — |
disabled | boolean | undefined | false | — |
readonly | boolean | undefined | false | Только для чтения: выбор видно, но он не меняется. |
invalid | boolean | undefined | false | Визуальное и ARIA-состояние ошибки для всей группы. |
required | boolean | undefined | false | Обязательная группа: `aria-required` объявляют сами чекбоксы. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | — |
ariaLabel | string | undefined | undefined | — |
name | string | undefined | undefined | Общее имя для нативной формы: значения уйдут как повторяющиеся поля. |
direction | GrCheckboxGroupDirection | undefined | "vertical" | — |
modelValueобязательный | string[] | — | — |
Slots
| Slot | Type | Описание |
|---|---|---|
default | any | Собственная разметка флажков вместо генерации из `options`. |
Events
| Event | Type | Описание |
|---|---|---|
update:modelValue | [value: string[]] | — |
change | [value: string[]] | — |
focus | [event: FocusEvent] | — |
blur | [event: FocusEvent] | — |
Methods / Expose
| Methods / Expose | Type | Описание |
|---|---|---|
focus | () => void | — |
blur | () => void | — |
Примеры 2
Множественный выбор из списка опций
Одна модель string[] на всю группу: чекбоксам не нужен собственный v-model, а раскладка переключается пропом direction.
<script setup lang="ts">
import { ref } from 'vue'
import { GrCheckboxGroup, GrSegmented } from '@feugene/granularity'
const options = [
{ value: 'email', label: 'Email' },
{ value: 'sms', label: 'SMS' },
{ value: 'push', label: 'Push' },
{ value: 'webhook', label: 'Webhook', disabled: true },
]
const channels = ref(['email', 'push'])
const direction = ref<'vertical' | 'horizontal'>('vertical')
</script>
<template>
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_240px]">
<div class="grid gap-3 rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
<GrCheckboxGroup
v-model="channels"
name="channels"
:options="options"
:direction="direction"
aria-label="Notification channels"
/>
</div>
<div class="grid gap-3 rounded-2xl border border-dashed border-[var(--gr-brd)] p-4">
<GrSegmented
v-model="direction"
size="sm"
:options="[
{ value: 'vertical', label: 'Vertical' },
{ value: 'horizontal', label: 'Horizontal' },
]"
/>
<div class="text-sm text-[var(--gr-muted-fg)]">
Selected: <span class="font-semibold text-[var(--gr-fg)]">{{ channels.join(', ') || 'none' }}</span>
</div>
</div>
</div>
</template>Проверка внутри GrForm
Обязательность объявляется через aria-required, а проверяет её правило формы — нативный required не блокирует отправку молча.
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { GrButton, GrCheckbox, GrCheckboxGroup, GrForm, GrFormField, type GrFormRules } from '@feugene/granularity'
const options = [
{ value: 'incidents', label: 'Incidents' },
{ value: 'releases', label: 'Releases' },
{ value: 'digest', label: 'Weekly digest' },
]
const model = reactive({ scopes: [] as string[], terms: false })
// `required` пустым считает `null`/`''`/`[]`, но не `false`: снятый чекбокс —
// это законное значение поля. «Согласие обязательно» — это `validator`.
const rules: GrFormRules = {
scopes: [{ required: true, message: 'Pick at least one subscription' }],
terms: [{ validator: value => value === true || 'Accept the policy to continue' }],
}
const submitted = ref(false)
</script>
<template>
<GrForm
:model="model"
:rules="rules"
class="grid max-w-md gap-4"
@submit="submitted = true"
>
<GrFormField name="scopes" label="Subscriptions">
<GrCheckboxGroup v-model="model.scopes" name="scopes" :options="options" required />
</GrFormField>
<GrFormField name="terms" label="Policy">
<GrCheckbox v-model="model.terms" required>
I accept the notification policy
</GrCheckbox>
</GrFormField>
<div class="flex gap-2">
<GrButton type="submit" size="sm">
Save preferences
</GrButton>
</div>
<p v-if="submitted" class="text-sm text-[var(--gr-success-text)]">
Saved — the form is valid.
</p>
</GrForm>
</template>Доступность
- Паттерн APG
group- Клавиши
- стрелок нет: каждый чекбокс — своя остановка
Tab, как у набора нативных<input type="checkbox">