COMPONENTS / ATOMS / al-button← ALL COMPONENTS

Button

<al-button><ALButton> REACTbetaOPEN IN FIGMA ↗

Reference generated from the custom elements manifest — 16 properties, 3 slots, 0 events.

PLAYGROUND12 CONTROLS
PREVIEW / al-button / ALTITUDE
Loading preview
Label
type
variant
size
target
FLAGS
ICON SLOTS
CODE — HTML / REACT

Guidance

AUTHORED · CITED TO SOURCE

The system's action control. It renders a real <button> — or an <a role="button"> when href is set — so the element the user actually gets, and the keyboard behaviour that comes with it, follows the props rather than the styling. Everything else about a button is ranking: variant says how much weight this action carries against the others on screen.

WHEN TO USE

  • The control performs an action in the current page: submits a form, opens a dialog, applies a filter, saves a draft.
  • The action has to participate in a form. type="submit" and type="reset" are routed through the shared FormController, so a button inside a <form> behaves like a native one even across the shadow boundary.
  • A dense toolbar needs an icon-only affordance: set hideText, supply label, and put the glyph in the before slot.
  • You need to rank actions visually — primary, secondary, tertiary, neutral and bare are the whole ladder, and there is no sixth treatment.

WHEN NOT TO USE

  • The control only navigates, and should read as text inside a sentence or a list of destinations. href does swap in an <a>, but it keeps button styling and role="button" — use it only when the navigation is meant to look and be announced like a button.USE LINK →
  • You are putting several buttons side by side and what you actually want is control over their spacing. There is no button-group component: it was removed precisely because it owned arrangement and nothing else.USE LAYOUT →
  • The control flips a state that stays visible after the click — a filter pill, a view switch, a formatting toggle. That is selection, not invocation, and it wants a component that owns the selected state.USE TOGGLE-BUTTON →
  • The trigger opens a list of navigation destinations. Let the menu own its own trigger relationship rather than wiring a bare button to a floating list by hand.USE MENU →

DO

  • For an icon-only button, set hideText, supply label, and place the icon in the before slot. That exact pattern is documented on the component with a worked example, and it is the only combination that produces one accessible name.
  • Gate the action in your own click handler when the button is disabled. The component renders aria-disabled but not the native disabled attribute, and its click handler has no disabled branch, so a disabled submit button still submits.
  • Drive isExpanded from the open state of whatever the button controls, so the control and the thing it discloses never disagree.
  • Reserve variant="primary" for the one action you most want taken on a screen. A strongest-emphasis treatment used for merely important actions stops ranking anything. A destructive confirm is a primary button with destructive copy — status lives on al-badge and al-alert, not on this axis.

DON’T

  • Don't combine hideText with visible slot text. The text node is only visually hidden — it stays in the layout and in the accessibility tree, so it duplicates label into a redundant accessible name.
  • Don't rely on the glyph alone for meaning. With hideText set and no label, the button ships with no accessible name at all.
  • Don't assume ariaControls reaches the DOM on a plain button. It is rendered on the href branch only; the <button> branch emits aria-label, aria-disabled, aria-pressed and aria-expanded and stops there.
  • Don't use variant="bare" as a way to draw a link. A bare button is still announced as a button, and still activates on Space.
  • Don't leave a disabled button as the only visible path forward without saying somewhere what would enable it. A disabled control is not an error message.

ACCESSIBILITY IN PRACTICE

  • label becomes aria-label on both rendered branches, and it is the only accessible name an icon-only button has.
  • isPressed accepts true, false or mixed and passes straight through to aria-pressedmixed is the tri-state case, such as a select-all whose children are partly checked. It is state you must keep in sync, not a style hook.
  • isDisabled renders aria-disabled only. The control therefore keeps its tab stop and can still be reached, focused and announced as unavailable — which is the accessible behaviour — but it also remains clickable, so the guard has to live in your handler.
  • Setting href produces <a role="button">. Assistive technology will announce a button while the browser still treats it as a link for middle-click, copy-link and Space-vs-Enter purposes.

CONTENT GUIDELINES

  • Label the action, not the mechanism — a verb plus the object it acts on: "Save changes", "Delete project", "Invite teammate".
  • Let the surrounding UI carry the object when it already does. A dialog titled "Delete project" can end in "Delete"; a button floating in a toolbar cannot.
  • Write an icon-only button's label as the whole sentence a screen-reader user hears, not as a tooltip fragment: "Open actions menu", not "Actions".

WHERE THIS CAME FROM

  • libs/al-web-components/components/button/button.tsThe icon-only recipe, its worked example and its explicit do-not, written on the class and previously visible to nobody outside the source file.
  • libs/al-web-components/components/button/button.ts`href` swapping the rendered element from `<button>` to `<a>` — the reason this page distinguishes navigation from action.
  • libs/al-web-components/components/button/button.tsThe disabled treatment is advisory only: `aria-disabled` is emitted, the native `disabled` attribute is not, and the click handler does not check it.
  • libs/al-web-components/components/button/button.tsThe single `aria-controls` render site, inside the link branch — the basis for the do-not about disclosure on a plain button.
  • AGENTS.mdThe rule that removed `al-button-group` and sends a row of buttons to `al-layout` instead.

Each line above names a file and a literal string that must still appear in it.pnpm gate:guidance re-reads them from this built page, so guidance that has stopped being true fails a check rather than misleading you.

BOTH CHANGEDAltitude Design SystemFIGMA SET · Button7 PROPERTY DIFFSFROM TRACKED PROJECTION

