Button groups

Button groups should make related actions feel connected. Use them for compact command sets or mutually related choices, not as decoration around unrelated links. There is no generic segmented control: grouping happens through three concrete containers, each owned by the component that renders it.

When to use#

  • Use a group for a compact command set or mutually related choices; never as decoration around unrelated links
  • Use the connected cluster when the choices are positions in one sequence and exactly one is current — page numbers are the only case today
  • Use a spaced row when the actions are related but independently meaningful — the home hero pair, filter chips
  • Show selection state on the member, not the container: tag-pill-active with aria-pressed for filters, aria-current for the current page
  • Give every group an aria-label naming what the group does, on the <nav> or wrapping element

Implementation#

<nav class="filter-chip-list" aria-label="Filter by topic">
  <button class="filter-chip filter-chip-button tag-pill-active" type="button" aria-pressed="true">All <span class="filter-chip-count">24</span></button>
  <button class="filter-chip filter-chip-button" type="button" aria-pressed="false">Security <span class="filter-chip-count">9</span></button>
</nav>

<nav aria-label="Page numbers">
  <ol class="pagination-pages">
    <li><span class="pagination-button pagination-button-current" aria-current="page">1</span></li>
    <li><a class="pagination-button" href="/en/blog/page/2/">2</a></li>
  </ol>
</nav>
<button class="filter-chip filter-chip-button" type="button" data-table-filter-group="tag" data-table-filter-value="security" aria-pressed="false">Security</button>

There is no shared group class. The three containers are home-hero-actions (layouts/index.html), filter-chip-list (rendered by partials/data-page/filter-row.html — see Filter row), and pagination-pages (rendered by partials/pagination.html — see Pagination). Styling is component classes in assets/css/site.css. Behaviour lives in assets/js/site.js: initTableLists() wires buttons carrying data-table-filter-group and data-table-filter-value, plus an optional data-table-filter-reset button, toggling tag-pill-active and aria-pressed; initTalksArchive() does the same for the talks archive.

Variants#

ContainerPatternRendered by
pagination-pagesConnected cluster; members share inner borderspartials/pagination.html
filter-chip-listSpaced row of toggles with countspartials/data-page/filter-row.html
home-hero-actionsSpaced pair of call-to-action buttonslayouts/index.html

Interface manifest

Kind
Component
Category
Action
Status
Implemented
Implementation
Partial:layouts/partials/data-page/filter-row.htmllayouts/partials/pagination.htmlLayout:layouts/index.htmlCSS:assets/css/site.cssJavaScript:assets/js/site.js

Accessibility#

  • Every container is a named landmark or group: pagination and filter-chip-list render as <nav> with an aria-label — supplied by all five current filter-row.html callers — and home-hero-actions carries role="group" with its aria-label
  • Selection is announced on the member: toggle chips are <button> elements whose aria-pressed is kept in sync by initTableLists() and initTalksArchive(); a chip rendered as an <a> navigates instead and carries no state
  • Each member is a separate tab stop and toggles with the native Enter and Space activation of <button>; no script manages focus or arrow keys
  • Disabled pagination arrows are aria-hidden <span> elements outside the tab order, and the previous, next, and page-number links each carry an aria-label
  • Avoid role="toolbar": it promises arrow-key navigation between members that these groups do not implement