Skip to content

A single radio button, for composing an exclusive group by hand inside your own markup.

import { BbRadio } from 'bitboss-ui';

On this page

Use it for

There are two reasons to reach for a lone BbRadio. Either the options must sit inside other markup, such as a pricing card per option, or one option needs a label the others do not have.

Use something else when

  • BbRadioGroup: almost always. It wraps the same buttons in a real <fieldset>, maps your items, wires name and the model, and gives you one validation channel for the set
  • BbSelect: the set is large or dynamic
  • BbCheckbox: it is an independent yes/no

A lone BbRadio is an escape hatch. This page is mostly about knowing whether you are standing in front of one.

The field states and the layout are the same as BbCheckbox's and are documented there. The value is not: each button carries its own value, and the set shares one model.

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.

An exclusive group by hand

Three things make a set of BbRadio behave as one group: a shared name, the same v-model, and a distinct value on each button.

Model value: 1

Selecting one deselects the others, because they share the model. And because they are native radios sharing a name, the browser gives you roving focus for free: Tab reaches the set once, and the arrow keys move focus and selection between the buttons.

value is what lands in the model. Any serializable value works and is matched structurally, but prefer stable primitive ids: they survive a refetch and are cheap to submit.

What you do not get for free is the grouping semantics. The container is yours, and so are its role="radiogroup" and its accessible name. A set of radios with no wrapper is announced as three unrelated buttons.

Coming from v2

name is no longer required. Omit it and the component generates one, unique to that button and stable for its lifetime, so a lone radio is never left unnamed. Watch the group case, though: generated names differ per button. Without an explicit shared name no native group forms, and you lose exclusivity and the arrow keys with it.

reverse inverted its meaning, as on BbCheckbox: delete :reverse="true" and write :reverse="false" as a bare reverse.

Options inside other markup

The first reason to hand-compose: each option carries content that sits beside the radio rather than inside it.

€0forever

€24per seat / mo

€96per seat / mo

BbRadioGroup maps one label and one value per item. When an option needs a second column, a price or a thumbnail say, that mapping runs out and you want a BbRadio per option inside your own card.

Keep description on the radio rather than in the card. It is wired into the input's aria-describedby, so it is read as part of the option. The same text in a sibling <p> is read as unrelated page content.

Per-option labels

The second reason: one option needs a badge, emphasis or a link that the others do not.

The label slot replaces the rendered label and receives { text, hasErrors, hasWarnings }, where text is the label prop. Keep passing label even when you override it: that string is still the accessible name.

Reserve this for the option that genuinely differs. If every option needs the same decoration you have a uniform mapping again, and BbRadioGroup does it with less markup.

Errors and locked options

errors lives on a button and the error belongs to the group, which is where the missing fieldset starts to cost you something.

Choose a sign-in method.

Put the message on one button, the last, so it reads as closing the set. Repeated errors render the same sentence three times and announce it three times. The price is that the message stays tied to that one option.

BbRadio never joins a BbForm, so a hand-composed set inside a form is not validated at all. When the choice needs validating, use BbRadioGroup, which joins the form by name and takes rules. When the radios must stay inside your own cards, bind the choice with useBbFormField (A control of your own) and pass its errors to the last button.

disabled and readonly differ exactly as they do on BbCheckbox. There is one radio-specific catch: role="radio" supports no aria-readonly, so a lone readonly radio cannot announce that it is locked. Say it in description, or use BbRadioGroup, whose radiogroup does carry the attribute.

Custom markup

At the far end of the same slope, BbRadioGroup then BbRadio then this, BbBaseRadioIcon gives you the library's dot inside a surface that is entirely yours.

Workspace plan

BbBaseRadioIcon draws and nothing more: the ring and the scaling inner dot, with no input, no events and no state. You pass checked, disabled, readonly, has-errors, has-warnings, focused and focus-visible. There is no indeterminate here, because a radio is binary per option.

It is aria-hidden and cannot be focused, so it must sit on real native radios sharing one name. Those are what keep exclusivity and the arrow keys working. Hide the input with sr-only rather than display: none, which would remove it from the tab order and from the form. The icon cannot see the input's focus, so pass focus-visible yourself, as the demo does, or a keyboard user gets no ring.

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

The custom properties live on the dot element, so target that element rather than an ancestor:

css
.bb-radio .bb-base-radio-icon {
    --size: 20px; /* ring diameter, default 16px */
    --space: 3px; /* gap between ring and dot, default 2px */
    --accent: #16a34a; /* the dot */
    --border-color: var(--bb-text-muted); /* the ring, default --bb-border */
}

Recolor the dot through --accent, or through the theme's --bb-primary when the whole product should follow.

Coming from v2color removed

v2's color prop is gone, with no replacement. A leftover color becomes --accent on the dot.