Button group <l-button-group>
A wrapper for multiple related buttons, joined into a single unit with shared borders. Buttons keep their own state; the group never manages the selection — for a single choice, see segmented control when it applies immediately, or radio group when the value is submitted with a form.
<l-button-group><l-button-group label="Record actions">
<button class="l-button">Edit</button>
<button class="l-button">Duplicate</button>
<button class="l-button">Archive</button>
</l-button-group>Options
Label
Add label to give the group an accessible name announced by screen readers.
Code
<l-button-group label="Record actions">
<button class="l-button">Edit</button>
<button class="l-button">Duplicate</button>
<button class="l-button">Archive</button>
</l-button-group>Orientation
Add orientation="vertical" to stack the buttons.
Code
<l-button-group
label="Record actions"
orientation="vertical"
>
<button class="l-button">Edit</button>
<button class="l-button">Duplicate</button>
<button class="l-button">Archive</button>
</l-button-group>Sizes
Set data-size on every button of a group so it stays even. The sizes below are sm, the default md, lg, and xl.
Code
<l-button-group label="Pagination, small">
<button
class="l-button"
data-size="sm"
>
Previous
</button>
<button
class="l-button"
data-size="sm"
>
Next
</button>
</l-button-group>
<l-button-group label="Pagination, medium">
<button class="l-button">Previous</button>
<button class="l-button">Next</button>
</l-button-group>
<l-button-group label="Pagination, large">
<button
class="l-button"
data-size="lg"
>
Previous
</button>
<button
class="l-button"
data-size="lg"
>
Next
</button>
</l-button-group>
<l-button-group label="Pagination, extra large">
<button
class="l-button"
data-size="xl"
>
Previous
</button>
<button
class="l-button"
data-size="xl"
>
Next
</button>
</l-button-group>Examples
Toolbar
Sit several groups side by side to build a toolbar. Each group keeps its own label, so assistive tech announces which set a button belongs to, and each group decides whether it carries state:
- History — plain actions, no state.
- Text formatting — independent toggles; several can be pressed at once.
- Text alignment — a single choice; exactly one stays pressed.
Buttons that carry state expose it with aria-pressed, and a pressed button keeps its active fill in every variant. The group never manages the selection — your application owns it. Independent toggles flip their own state:
formatting.addEventListener('click', (event) => {
const button = event.target.closest('button');
if (!button) return;
const pressed = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', String(!pressed));
});A single choice presses one button and releases the others:
alignment.addEventListener('click', (event) => {
const button = event.target.closest('button');
if (!button) return;
for (const b of alignment.querySelectorAll('button')) {
b.setAttribute('aria-pressed', String(b === button));
}
});Add data-icon-only to a button with no visible text to square it, and give it an aria-label.
The wrapper is a plain element, not role="toolbar": that role expects arrow-key navigation across the whole bar, which the groups do not implement — each button stays a Tab stop.
For a single choice, reach for segmented control when it applies immediately, or radio group when the value is submitted with a form.
Code
<div class="flex flex-wrap items-center gap-2">
<l-button-group label="History">
<button
class="l-button"
data-icon-only
aria-label="Undo"
>
<l-icon name="lucide:undo-2"></l-icon>
</button>
<button
class="l-button"
data-icon-only
aria-label="Redo"
>
<l-icon name="lucide:redo-2"></l-icon>
</button>
</l-button-group>
<l-button-group label="Text formatting">
<button
class="l-button"
data-icon-only
aria-label="Bold"
aria-pressed="true"
>
<l-icon name="lucide:bold"></l-icon>
</button>
<button
class="l-button"
data-icon-only
aria-label="Italic"
aria-pressed="true"
>
<l-icon name="lucide:italic"></l-icon>
</button>
<button
class="l-button"
data-icon-only
aria-label="Underline"
aria-pressed="false"
>
<l-icon name="lucide:underline"></l-icon>
</button>
</l-button-group>
<l-button-group label="Text alignment">
<button
class="l-button"
data-icon-only
aria-label="Align left"
aria-pressed="true"
>
<l-icon name="lucide:align-left"></l-icon>
</button>
<button
class="l-button"
data-icon-only
aria-label="Align center"
aria-pressed="false"
>
<l-icon name="lucide:align-center"></l-icon>
</button>
<button
class="l-button"
data-icon-only
aria-label="Align right"
aria-pressed="false"
>
<l-icon name="lucide:align-right"></l-icon>
</button>
</l-button-group>
</div>Split button
Pair an action with an <l-dropdown> of related ones. The group rounds the corners of the dropdown trigger automatically.
Code
<l-button-group label="Save options">
<button class="l-button">Save</button>
<l-dropdown>
<button
slot="trigger"
class="l-button"
data-icon-only
aria-label="More save options"
>
<l-icon name="lucide:chevron-down"></l-icon>
</button>
<l-dropdown-item value="draft">Save as draft</l-dropdown-item>
<l-dropdown-item value="template">Save as template</l-dropdown-item>
</l-dropdown>
</l-button-group>Accessibility
Criteria
- Orientation
orientationis visual only. ARIA 1.2 does not allowaria-orientationonrole="group", so no ARIA attribute is setWCAG1.3.1- Focus order
Each button is an independent
Tabstop; the focus ring is raised above neighbours so it is never clippedRGAA10.7
Rules
- Always add a
labeldescribing the group purpose - Children must be native
<button class="l-button">elements (optionally wrapped inl-dropdownfor split buttons) - Give each icon-only button its own
aria-label - For toggle buttons, set
aria-pressedon every button of the group, not only the pressed one
Keyboard interactions
API reference
Importing
import 'luxen-ui/button-group';@import 'luxen-ui/css/button-group';Attributes & Properties
labelstring | undefinedProperty- Accessible label announced for the group. Not displayed on screen.
orientationButtonGroupOrientationdefault:'horizontal'Property- Layout direction of the buttons. Visual only (drives the CSS via the
reflected attribute): ARIA 1.2 does not allow
aria-orientationonrole="group", so no ARIA attribute is set.