Skip to content

Button

C-01 · Components · Actions

All elements · Button on the Components page

Last checked 2026-10-22 · Awaiting approval · Accessibility tests

Copies the bun run a11y:approve command for Button.

Button

C-01WCAG 2.2 AA · Passed #394

The one clickable action affordance, icon buttons included. Built from Pressable + Surface + Text + Icon.

Axis Values
data-variant contained (default) · outlined · text · ghost · soft
data-color primary (default) · info · success · warning · error
data-size md 44px (default) · sm 32px · xs 24px
data-icon-only · data-shape · data-block · data-loading square / circle / full-width / spinner
:disabled · aria-pressed state
data-disclosure + aria-expanded pressed disclosure: a button that shows and hides a region in place (the search toolbar's Filters); open, it reads as pressed in (darker fill, selected border, inset shade). Not for a menu or window opener (aria-haspopup)
__badge a count pinned to the corner (aria-hidden; the name says it: "Filters, 2 applied")
Variants × primary
Colors
Sizes, icons, icon-only
Pressed disclosure and badge: Filters closed, open, open with 2 applied
The filter rows the open buttons control
Dialog action pattern: cancel left, primary right

Button: command

C-01 Office

The transparent command button of ribbons, menus bars, panes and status bars (QEditor spec §2.3 C11). sm is the Full small row (1.5rem), md the Compact row (2rem), lg stacked the Full large button.

Axis / element Values
data-variant="command" on .qf-button
data-size sm 24px · md 32px (default) · lg 66px
data-layout="stacked" icon over a two-line label (Full large)
.qf-button__label · .qf-button__chevron label · menu chevron
aria-pressed · aria-expanded · :disabled toggle · open menu · unavailable
data-priority · data-collapsed 1–4 · label / hidden (P-20)
Sizes and states

Guidance

When to use

  • Use a button to run an action now: save, approve, send, refresh, delete.
  • Use the contained primary button for the one main action in a dialog, pane or form.
  • Use outlined, soft, text or ghost buttons for secondary actions next to the main one.
  • Use an icon-only button in dense toolbars and table rows where the icon is well known and a name is set.
  • Use data-variant="command" for commands in a ribbon, menu bar, task pane or status bar inside the office editors.

When to consider something else

  • If the action takes the user to another page or record, use a link, not a button.
  • If one action has a few close variants, use a split button so the default stays one click. See SplitButton.
  • If the control switches between a few views or formats, use a button group with data-selection. See ButtonGroup.
  • If the control stores an on or off setting, use a toggle instead of a pressed button. See Toggle.
  • If the button opens a list of actions, pair it with a menu and set aria-haspopup="menu". See Menu.

Usability

Do
  • Start labels with a verb and name the object when it is not obvious: "Save view", "Export".
  • Keep one contained primary button per surface; in dialogs put Cancel on the left and the primary action on the right.
  • Use data-color="error" for destructive actions and confirm them first with a confirmation.
  • Use data-loading while an action runs, and keep the button width stable so the layout does not move.
  • In the editors, set data-priority so the ribbon drops low-priority commands into the overflow first.
  • Give a button that shows and hides a region in place (the search toolbar's Filters) data-disclosure with aria-expanded and aria-controls; while the region is open it reads as pressed in.
Don’t
  • Don't rely on color alone to tell buttons apart; the label must say what happens.
  • Don't disable a button without telling the user why; show the reason nearby or in a rich tooltip.
  • Don't use xs (24px) buttons as the main action, or pack them tightly in touch layouts.
  • Don't mix Tabler icons into .qf-office command buttons, or Fluent icons outside it.
  • Don't put data-disclosure on a button that opens a menu or a window (aria-haspopup), or use data-selected to draw a pressed state.

Accessibility

Keyboard

Button keyboard
Keys Action
Tab Moves focus to the button. A command button inside a data-rove toolbar is one stop for the whole toolbar.
Enter or Space Activates the button; on a button with aria-pressed, flips the pressed state.
Arrow Left or Arrow Right Inside a data-rove toolbar, moves to the previous or next command, wrapping at the ends.
Home or End Inside a data-rove toolbar, moves to the first or last command.
Ctrl+Arrow Left or Ctrl+Arrow Right Inside a ribbon, jumps to the first command of the previous or next group.
Escape Closes the menu a command button opened and returns focus to the button.

Roles and names

  • Use a native <button> with type="button" unless it submits a form.
  • Icon-only buttons need aria-label; the icon carries aria-hidden="true". Office commands also set title as the tooltip.
  • A toggle button uses aria-pressed="true|false"; a menu button uses aria-haspopup="menu" and aria-expanded.
  • While data-loading is set, keep the visible label in the DOM so the name stays, set aria-busy="true", and ignore further activation; the static layer only blocks pointer input.
  • A __badge count is aria-hidden; the button's accessible name carries it ("Filters, 2 applied"). The badge sits on the button's corner wherever the button is.

Focus

  • Focus shows the token ring (--qf-focus-ring); command buttons use an inset outline so it is not clipped by the ribbon.
  • A native disabled button leaves the tab order. When a disabled command must stay reachable to read its reason, use aria-disabled="true" instead; both are styled.
  • After a menu or dialog the button opened closes, focus returns to the button.

Other requirements

  • Text contrast must meet 4.5:1 (1.4.3). The a11y report lists success, warning and error fills and some outlined colors as known contrast debt in the classic and dark themes; check before using them for text.
  • Target size 2.5.8: md is 44px and sm 32px; xs and sm command buttons are 24px, so keep at least 24px of spacing around them.
  • The loading spinner animates; prefers-reduced-motion shortens it (2.3.3). Announce the result of a long action in a live region (4.1.3).
  • A visible label must be part of the accessible name (2.5.3).

Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.

Accessibility tests

WCAG 2.2 AA · Passed

Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.

Button test checklist
Area Automated A person checks
General Passed Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme.
Zoom Passed At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping.
Keyboard Passed Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. (6 rows)
Screen reader Passed With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (5 items)

Approving. When the checklist passes on the checked run, record it with bun run a11y:approve -- button --by "Your name" and commit a11y/approvals.json with the rebuilt pages. All test status

Package

Consumers link one stylesheet, css/classic/index.css, which includes every element. The files below are where this element is defined.

Button package
Source css/classic/components/actions.css
css/classic/components/button-states.css
css/classic/components/commands.css
Specimens also use Box · Card · Chip · Icon · Table · Text · Toolbar
Sprites sprites/icons.svg · sprites/office-icons.svg
React Not yet available: the React binding has not started.

Latest updates

Button latest updates
Date Version Breaking Description
2026-10-20 0.1 Yes Button: the pressed disclosure. A button that shows and hides a region in place (the search toolbar's Filters) carries data-disclosure beside aria-expanded and aria-controls; open, it reads as pressed in on every screen: an info-200 fill, the selected border and an inset shade (--qf-shadow-pressed, dropped under the focus ring). __badge (the applied-filter count, sized by --qf-badge-text) now takes the button as its containing block wherever the button is (components/button-states.css). Breaking: the inbox-only .qf-search.qf-inbox .qf-button[data-selected] tint is gone; set data-disclosure on the Filters button instead, classic skin

All release notes