Skip to content

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.

HTML tag<l-button-group>
⏣Native
⬡Progressive
◇Plain
⬢Shadow-DOM
Plain Custom Element
html
<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
html
<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
html
<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
html
<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:

js
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:

js
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
html
<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.

Save as draft Save as template
Code
html
<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 ​

Role

Host gets role="group" to expose the buttons as a set

WCAG1.3.1
RGAA9.3
Accessible name

label sets aria-label so assistive tech announces the group purpose

WCAG4.1.2
RGAA11.1
Orientation

orientation is visual only. ARIA 1.2 does not allow aria-orientation on role="group", so no ARIA attribute is set

WCAG1.3.1
Focus order

Each button is an independent Tab stop; the focus ring is raised above neighbours so it is never clipped

RGAA10.7
Toggle state

Buttons that carry state expose it with aria-pressed; the group never sets it

WCAG4.1.2
RGAA11.1
Icon-only buttons

Each icon-only button needs its own aria-label

WCAG4.1.2
RGAA11.1

Rules

  • Always add a label describing the group purpose
  • Children must be native <button class="l-button"> elements (optionally wrapped in l-dropdown for split buttons)
  • Give each icon-only button its own aria-label
  • For toggle buttons, set aria-pressed on every button of the group, not only the pressed one

Keyboard interactions ​

Tab
Moves focus to the next button in the group
ShiftTab
Moves focus to the previous button
Enter
Activates the focused button
Space
Activates the focused button

API reference ​

Importing ​

js
import 'luxen-ui/button-group';
css
@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-orientation on role="group", so no ARIA attribute is set.