Skip to content
bitboss-ui3.0.0-beta.64

BbCheckbox

One checkbox for a yes/no that stands on its own, with the full field surface.

import { BbCheckbox } from 'bitboss-ui';

On this page

Use it for

Reach for BbCheckbox when the answer is one yes/no standing on its own: accept the terms, remember this device, include archived records.

Use something else when

  • BbCheckboxGroup: several boxes inside one set. Never hand-stack BbCheckbox
  • BbRadioGroup: exactly one choice out of the set
  • BbSelect: the set is long, searchable or fetched
  • BbSwitch: it is the state of a thing, like notifications or dark mode

Also see: Validated forms.

A checkbox is an answer, a switch is a state. A Save button below the field changes nothing about which of the two you have.

This page also covers the field states and the layout of BbRadio and BbSwitch, and the value contract of BbSwitch; those pages link back here.

Passthrough

Hover or tap a part to outline it. The toggles turn on loading, errors and warnings where the component has them; only parts get a row.

Guidance while the control is focused.

Default

You need a label and a bound v-model. Clicking the box or the label toggles the value.

Model value: false

label is required: it is the control's accessible name. id and name are generated when you leave them off, but pass both in a form that posts.

States and messages

A checkbox carries the same chrome as every other field in the library, and the four message channels are not interchangeable.

You can leave the programme at any time.
Accept the terms to continue.
Exports will take noticeably longer.

Put durable information in description and momentary guidance in hint. hint shows only while the field has focus, unless persistent-hint pins it open.

errors takes a string or an array, and a non-empty value puts the field into the error state on its own. has-errors gives you the styling without a message. warnings is the amber counterpart and does not set aria-invalid. When both are set errors win, so the field is never red and amber at once.

All four are wired into aria-describedby in that order: errors, warnings, description, hint. An aria-live="polite" region announces them when they appear and re-reads them on focus, so keep them short.

readonly is not disabled

disabled sets the native attribute: the box leaves the tab order and its value is never submitted. readonly keeps it focusable and submitted, and blocks only the changes. Clicks and Space do nothing, and update:modelValue never fires.

Use readonly for a value that must reach the server without changing, and disabled for a value that must not post. When you disable, say why in the description: a box that is off for no visible reason reads as a bug.

What the model holds

With nothing else set the model is true or false. Point true-value and false-value at your own values and it holds those.

Posted as digest=off whichever way it is set.

Any serializable value works, matched structurally rather than by reference. update:modelValue emits true-value on check and false-value on uncheck, so never read the checked state off a @click or @change handler. Bind an array instead and the checkbox adds or removes its true-value in it, as a native checkbox does.

For a classic form post the native input carries name and a serialized value. An unchecked box posts nothing, which is standard HTML and a recurring surprise on the server. submit-when-false renders a hidden input carrying false-value, so the field always arrives.

In an SPA or an Inertia app you bind into form state and drop both name and submit-when-false.

vue
<BbCheckbox v-model="form.newsletter" label="Subscribe to the newsletter" />

A select-all box

Not everything a box shows comes from its model. A parent box that summarises a group is checked when every child is on, and indeterminate when only some are.


Teams to notify

2 of 7 teams notified

indeterminate is a purely visual third state, a dash instead of a check. It never enters the model, and the component re-applies it after every click. If your server needs a real tri-state value, model it separately.

The master box is driven from outside, so it takes checked rather than a v-model. Passing both gives it two sources of truth: pick one.

The children are a BbCheckboxGroup, not a hand-stacked row.

Coming from v2

checked used to accept the strings 'true' and 'false' too. In v3 it is a plain boolean, so checked="false" no longer compiles. That string was truthy and checked the box, so this is a loud failure replacing a silent one.

The "I agree" box, with links inside its label, that unlocks the primary action.

The label slot decorates the text without touching the accessible name, and receives { text, hasErrors, hasWarnings }. The whole control is one <label>, so the links inside it have to be real anchors. A click on an <a> navigates, a click on anything else toggles the box.

Bind the button's disabled to the model, rather than intercepting the click and reading the box state by hand.

Inside a BbForm the box joins the form under its name. Give it rules="required", a rule you register once, and the form holds the submit until the box is ticked. The label is a sentence, so name the field for the message with validation-label="Terms", or write the message in the rule itself. The required prop on a box inside a form is a hint: it marks the box and enforces nothing. Outside a form it sets the native attribute, as in the demo; write your own message in errors.

A box that is not form data, such as a "show archived" filter, takes formless and stays out of the submitted values.

Layout

By default the box comes first and the label follows. reverse swaps them, and that is what turns a checkbox into a settings row.

layout decides how the two halves share the width. auto, the default, sizes each to its content on one line. label-fill gives the label the free width and input-fill the other way round; horizontal splits them evenly and vertical stacks them. A two-token pattern like "x xxxxxxx" splits them by token length, the first token sizing the element that renders first. input-position and label-position (left, center, right) then align the box and the text inside their columns.

compact tightens the gap between the label and the description, as on the first row, for dense forms and settings panels.

hide-label keeps the label for screen readers and hides it on screen. It is for a table's selection column, where visible text already labels the box, and nothing else.

Coming from v2

reverse means the opposite of what it used to, and the compiler cannot see it. The default layout is unchanged, so a checkbox with no reverse is fine. What changed is the explicit value. In v2 reverse defaulted to true and :reverse="false" produced the swap. In v3 reverse defaults to false, and passing it opts into the swap. Delete reverse and :reverse="true", rewrite :reverse="false" as a bare reverse, and grep for the prop on all three single controls.

Custom markup

Sometimes the design is not a labelled row at all: a grid of selectable cards, say. BbBaseCheckboxIcon gives you the library's box, and you own everything around it.

Teams to notify

BbBaseCheckboxIcon draws and nothing more: the rounded box, the animated check and the indeterminate dash, with no input, no events and no state. You tell it how to look with checked, indeterminate, disabled, readonly, has-errors, has-warnings, focused and focus-visible.

Because it is aria-hidden and cannot be focused, it has to sit on a real control. Copy the shape in the demo: a <label> wrapping an <input type="checkbox"> hidden with sr-only. Never display: none, which takes the input out of the tab order and out of the form. Keyboard, focus and form submission come free. The icon cannot see the input's focus, so pass focus-visible yourself, as the demo does, or a keyboard user gets no ring. Tab into the cards to see it.

There is a smaller step before that one. The icon slot on BbCheckbox replaces only the box visual and keeps the input, the label and the field chrome. It receives the current state: checked, indeterminate, disabled, readonly, hasErrors, hasWarnings, focused, focusVisible, value, trueValue, falseValue and text.

Set the box's custom properties through pt:icon, so they land on the box element and not on an ancestor:

vue
<BbCheckbox
    v-model="done"
    label="Mark as done"
    pt:icon="[--size:20px] [--r:6px] [--accent:#16a34a] [--check-color:white]"
/>

--size is the box edge (default 16px), --r the corner radius, --accent the fill and border when checked, --check-color the checkmark stroke and --border-color the unchecked border.

This is where v2's color prop went, removed with no replacement. Recolor through --accent, or through the theme's --bb-primary when the whole product should follow.