Documentation
Actions
Button
The main way to act on a page. One solid button per area for the primary action; subtle and ghost for the rest.
components/button.css
Variants
data-variant sets the weight. Use solid (the default) for the one main action of a view, subtle for secondary actions next to it and ghost for quiet actions in toolbars, lists and cards.
<div class="dsgn-cluster">
<button class="dsgn-button" type="button">Save</button>
<button class="dsgn-button" data-variant="subtle" type="button">Preview</button>
<button class="dsgn-button" data-variant="ghost" type="button">Discard draft</button>
</div>Intents
data-intent sets the colour. Accent (the default) follows your brand, neutral is grey and suits secondary actions, danger is for actions that delete or cannot be undone.
<div class="dsgn-cluster">
<button class="dsgn-button" type="button">Publish</button>
<button class="dsgn-button" data-variant="subtle" data-intent="neutral" type="button">Cancel</button>
<button class="dsgn-button" data-intent="danger" type="button">Delete project</button>
</div>With an icon
Put a dsgn-icon inside the button. A leading icon needs nothing else. An icon after the text takes data-end: CSS cannot see where the bare text is, so the attribute tells the button which side gets the tighter padding.
<div class="dsgn-cluster">
<button class="dsgn-button" data-variant="ghost" data-intent="danger" type="button">
<span class="dsgn-icon" aria-hidden="true">delete</span>Delete
</button>
<button class="dsgn-button" type="button">
Next<span class="dsgn-icon" data-end aria-hidden="true">arrow_forward</span>
</button>
</div>Icon button
dsgn-icon-button holds only an icon, so it needs an aria-label that names the action. It is always a square of the same height as a button next to it.
<div class="dsgn-cluster">
<button class="dsgn-icon-button" data-variant="ghost" data-intent="neutral" type="button" aria-label="Settings">
<span class="dsgn-icon" aria-hidden="true">settings</span>
</button>
<button class="dsgn-icon-button" data-variant="subtle" type="button" aria-label="Add a row">
<span class="dsgn-icon" aria-hidden="true">add</span>
</button>
</div>Toggle
Subtle and ghost buttons can be toggles with aria-pressed. When pressed, the background is tinted and the icon fills. Solid buttons are never toggles. Update aria-pressed from your script when the state changes.
<div class="dsgn-cluster">
<button class="dsgn-button" data-variant="ghost" data-intent="neutral" type="button" aria-pressed="true">
<span class="dsgn-icon" aria-hidden="true">star</span>Starred
</button>
<button class="dsgn-icon-button" data-variant="ghost" data-intent="neutral" type="button" aria-pressed="false" aria-label="Mute">
<span class="dsgn-icon" aria-hidden="true">notifications_off</span>
</button>
</div>Disabled
Use disabled on a <button>. Use aria-disabled="true" when the button must stay focusable, for example to show a tooltip that explains why it is not available yet, and ignore clicks in your script.
<div class="dsgn-cluster">
<button class="dsgn-button" type="button" disabled>Save</button>
<button class="dsgn-button" data-variant="subtle" data-intent="neutral" type="button" aria-disabled="true">Export</button>
</div>Links
A link that looks like a button is still a link: use <a href> when it goes somewhere, <button> when it does something on the page. Both take the same classes and attributes.
<a class="dsgn-button" data-variant="subtle" data-intent="neutral" href="#attributes">See the attributes</a>Size
A button has no height of its own. It is the line height plus twice the vertical padding of the current density, so it follows data-density on the page or on any section: 28 px in S, 36 px in M and 44 px in L. Every control in a row lines up.
Accessibility
- Every button needs a visible label or, for an icon button, an
aria-label. Icons inside carryaria-hidden="true". - Give
<button>atype: inside a form, a button without one submits the form. - The focus ring meets WCAG 2.2 focus appearance and has 3:1 contrast against every surface in light and dark.
- Even in density S a button is 28 px tall, above the 24 × 24 px minimum target size. Touch screens without a density set switch to L with 44 px controls.
Usage
Also works on <a href>. An icon after the text takes data-end (CSS cannot see where the bare text is, so this tells the button which side gets the tighter icon padding; without it the icon is treated as leading).
Height is never set: line-height + 2 × pad-y. An icon button uses pad-y on all sides and its icon is 1 line-height wide, so it is always a square of the same height. Uses tokens only — never --dsgn-unit directly (enforced by the build).
Attributes
| Attribute | Values and notes |
|---|---|
data-variant | solid (default) | subtle | ghost |
data-intent | accent (default) | neutral | danger |
aria-pressed | true|false → toggle, subtle and ghost only; selected = tinted background + filled icon. Solid buttons are never toggles. |
disabled / aria-disabled="true" | — |
Details
- Classes
.dsgn-button.dsgn-icon-button.dsgn-icon- Tokens used
--dsgn-accent-solid--dsgn-accent-solid-hover--dsgn-accent-solid-pressed--dsgn-accent-solid-text--dsgn-accent-subtle--dsgn-accent-subtle-hover--dsgn-accent-subtle-pressed--dsgn-accent-text--dsgn-control-font-size--dsgn-control-gap--dsgn-control-line-height--dsgn-control-pad-x--dsgn-control-pad-y--dsgn-danger-solid--dsgn-danger-solid-hover--dsgn-danger-solid-pressed--dsgn-danger-solid-text--dsgn-danger-subtle--dsgn-danger-subtle-hover--dsgn-danger-subtle-pressed--dsgn-danger-text--dsgn-duration-fast--dsgn-ease--dsgn-focus--dsgn-focus-danger--dsgn-focus-offset--dsgn-focus-width--dsgn-font-sans--dsgn-font-weight-medium--dsgn-neutral-solid--dsgn-neutral-solid-hover--dsgn-neutral-solid-pressed--dsgn-neutral-solid-text--dsgn-neutral-subtle--dsgn-neutral-subtle-hover--dsgn-neutral-subtle-pressed--dsgn-neutral-text--dsgn-radius-m--dsgn-shadow-control--dsgn-space-6--dsgn-text-disabled- In Figma
- Button · Icon Button
- Import alone
dsgn.systems/components/button.css