Button
Triggers actions, confirms decisions, and advances processes with clear hierarchy.
Design
Functional preview
Running with stable DS HTML and CSS, without a component JavaScript runtime.
Open the HTML/CSS/JS playground
Styles
Anatomy
2 Icon (optional) — 20px frame/icon on sm and 24px on md/lg.
3 Label (
.ds-button__label) — typography and color via Component tokens.4 Root padding — sm 8×8px, md 10×12px, lg 12×16px.
5 Label padding — additional 4px on the Label Frame for optical balance.
Sizes
| Size | Height | Font | Padding | Touch target |
|---|---|---|---|---|
Small (--sm) | 32px (component.button.height.sm) | component.button.label.font-size.sm | 8×8px (component.button.padding-y.sm × padding-x.sm) | 32px — meets WCAG 2.5.8 min (24px) |
Medium (--md) | 40px (component.button.height.md) | component.button.label.font-size.md | 10×12px (component.button.padding-y.md × padding-x.md) | 40px |
Large (--lg) | 48px (component.button.height.lg) | component.button.label.font-size.lg | 12×16px (component.button.padding-y.lg × padding-x.lg) | 48px — meets AAA 44px target |
States
States are handled via CSS pseudo-classes (:hover, :active, :focus-visible, [disabled]), not modifier classes.
| State | CSS trigger | Visual change | Token |
|---|---|---|---|
| Default | — | Primary fill | component.button.bg.brand.default |
| Hover | :hover | Darker fill | component.button.bg.brand.hover |
| Pressed | :active | Darkest fill | component.button.bg.brand.pressed |
| Focus | :focus-visible | 2px ring with 2px offset and focus-state colors | component.button.bg.brand.focus, component.button.content.color.brand.focus, component.button.focus-ring.* |
| Disabled | [disabled] | Muted bg + content, no pointer events | component.button.bg.brand.disabled, component.button.content.color.brand.disabled |
With icon
Icon only
Loading
Full width
Token mapping
Tokens consumed by the Brand variant. Other variants follow the same pattern using their respective color groups.
| Property | Token (Component) | CSS variable |
|---|---|---|
| height (sm) | component.button.height.sm | --ds-button-height-sm |
| height (md) | component.button.height.md | --ds-button-height-md |
| height (lg) | component.button.height.lg | --ds-button-height-lg |
| min-width (sm/md/lg) | component.button.min-width.{sm,md,lg} | --ds-button-min-width-* |
| padding-x (sm/md/lg) | component.button.padding-x.{sm,md,lg} | --ds-button-padding-x-* |
| padding-y (sm/md/lg) | component.button.padding-y.{sm,md,lg} | --ds-button-padding-y-* |
| gap (sm/md/lg) | component.button.gap.{sm,md,lg} | --ds-button-gap-* |
| radius / border-width | component.button.radius.default, component.button.border-width.default | --ds-button-radius-default, --ds-button-border-width-default |
| bg por estilo/estado | component.button.bg.{brand,toned,outline,ghost,success,danger}.* | --ds-button-bg-* |
| border color Outline | component.button.border-color.outline.* | --ds-button-border-color-outline-* |
| text/icon color | component.button.content.color.{style}.{state} | --ds-button-content-color-* |
| label typography | component.button.label.{font-size,line-height,font-weight,letter-spacing}.* | --ds-button-label-* |
| icon frame/size | component.button.icon.size.{sm,md,lg}, component.button.icon.stroke-width.{sm,md,lg} | --ds-button-icon-* |
| icon-only | component.button.icon-only-{width,padding}.{sm,md,lg} | --ds-button-icon-only-* |
| focus ring | component.button.focus-ring.radius.default, component.focus-ring.{width,color.default,color.success,color.error} | --ds-focus-ring-* + --ds-button-focus-ring-radius-default |
Toned variant
--ds-toned-background-default, --ds-toned-background-hover, --ds-toned-background-active) instead of opaque fills. This differs from Subtle/Muted patterns which use opaque background tokens. The translucent approach lets the Toned button adapt naturally to any surface color beneath it while maintaining a consistent brand tint.
Figma
The Figma component exposes the properties below in the panel. Visibility booleans sit immediately above the slot they control.
| Property | Type | Default | Options |
|---|---|---|---|
| Style | Variant | Brand | Brand, Toned, Outline, Ghost, Success, Danger |
| Size | Variant | Medium | Small (32px), Medium (40px), Large (48px) |
| State | Variant | Default | Default, Hover, Pressed, Focused, Disabled |
| Icon Only | Variant | false | Default (with label), Icon Only (square) |
| Loading | Boolean | false | true, false |
| Show Left Icon | Boolean | false | Toggles visibility for the left icon frame |
| Left Icon | Instance swap | Placeholder | Sets the left icon component |
| Show Right Icon | Boolean | false | Toggles visibility for the right icon frame |
| Right Icon | Instance swap | Placeholder | Sets the right icon component |
Usage
When to use
<a>) instead. Don't use buttons for inline text actions or when the visual weight would compete with the page's primary action.
| Style | Use for | Emphasis |
|---|---|---|
| Brand | Primary action on the page. One per section, ideally one per page. | Highest |
| Toned | Secondary actions that need visibility but shouldn't compete with Brand. | Medium |
| Outline | Secondary or tertiary actions. Pairs well next to a Brand button. | Medium |
| Ghost | Tertiary actions, toolbar actions, or close/dismiss buttons. | Low |
| Success | Confirming a positive outcome: approve, complete, publish. | Contextual |
| Danger | Destructive actions: delete, remove, revoke. Always requires confirmation. | Contextual |
Best practices
Content guidelines
| Rule | Example |
|---|---|
| Use verb + noun for clarity | "Save changes", "Add item", "Delete project" — not just "Submit" or "OK" |
| Use sentence case | "Save changes" — not "Save Changes" or "SAVE CHANGES" |
| Keep labels short (1–3 words) | "Export CSV" — not "Click here to export your data as CSV" |
| Be specific about destructive actions | "Delete account" — not "Delete" or "Remove" |
| Avoid generic labels | "Confirm order" — not "Click here" or "Yes" |
Icon-only buttons need aria-label | aria-label="Close dialog" — describes the action, not the icon |
Implementation
- Status
- Stable
- Distribution
- npm package
Installation
npm install ds-tispnpm add ds-tisyarn add ds-tisbun add ds-tisImports
import 'ds-tis/css'Markup
<button class="ds-button ds-button--brand ds-button--md" type="button">
<span class="ds-button__label">Continuar</span>
</button>Web implementation contract
CSS classes
| Class | Description |
|---|---|
ds-button | Base button class |
ds-button--brand | Filled with brand color |
ds-button--toned | Alpha-tinted brand background |
ds-button--outline | Neutral border, no brand fill |
ds-button--ghost | Brand text, no background or border |
ds-button--success | Filled with success/green color |
ds-button--danger | Filled with error/red color |
ds-button--sm | Small size (32px height) |
ds-button--md | Medium size (40px, default — can be omitted) |
ds-button--lg | Large size (48px height) |
ds-button--icon-only | Square button for icon-only use |
ds-button--full | Full-width button (100%) |
ds-button--loading | Loading state — hides label, shows spinner |
ds-button__label | Text label (child element) |
ds-button__spinner | Spinner container (child element, used with --loading) |
Accessibility
Keyboard interaction
| Key | Action |
|---|---|
Tab | Moves focus to the button (or past it if disabled) |
Enter | Activates the button |
Space | Activates the button |
Disabled buttons ([disabled]) are removed from tab order automatically by the browser. Loading buttons should also set aria-disabled="true" to prevent interaction while preserving tab order.
Accessibility
| WCAG criterion | Requirement | Status |
|---|---|---|
| 2.4.11 Focus Appearance (AA) | Focus ring 2px + 2px gap, contrast ≥ 3:1 against adjacent colors | ✓ |
| 2.5.8 Target Size min (AA) | Smallest size (32px) exceeds 24px minimum | ✓ |
| 2.5.5 Target Size (AAA) | Large size (48px) meets 44px touch target | ✓ |
| 1.4.3 Contrast (AA) | White text on Brand/Danger fill ≥ 4.5:1. Disabled exempt. | ✓ |
| 4.1.2 Name, Role, Value (A) | Use <button> element. Icon-only requires aria-label. | ✓ |
| 1.4.1 Use of Color (A) | Focused state has visible ring. Disabled has dimmed text + reduced opacity. | ✓ |
aria-label — required on icon-only buttons.aria-disabled="true" + aria-busy="true" — set on loading buttons.disabled — use the native attribute for truly disabled buttons (removed from tab order).
Output responsibility
Preserve native element semantics, accessible names, keyboard behavior, and visible focus.
Validation evidence
Covered by the stable Storybook, public runtime tests when applicable, keyboard scenarios, responsive checks, and Axe.