Button group
Two roles in the same box. Joined, it turns actions that belong together into one block instead of
three loose buttons on the line. With is-segmented, it dresses the <input> as a
button — picking one option out of a few, with the look of the segmented tabs. Class only:
no JavaScript, and the value still belongs to the native <input>.
Examples
The same .tuc-btn-group on the outside; inside, either buttons or labels with an
<input> in them.
One option out of a few
It is type="radio": the browser clears the previous one, and the arrow keys move through the group.
Several at once
Switch to type="checkbox" and each button turns on and off on its own.
Joined actions
Without is-segmented, the buttons share the border and only the ends are rounded. Any variant works.
Small, next to a field
is-sm on the buttons matches the height of an is-sm field; the corner follows.
Full width
is-block takes the whole line and splits it evenly — the way to go on a phone.
With label and hint
In a form it is a field like any other: .tuc-label above, .tuc-hint below.
Changes how the tax on the invoice is worked out.
How to use
For the choice, the <input> goes inside the
<label class="tuc-btn">, as a direct child: it is what the CSS reads to know which one is
checked.
<div class="tuc-btn-group is-segmented" role="group" aria-label="Period">
<label class="tuc-btn">
<input type="radio" class="tuc-btn__input" name="period" value="day" checked> Day
</label>
<label class="tuc-btn">
<input type="radio" class="tuc-btn__input" name="period" value="week"> Week
</label>
</div>
<div class="tuc-btn-group" role="group" aria-label="Contract actions">
<button type="button" class="tuc-btn is-outline">Edit</button>
<button type="button" class="tuc-btn is-outline">Duplicate</button>
</div>role="group" with an aria-label (or aria-labelledby pointing at the label)
is what makes the screen reader announce what the row is about. In a form, a <fieldset> with a
<legend> works too — just do not put the legend inside the group, which is a row of buttons.
In a Django form
No new widget: it is the usual RadioSelect, with the class in attrs and the field
looped over in the template.
period = forms.ChoiceField(
choices=[('day', 'Day'), ('week', 'Week'), ('month', 'Month')],
widget=forms.RadioSelect(attrs={'class': 'tuc-btn__input'}),
)<div class="tuc-btn-group is-segmented" role="group" aria-label="{{ form.period.label }}">
{% for radio in form.period %}
<label class="tuc-btn">{{ radio.tag }} {{ radio.choice_label }}</label>
{% endfor %}
</div>Looks like a tab, but is not one
A segmented tab changes what is on the screen and never reaches the server. The button group is a field: it has name and value, it goes in the POST and it takes required. If the choice travels with the form, it belongs here; if it only swaps a panel, it belongs there.
Up to about four options, and short ones
They all stay visible side by side: with many of them, or with long text, the row bursts the line on a phone. Past that, use the select or the options in a list — which are also the right call when each option needs an explanation under it.
Keyboard and accessibility
All native. In the choice, what takes the focus is the <input> — invisible, but
present: it has zero opacity rather than being hidden, precisely so it stays focusable and announced. The focus
ring shows on the button around it.
| Key | Where | Action |
|---|---|---|
Tab | Radio group | Lands on the checked option and then leaves — the whole group is a single stop |
← → ↑ ↓ | Focused radio | Moves through the options and checks the one it lands on |
Tab | Checkboxes and buttons | Each is its own stop, because each acts on its own |
Space | Focused checkbox | Turns it on and off |
Enter Space | Action button | Activates it |
- The group needs a name:
role="group"witharia-label, or a<fieldset>with a<legend>. - An icon-only button still needs
aria-label, as in any button. disabledon the<input>dims the button by itself — no extra class on the label.
Classes
CSS only. The box is the segmented tab's, on purpose: both have to line up with the fields on the
row, which is why the border plus the padding add up to exactly --tuc-control-height.
| Class | What for |
|---|---|
tuc-btn-group | The row. On its own, it joins the buttons into a single block |
is-segmented | The recessed box of the segmented tabs, for choice as buttons |
is-block | Takes the whole line, split evenly |
tuc-btn__input | The <input> inside the label: invisible, focusable and still the owner of the value |
tuc-btn and its variants | Each item in the group — is-outline, is-primary, is-sm, is-icon |