One panel, five components

A notification settings panel — the ordinary screen every product has. It is also the clearest place to see the distinction this page is built on: the two switches at the top act the moment you move them, and everything below waits for Save.

Include
Save preferences Reset
Move a switch, then save the rest.
Three checkboxes share one name, so the form receives include three times — the same shape a native checkbox group submits, and the reason a server handler needs no special case for these.

A switch is not a checkbox with a different skin. A checkbox states an intention that a form will act on later, so it belongs beside a Save button and it can be indeterminate; a switch acts now, which is why it has no third state and why putting one inside a form that has to be submitted is the commonest way to make people wonder whether their change took.

Get the code
<script src="/assets/bmx-components.min.js"></script>

<bmx-radio-group name="cadence" label="How often" value="digest" required="true">
  <bmx-radio value="instant" label="As they happen"></bmx-radio>
  <bmx-radio value="digest" label="Daily digest"></bmx-radio>
  <bmx-radio value="weekly" label="Weekly summary"></bmx-radio>
</bmx-radio-group>
<bmx-slider name="cap" label="At most, per day" value="12" min="1" max="50" ticks="7" show-value="always" style="inline-size: 20rem"></bmx-slider>
<bmx-rating name="usefulness" label="How useful have these been?" show-value="true" clearable="true"></bmx-rating>

Checkbox

The same chrome as the text fields, turned on its side: the caption sits beside the box rather than above it, and the help text lines up with the caption rather than with the tick.

One box, and the third state
Clicking a mixed box resolves it to ticked, not to unticked — which is what the platform does, and what people mean.
Sizes, tones and states

The box you see is not the input. The real <input> fills a 24 × 24 pixel target invisibly and the tick is drawn inside it, so WCAG 2.2 SC 2.5.8 is met at every size and whether or not the caption is showing. Try clicking just outside the smallest box above.

Get the code
<script src="/assets/bmx-components.min.js"></script>

<bmx-checkbox label="Email me about new releases" description="No more than once a month."></bmx-checkbox>
<bmx-checkbox id="cb-parent" label="Select all regions" indeterminate="true" description="Mixed — some of the three below are ticked."></bmx-checkbox>
<bmx-checkbox class="cb-child" label="North" checked="true"></bmx-checkbox>
<bmx-checkbox class="cb-child" label="Midlands"></bmx-checkbox>
<bmx-checkbox class="cb-child" label="South"></bmx-checkbox>
<bmx-checkbox label="Extra small" size="xs" checked="true"></bmx-checkbox>
<bmx-checkbox label="Small" size="sm" checked="true"></bmx-checkbox>
<bmx-checkbox label="Medium" size="md" checked="true"></bmx-checkbox>
<!-- …and 7 more like these -->

Radio group

One answer from several. The group holds the value, the keyboard and the form participation; the options only render, which is what makes two of them selected at once impossible rather than merely unlikely.

Arrow keys move and choose, in one keystroke
Tab lands on the current answer, not on wherever focus was left — so a stray arrow key cannot change a value you only meant to look at.

A disabled option is stepped over rather than focused and ignored, and a group whose options are all disabled hands out no tab stop at all — exactly as a native group of disabled radios does.

Get the code
<script src="/assets/bmx-components.min.js"></script>

<bmx-radio-group id="radio-contact" label="Contact me by" value="email" description="Tab in, then press Down.">
  <bmx-radio value="email" label="Email"></bmx-radio>
  <bmx-radio value="post" label="Post"></bmx-radio>
  <bmx-radio value="phone" label="Telephone"></bmx-radio>
</bmx-radio-group>
<bmx-radio-group label="Delivery" orientation="horizontal" tone="success" description="Both axes work whichever way it is laid out.">
  <bmx-radio value="standard" label="Standard"></bmx-radio>
  <bmx-radio value="express" label="Express"></bmx-radio>
  <bmx-radio value="collect" label="Collect" disabled="true"></bmx-radio>
</bmx-radio-group>

Switch

For a setting that takes effect the moment it moves. A checkbox states an intention a form will act on later; a switch acts now — which is why it has no third state.

On and off
Sizes, tones and states

The thumb travels rather than jumping, because the travel is the feedback — a switch often has nothing else on screen to confirm it did anything. Under prefers-reduced-motion it arrives at once, which removes the animation without removing the confirmation.

Get the code
<script src="/assets/bmx-components.min.js"></script>

