GrCheckbox
Берут, когда согласие или флаг в форме.
Когда брать
- согласие или флаг в форме — «запомнить меня», «согласен с условиями»: значение уедет с отправкой;
- выбор строк списка — чекбокс в заголовке с
indeterminateпоказывает частичный выбор; - подпись интерактивна — она живёт снаружи роли, поэтому ссылка внутри неё остаётся ссылкой;
- нужна нативная форма — скрытый
<input type="checkbox">уходит сnameиvalue.
Когда взять другое
| Нужно | Берите |
|---|---|
| Настройка применяется немедленно | GrSwitch |
| Вариантов несколько и они связаны | GrCheckboxGroup |
| Можно выбрать только один вариант | GrRadio |
| Переключаются режимы отображения | GrSegmented |
Граница со GrSwitch проходит по моменту применения: чекбокс — значение
формы, оно уезжает с сохранением; переключатель меняет состояние системы
сразу, и кнопки «Сохранить» рядом с ним быть не должно.
`required` объявляется, но не проверяется браузером
required доезжает до aria-required на span[role="checkbox"] и не
выставляется на скрытом инпуте. Нативная проверка потребовала бы от браузера
сфокусировать невалидный контрол, а он невидим и aria-hidden: Chrome в таком
случае отменяет отправку всей формы и пишет в консоль «An invalid form control
… is not focusable» — то есть required не мешал бы отправить, а ломал бы
отправку молча.
Проверяет обязательность правило формы:
<script setup lang="ts">
// `required` пустым считает `null`/`''`/`[]`, но не `false`: снятый чекбокс —
// это законное значение поля. «Согласие обязательно» — это `validator`.
const rules: GrFormRules = {
terms: [{ validator: value => value === true || 'Примите условия' }],
}
</script>
<template>
<GrForm :model="model" :rules="rules">
<GrFormField name="terms" label="Условия">
<GrCheckbox v-model="model.terms" required />
</GrFormField>
</GrForm>
</template>Подпись живёт снаружи роли-виджета
role="checkbox" объявляет своих потомков презентационными, поэтому внутри него
нет ни нативного инпута, ни содержимого слота: имя виджета приходит через
aria-labelledby. Ради этого подпись и вынесена наружу — она может содержать
ссылку на политику или кнопку «показать изменения».
Следствие: клик по подписи чекбокс переключает, а клик по её интерактивному
содержимому (a, button, input, select, textarea, вложенный label,
[role="button"], [role="link"]) — нет, такой элемент адресует сам себя.
labelPosition="start" ставит подпись перед контролом.
Состояния
disabled и invalid показываются токенами фона и рамки, а не прозрачностью:
opacity разбавляет выверенные на AA токены текста. readonly оставляет
значение в форме и возвращает нативный инпут к модели, если его переключил клик
по внешнему <label for>.
Внутри GrFormField контрол получает id на виджет, aria-describedby,
aria-invalid и aria-required из контекста поля.
Playground 13
Загружается…
<GrCheckbox />Установка
npm i @feugene/granularityИмпорт
import { GrCheckbox } from '@feugene/granularity/components/GrCheckbox'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
modelValue | boolean | undefined | undefined | Не задан внутри `GrCheckboxGroup` — состояние берётся из группы. |
disabled | boolean | undefined | undefined | — |
readonly | boolean | undefined | false | Только для чтения: состояние видно, но не переключается. |
invalid | boolean | undefined | false | Визуальное и ARIA-состояние ошибки. |
required | boolean | undefined | false | Обязательное поле: объявляется как `aria-required`, нативной проверки нет. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Размер контрола. Не задан — берётся из группы, затем из `GrConfigProvider`, иначе `md`. |
ariaLabel | string | undefined | undefined | Имя контрола, когда подписи в слоте нет (или она чисто визуальная). |
name | string | undefined | undefined | — |
value | string | undefined | "on" | — |
form | string | undefined | undefined | — |
id | string | undefined | undefined | Пробрасывается на скрытый нативный `<input>`, чтобы работал `<label for="...">`. |
indeterminate | boolean | undefined | false | Промежуточное («смешанное») состояние: `aria-checked="mixed"`, индикатор — тире. |
labelPosition | "end" | "start" | undefined | "end" | Сторона подписи относительно контрола. |
Slots
| Slot | Type | Описание |
|---|---|---|
default | any | Подпись флажка. Может содержать ссылки: роль остаётся на самом контроле. |
Events
| Event | Type | Описание |
|---|---|---|
update:modelValue | [value: boolean] | — |
change | [value: boolean] | — |
focus | [event: FocusEvent] | — |
blur | [event: FocusEvent] | — |
Methods / Expose
| Methods / Expose | Type | Описание |
|---|---|---|
focus | () => void | — |
blur | () => void | — |
Примеры 4
Размеры вровень с остальной строкой формы
Чекбокс живёт на той же размерной шкале, что GrInput, GrRadio и GrButton (xs–lg), поэтому смешанная форма выравнивается сама — и глобально, через GrConfigProvider.
<script setup lang="ts">
import { ref } from 'vue'
import { GrCheckbox, GrFormField, GrInput } from '@feugene/granularity'
const sizes = ['xs', 'sm', 'md', 'lg'] as const
const size = ref<typeof sizes[number]>('md')
const compact = ref(true)
const digest = ref(false)
const project = ref('Granularity')
</script>
<template>
<div class="grid gap-4">
<div class="flex flex-wrap items-center gap-4">
<GrCheckbox
v-for="value in sizes"
:key="value"
:model-value="size === value"
:size="value"
@update:model-value="size = value"
>
{{ value }}
</GrCheckbox>
</div>
<div class="grid gap-3 rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
<!-- Подпись через GrFormField: он выдаёт контролу id и связывает с ним
`<label for>`. Нарисованный рядом текст доступным именем не становится. -->
<GrFormField label="Project name">
<GrInput v-model="project" :size="size" />
</GrFormField>
<GrCheckbox v-model="compact" :size="size">
Compact rows
</GrCheckbox>
<GrCheckbox v-model="digest" :size="size" indeterminate>
Partial digest selection
</GrCheckbox>
</div>
</div>
</template>Отмечен, снят и заперт
Показываем базовую матрицу состояний: управляемые чекбоксы, отдельный disabled-case и компактную сводку по текущему выбору.
<script setup lang="ts">
import { computed, ref } from 'vue'
import { GrCheckbox, GrSwitch } from '@feugene/granularity'
const weeklyDigest = ref(true)
const incidentAlerts = ref(false)
const controlsDisabled = ref(false)
const terms = ref(false)
const compactRow = ref(true)
const enabledCount = computed(() => [weeklyDigest.value, incidentAlerts.value].filter(Boolean).length)
</script>
<template>
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_240px]">
<div class="grid gap-3">
<GrCheckbox v-model="weeklyDigest" :disabled="controlsDisabled">
Weekly product digest
</GrCheckbox>
<GrCheckbox v-model="incidentAlerts" :disabled="controlsDisabled">
Incident alerts
</GrCheckbox>
<GrCheckbox :model-value="true" disabled>
Security bulletins are always enabled
</GrCheckbox>
<GrCheckbox v-model="terms" :invalid="!terms" required>
Accept the notification policy
</GrCheckbox>
<GrCheckbox v-model="compactRow" label-position="start">
Label before the control
</GrCheckbox>
</div>
<div class="grid gap-3 rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
<div>
<div class="text-sm font-semibold text-[var(--gr-fg)]">
Selection summary
</div>
<div class="text-sm text-[var(--gr-muted-fg)]">
{{ enabledCount }} of 2 optional channels are active.
</div>
</div>
<GrSwitch v-model="controlsDisabled" size="sm">
Lock editable options
</GrSwitch>
</div>
</div>
</template>Интерактивное содержимое в слоте подписи
Отдельный сценарий фиксирует важную интеграционную деталь: ссылки и кнопки внутри slot-контента не должны случайно переключать чекбокс.
<script setup lang="ts">
import { ref } from 'vue'
import { GrCheckbox } from '@feugene/granularity'
const accepted = ref(false)
const previewOpens = ref(0)
</script>
<template>
<div class="grid gap-4">
<GrCheckbox v-model="accepted">
<span class="inline-flex flex-wrap items-center gap-2 text-sm">
I accept the rollout policy and reviewed the
<a
class="font-medium text-[var(--gr-primary-text)] underline underline-offset-2"
href="https://example.com/policy"
target="_blank"
rel="noreferrer"
@click.stop
>
privacy policy
</a>
<button
type="button"
class="rounded-full border border-[var(--gr-brd)] px-2 py-1 text-xs font-medium text-[var(--gr-fg)] transition hover:border-[var(--gr-primary)] hover:text-[var(--gr-primary-text)]"
@click.stop="previewOpens += 1"
>
Preview changes
</button>
</span>
</GrCheckbox>
<div class="rounded-2xl border border-dashed border-[var(--gr-brd)] bg-[var(--gr-muted)]/35 p-4 text-sm text-[var(--gr-muted-fg)]">
Checkbox value: <span class="font-semibold text-[var(--gr-fg)]">{{ accepted ? 'accepted' : 'pending' }}</span> ·
Preview clicked {{ previewOpens }} times.
</div>
</div>
</template>Семантика отправки нативной формы
Показываем, какие name/value пары реально уходят в FormData, чтобы поведение чекбокса было предсказуемо в обычных формах и без обвязки form-library.
Submit the form to inspect native checkbox values.
<script setup lang="ts">
import { ref } from 'vue'
import { GrButton, GrCheckbox } from '@feugene/granularity'
const marketing = ref(true)
const productUpdates = ref(false)
const submission = ref('Submit the form to inspect native checkbox values.')
function onSubmit(event: SubmitEvent): void {
event.preventDefault()
const formData = new FormData(event.currentTarget as HTMLFormElement)
submission.value = JSON.stringify(Object.fromEntries(formData.entries()), null, 2)
}
</script>
<template>
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_280px]">
<form class="grid gap-3" @submit="onSubmit">
<GrCheckbox v-model="marketing" name="marketing" value="enabled">
Marketing updates
</GrCheckbox>
<GrCheckbox v-model="productUpdates" name="productUpdates" value="beta">
Beta feature updates
</GrCheckbox>
<div class="flex items-center gap-3 pt-2">
<GrButton type="submit" size="sm">Read form data</GrButton>
</div>
</form>
<!-- tabindex: скроллящийся блок обязан быть достижим с клавиатуры,
иначе его содержимое недоступно без мыши (axe: scrollable-region-focusable). -->
<pre
tabindex="0"
aria-label="Submitted form data"
class="overflow-x-auto rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-fg)] p-4 text-xs text-[var(--gr-bg)]"
>{{ submission }}</pre>
</div>
</template>Доступность
- Паттерн APG
checkbox- Клавиши
Space— переключить (в т.ч. изindeterminate→ включено)