Code and Figma each changed since the last confirmed sync. Neither side should be overwritten blindly.

DECIDED BY
SOURCE HASH
FIGMA SIDE
OBSERVED
LAST CONFIRMED SYNC
2026-08-22
PUBLIC SURFACE
16 ATTR · 3 SLOT · 0 EVENT · 1 PART

A sha1 of every .ts/.scss byte in the component directory, compared against the hash stored at the last sync. It cannot say what changed, and a comment edit moves it. Re-stamping this component records a contract digest instead.

PROPERTYDISAGREEMENTCODE / CANVAS
Show Text[prop] missing-in-code— / Show Text
theme/icon/md[token-binding] missing-in-code— / theme/icon/md
theme/space/md[token-binding] missing-in-canvas--al-theme-space-md / —
theme/space/sm[token-binding] missing-in-canvas--al-theme-space-sm / —
typography/font-size/12[token-binding] missing-in-canvas--al-font-size-12 / —
typography/font-size/14[token-binding] missing-in-canvas--al-font-size-14 / —
typography/font-size/16[token-binding] missing-in-canvas--al-font-size-16 / —
CHECKEVIDENCEDETAILRESULT
Default stateAXEbutton--BarePASS
Advanced statesAXE31 further storiesPASS
Screen readerMANUALNo screen-reader pass has been recorded for this component.NOT RECORDED
Keyboard navigationINTERACTION TESTSNo interaction tests and no manual keyboard pass.NOT RECORDED

MEASURED 2026-09-08 · 32 STORIES · AXE WCAG2A + WCAG2AA + WCAG21A + WCAG21AA + WCAG22AA

COLOR CONTRAST0 RULES FAILING

The CI gate disables color-contrast globally (libs/al-web-components/story-fixture/src/main.ts), so its result never reaches a test run. It is enabled for this measurement, and reported here whichever way it comes out.

NO CONTRAST VIOLATIONS ACROSS THIS COMPONENT’S 32 STORIES.

import '@southleft/al-web-components/components/button';
import { ALButton } from '@southleft/al-react';
PROPTYPEDEFAULT
type'button' | 'submit' | 'reset'Type of button
variant'primary' | 'neutral' | 'bare' | 'secondary' | 'tertiary'Style variant — an EMPHASIS axis, strongest to weakest. Status is not on this axis: `al-badge` and `al-alert` carry info/success/warning/danger. - **primary** renders the strongest emphasis, and is what an omitted `variant` renders — it is named here so the axis is expressible in code the way `Primary` is on the Figma canvas, rather than living as the absence of a value - **secondary** renders the secondary colour role's own fill - **tertiary** renders an outlined button on a transparent ground - **neutral** renders a low-emphasis filled button - **bare** renders the button with no fill and no border
size'sm' | 'md' | 'lg'Size variant - **sm** renders a 32px control with 13px text - **md** renders the 40px control with 14px text, and is what an omitted `size` renders — named for the same reason `primary` is, so the axis is expressible rather than living as the absence of a value - **lg** renders a 48px control with 15px text
isPillbooleanPill shape - **true** renders the button with a fully rounded (pill) radius - **false** renders the button with the default action radius
target'_blank' | '_self' | '_parent' | '_top'Target attribute for a link (i.e. set to _blank to open in new tab) - **_blank** yields a link that opens in a new tab - **_self** yields a link that loads the URL into the same browsing context as the current one. This is the default behavior - **_parent** yields a link that loads the URL into the parent browsing context of the current one. If there is no parent, this behaves the same way as _self - **_top** yields a link that loads the URL into the top-level browsing context. If there is no parent, this behaves the same way as _self.
hrefstringURL if this is an <a> element - this swaps <button> for <a>
namestringIndicates the name when submitted with form data.
labelstringIndicates the aria label to apply to the button.
valuestringIndicates the value associated with the name when submitted with form data.
isPressedboolean | 'mixed'Indicates this button is a toggle button and whether it is pressed or not.
isDisabledbooleanDisabled attribute
isAriaDisabledbooleanAria-disabled attribute - **true** marks the button disabled to assistive tech while leaving it focusable and clickable — use when the button must stay reachable to explain why it is unavailable - prefer `isDisabled` (native `disabled`) unless focusability is required
isExpandedbooleanIndicates this button is a toggle button and whether it is pressed or not.
hideTextbooleanVisually hide button text (but text is still accessible to assistive technology) 1. Use this for icon-only buttons for accessibility
fullWidthbooleanFull width button
ariaControlsstringaria-controls attribute on the button 1. Used for items like the buttons attached drawers
SLOTDESCRIPTION
(default)The button text content. Omit when rendering an icon-only button (set `hideText` + `label` instead).
beforeContent to display before the button text, typically an icon.
afterContent to display after the button text, typically an icon. Icon-only button pattern: set `hideText` + provide an accessible name via `label`, then place the icon in the `before` slot. Do NOT also include a visible text node — the slot's default text content is hidden but still present in the layout, and a duplicate label produces a redundant accessible name. Example: <al-button hideText label="Open actions menu" ariaControls="menu-id"> <al-icon-dots-vertical slot="before" iconTitle="Actions"></al-icon-dots-vertical> </al-button>

THIS COMPONENT DECLARES NO EVENTS IN THE MANIFEST.

PARTDESCRIPTION
button
METHODDESCRIPTION
handleOnClick()Handle click events 1. When we click on button which has type=submit trigger requestSubmit on closest form element in order to invoke submit event on form element

SOURCE — components/button/button.ts · Atoms/Button