Checkbox and radio
Native controls, with the label wrapping the input so the whole thing is a target.
Code
The three render the same class names, the same ARIA and the same data attributes. That is checked when this site builds: a class in any of these samples that neither the markup contract nor the shipped stylesheet defines fails the build.
<label class="wsu-checkbox">
<input type="checkbox" id="advisor" name="advisor">
<span>I have spoken with my advisor</span>
</label>
<fieldset class="wsu-field" id="delivery" tabindex="-1">
<legend class="wsu-field__label">Delivery</legend>
<label class="wsu-radio"><input type="radio" name="delivery" value="in-person"><span>In person</span></label>
<label class="wsu-radio"><input type="radio" name="delivery" value="online"><span>Online</span></label>
</fieldset><x-wsu::form.checkbox name="advisor" label="I have spoken with my advisor" />
<x-wsu::form.radio
name="delivery"
label="Delivery"
:options="['in-person' => 'In person', 'online' => 'Online', 'hybrid' => 'Hybrid']" /><Checkbox v-model="form.advisor" name="advisor" label="I have spoken with my advisor" />
<RadioGroup
v-model="form.delivery"
name="delivery"
label="Delivery"
:options="[
{ value: 'in-person', label: 'In person' },
{ value: 'online', label: 'Online' },
{ value: 'hybrid', label: 'Hybrid' },
]"
/>Why it works this way
- The label wraps the control, so the text is part of the click target rather than a separate 16px box next to it.
- A radio set is a fieldset with a legend. Without it a screen reader user hears the options and not the question.
- The fieldset carries an id and tabindex="-1" of its own, the same as the field it groups. A radio question is answered or not as a whole, so when an error summary links at it, the link has to land on the fieldset, and a fieldset only takes focus when something has given it a negative tabindex first.
- The control keeps its native appearance under forced colors, which is why the styling is an accent-color and a focus ring rather than a hidden input and a drawn box.
Accessibility
- Group of radios wrapped in fieldset with legend (1.3.1, 3.3.2).
- The whole label is the target, which is what gets it past 44px (2.5.5).
- fieldset id and tabindex="-1" so an error summary entry can move focus to the group rather than only scroll to it (2.4.3, 3.3.1).
Success criteria in brackets refer to WCAG 2.2. Every one of them is checked by axe-core in both themes at three viewport widths before this page can be published. See Accessibility for what the gates are.
Props
Blade
| Attribute | Default | What it does |
|---|---|---|
name | required | Also the default id and error-bag key. |
label | null | Rendered next to the box when no slot is given. |
value / falseValue | '1' / '0' | falseValue renders the hidden input, so an unchecked box still submits. |
checked | old input | Falls back to the old input value on a failed submit. |
hint | null | After the control, wired with aria-describedby. The one field whose hint follows rather than leads, because the label already wraps the toggle. |
error | the error bag | Pass a string to override. |
required | false | Adds the required attribute and the "(required)" text. |
id | a slug of name | Set it to change the id without changing name. |
bag | 'default' | Named error bag. |
Vue
| Prop | Type and default | What it does |
|---|---|---|
modelValue | Boolean or Array, false | An array collects several checkboxes into one model. |
value | String, Number or Boolean, true | Only meaningful when modelValue is an array. |
RadioGroup options | Array, [] | [{ value, label }] or plain strings. |