<bmx-switch label="Dark mode" description="A real checkbox carrying role=&quot;switch&quot;, so the platform still supplies Space, focus and form participation."></bmx-switch>
<bmx-switch label="Two-factor authentication" checked="true" checked-text="On" unchecked-text="Off"></bmx-switch>
<bmx-switch label="Small" size="sm" checked="true"></bmx-switch>
<bmx-switch label="Medium" size="md" checked="true"></bmx-switch>
<bmx-switch label="Large" size="lg" checked="true" tone="success"></bmx-switch>
<bmx-switch label="Danger" tone="danger" checked="true"></bmx-switch>
<bmx-switch label="Caption first" label-placement="start" checked="true"></bmx-switch>
<bmx-switch label="Disabled" disabled="true" checked="true"></bmx-switch>

Slider

A value along a track, or a range between two of them. The one form component that is not built on a native input — because there is no native two-ended slider, and shipping the two cases on different mechanisms would mean two keyboards in one tag.

One value, with ticks
A range, and what it announces
Two role="slider" elements, not one with two values — so each thumb can be told how far it may go before it meets the other. The thumbs never cross.
Scales, tones and a vertical track

A drag uses pointer capture, so it keeps working when the pointer leaves the track — past the end, out of the window, over an iframe — and releases itself if the pointer is lost. format supplies the value bubble, the tick labels and aria-valuetext together, so a price slider cannot show "£1,200" while announcing "1200".

Get the code
<script src="/assets/bmx-components.min.js"></script>

<bmx-slider id="sl-volume" label="Volume" value="60" ticks="10" show-value="auto" full-width="true" description="Tab to it, then try the arrows, Page Up, Home and End."></bmx-slider>
<bmx-slider id="sl-price" label="Price range" range="true" min="0" max="5000" step="50" min-distance="250" show-value="always" full-width="true"></bmx-slider>
<bmx-slider id="sl-rating" label="Rating" value="3" min="1" max="5" step="1" ticks="1" tick-labels="true" tone="warning" style="inline-size: 14rem"></bmx-slider>
<bmx-slider label="Opacity" value="0.4" min="0" max="1" step="0.1" ticks="5" tone="success" style="inline-size: 12rem" description="Steps of 0.1, with no floating-point dust."></bmx-slider>
<bmx-slider label="Level" value="70" orientation="vertical" show-value="always" tone="info"></bmx-slider>
<bmx-slider label="Disabled" value="30" disabled="true" style="inline-size: 10rem"></bmx-slider>

Rating

The last of tier one, and the one that argues with itself: it reads like a small set of answers, which is a radio group, and it draws as a value along a scale, which is a slider. It takes the keyboard from one and the role from the other.

Click, drag, or arrow between them
The hover preview never survives the pointer leaving. A row still showing four stars after the mouse has gone is a field that appears to hold a value it will not submit, and the user finds that out at the point of submission.
A picture of a number, which is what most ratings are
A read-only rating is role="img" with the value as its accessible name, and it takes no tab stop. Showing an average beside a review is not a control, and publishing it as one puts a widget in the tab order that answers to nothing the user does.
Other glyphs, scales and wording
The glyph is a path rather than a slot: a slot puts its node in exactly one place, and five icons need five copies. Four shapes ship, and icon-path takes your own in a 24×24 box.

aria-valuetext says "3 of 5 stars", because aria-valuenow="3" on a row of icons is a quantity with no unit and no scale attached to it. Each icon is one shape painted twice — the empty glyph with a filled copy over it, clipped by inline-size — so a half never shimmers along its seam, a right-to-left row fills from the correct edge with no second rule, and a fill of 30% would need nothing added. And zero submits nothing at all rather than score=0: a form cannot otherwise tell "rated nought" from "not rated".

Get the code
<script src="/assets/bmx-components.min.js"></script>

<bmx-rating id="ra-stay" name="stay" label="How was your stay?" show-value="true" description="Tab to the row — one stop, not five."></bmx-rating>
<bmx-rating id="ra-food" label="Food" allow-half="true" clearable="true" show-value="true" description="Halves, and clicking the same star again clears it."></bmx-rating>
<bmx-rating label="Average score" value="4.5" allow-half="true" readonly="true" show-value="true" hide-label="true"></bmx-rating>
<bmx-rating label="Cleanliness" value="4" readonly="true" size="sm" hide-label="true"></bmx-rating>
<bmx-rating label="Value" value="3" readonly="true" size="sm" hide-label="true"></bmx-rating>
<bmx-rating id="ra-words" label="Recommend us" icon="heart" tone="danger" show-value="true" description="Its own wording, in the caption and in the announcement."></bmx-rating>
<bmx-rating label="Difficulty" icon="circle" max="10" tone="info" value="7" size="sm" description="Ten of them."></bmx-rating>
<bmx-rating label="Helpful?" icon="thumb" max="1" tone="success" description="One is a legitimate scale."></bmx-rating>
<!-- …and 1 more like these -->