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 / SOUTHLEFT
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.

CODE AHEADSouthleft Design SystemFIGMA SET · Button

The component changed since the last confirmed sync — the Figma set is behind.

DECIDED BY
CONTRACT DIFF
FIGMA SIDE
OBSERVED
LAST CONFIRMED SYNC
2026-08-28
PUBLIC SURFACE
16 ATTR · 3 SLOT · 0 EVENT · 1 PART

The component’s public surface — attributes and their value sets, slots, events, CSS parts and custom properties, from the manifest — compared against the Figma set’s property definitions.

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