Skip to content

BbRadioGroup

Exactly one choice out of a set, with native arrow keys and a required prop.

import { BbRadioGroup } from 'bitboss-ui';

On this page

Use it for

Reach for BbRadioGroup when exactly one option out of a small, visible set is the answer: a priority, a shipping speed, a plan. Up to about seven options that a reader wants to compare at a glance belong here.

Use something else when

  • BbSelect: the set runs past that, or the list is searchable, paged or fetched
  • BbCheckboxGroup: the choices are not exclusive
  • BbRadio: an escape hatch for options that have to live inside markup of your own

Pass Through

Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.

Shown under the label — durable context.
Guidance while the control is focused.

Everything this group shares with the other two is on BbCheckboxGroup. That covers the items mapping, the fieldset props and selectable. It also covers the provider and enforce-coherence, the label slot and the layout. The field surface is BbCheckbox's. What follows is only what an exclusive group has to answer.

One exclusive choice

The model holds one resolved value, not an array, and it starts empty as null.

PrioritySets the response time the customer is promised.

Model: null

items and the accessors work as they do on the checkbox group. An array of strings needs nothing. An array of objects needs item-text and item-value. There is no multiple prop here and no max.

The thing to design around is that the user cannot go back to empty. Radios have no un-choose gesture: once an option is picked, only another option can replace it. If "no answer" is still legitimate after the first click, make it an option of its own. A "No preference" row whose value is null does the job.

Seed the model with null when nothing is chosen, and with a real value when a sensible default exists. A pre-selected default that quietly commits the user to something they never read is worse than an empty group.

One tab stop, then the arrows

The options are native radios, so the browser's grouping behaviour comes for free. The group adds the ARIA role the native markup lacks.

Team
Role

Tab reaches each group once. Inside it, the arrow keys move focus and change the choice together — selected: Engineering · Developer

Tab reaches the whole group once, landing on the chosen option or on the first one. Inside it, the arrow keys move focus and the selection together, and wrap at the ends. That coupling is what makes a radio group an exclusive choice. It is why a locked option has to be skipped rather than merely greyed.

The options container carries role="radiogroup", which a <fieldset> cannot supply. The component claims the role only when it can name it, from legend or from an aria-label you pass. An unnamed radiogroup is worse than no role at all. That is another reason legend is required, and why hide-legend is the right way to drop it visually.

readonly is the one field state that behaves differently here. The radio role supports no aria-readonly, so readonly can only be announced on the group. A hand-composed set of BbRadio has no group to announce it on.

Coming from v2

name is no longer required. Omit it and the group generates one, unique to that instance and stable for its lifetime. Two unnamed groups on one page never clear each other's selection. Pass a name when the value has to post under a known key in a native form submission. The relaxation is specific to the group: a set of bare BbRadio still needs an explicit shared name.

Locked options

selectable rejects an option per item, and in a radio group a rejected option disappears from the keyboard path entirely.

Role for the new memberYou are an Admin, so you can grant any role below Admin.

Inviting as Developer

Arrow keys skip disabled radios, so a keyboard user never lands on one. That makes the description do real work: name the rule that locks them, once, for the set. "You are an Admin, so you can grant any role below Admin" is worth more than three greyed rows.

Keeping such options visible is usually the right call, because the ladder above you is information. When the locked options carry nothing, filter them out of items and show a shorter list.

The predicate signature and the migration from v2's per-item disabled field are on BbCheckboxGroup. The short version matters here too: a disabled field inside your item objects does nothing, silently, so grep for it.

disabled on the group does not lock the option already chosen: it stays reachable by keyboard, though choosing it again changes nothing. Use readonly when you need the set genuinely frozen.

Requiring a choice

This is the only one of the three groups with a required prop, and it marks the field rather than validating it.

Stock status

Submit without choosing to see the message.

required reaches the native radios, so assistive technology announces the group as required. With the requiredAsterisk plugin option on, the legend picks up its marker. What it does not do is produce a message. The sentence the user reads comes from errors, which you control. Set both, and let the message appear after a failed submit.

required exists here and not on the other two groups for a reason. One value is either present or absent, so "required" is a complete rule. On a checkbox or switch group the same question becomes "at least one". That is an application rule, with no flag behind it. See validating the whole set.

Inside a validated form, import the group from bitboss-ui/validated and give it rules="required". The field's name comes from legend, and validate-on defaults to ['inactive'].

Selection tiles and CSS

A radio group is the component people most often want to stop looking like radio buttons. Compose the two per-option slots, then style the option element itself.

Appearance

icon replaces the dot and label replaces the text, and both receive the option's item alongside the live state. A tile is built from your own data, with no second array to keep in step. Everything underneath stays a real radio: the name, the exclusivity and the arrow keys are untouched.

Two facts about width decide whether a tile row looks right. The options container is a wrapping flexbox and each option is inline-flex, so options are content-sized. Add flex: 1 1 0 on the option to make them share the row evenly, and note there is no grid to opt into.

The control column is a grid whose computed width defaults to auto. If the row should span the full width, size the BbRadioGroup itself, and do not reach for direction="vertical" just to pick up width: 100%.

Paint the chosen tile from .bb-base-radio-group-option--selected, which the component sets for you, rather than from a :has() selector. It is stable and it is what the library tests against.

Coming from v2

This group's CSS block used to be the shared bb-cr-container, and it is now bb-base-radio-group with the same suffixes. Nothing renders the old prefix and nothing warns, so the rules simply stop matching. The full before-and-after table is on BbCheckboxGroup, along with the labelPositionlegendPosition rename that applies here too.

The group exposes no custom properties of its own. The dot is BbBaseRadioIcon, which owns --color, --size, --space and --ring-color on its own element. That is the surface documented on BbRadio, and where v2's removed color prop went.