Button
Apply button-filled, button-outlined, or button-text to a <button> or <a> element.
<div class="flex flex-wrap gap-16"> <button class="button-filled">Filled</button> <button class="button-outlined">Outlined</button> <button class="button-text">Text</button></div>Variants
Section titled “Variants”Filled — button-filled
Section titled “Filled — button-filled”Primary actions. Use at most one per viewport.
Outlined — button-outlined
Section titled “Outlined — button-outlined”Secondary actions. Can appear multiple times on a page.
Text — button-text
Section titled “Text — button-text”Tertiary actions. Shows a forward arrow by default. Control direction with data-arrow:
<div class="flex-col items-start gap-8"> <button class="button-text">Default (forward)</button> <button class="button-text" data-arrow="back">Back</button> <button class="button-text" data-arrow="down">Down</button> <button class="button-text" data-arrow="up">Up</button> <button class="button-text" data-arrow="none">No arrow</button></div>Arrow direction automatically flips in RTL layouts.
Options
Section titled “Options”Color — data-color
Section titled “Color — data-color”Use data-color to change the button’s intent. Available values:
accent— extra prominence. Use only on plain backgrounds. Not supported on text buttons (maps back to default).destructive— delete, remove, or cancel actions.subtle— low-emphasis filled buttons with a gray surface and primary text. Filled only.
<div class="flex flex-wrap gap-16 items-center"> <button class="button-filled">Default</button> <button class="button-filled" data-color="accent">Accent</button> <button class="button-filled" data-color="destructive">Destructive</button> <button class="button-filled" data-color="subtle">Subtle</button></div>Size — data-size
Section titled “Size — data-size”Buttons default to 3rem (48px). Use data-size="medium" for 2.5rem (40px) or data-size="small" for 2rem (32px). Both are available on filled and outlined buttons. Small buttons maintain a 40px touch target via an invisible pseudo-element.
Text buttons do not support data-size — the attribute is ignored.
<div class="flex flex-wrap gap-16 items-center"> <button class="button-filled">Default</button> <button class="button-filled" data-size="medium">Medium</button> <button class="button-filled" data-size="small">Small</button></div>Use <Icon> from @volvo-cars/react-icons with color="currentColor" so the icon color follows the button. Wrap the label text in a <span> to preserve spacing. Set data-arrow="none" on text buttons to suppress the default arrow. Icons get automatic inline margin depending on whether they appear first or last.
import { Icon } from '@volvo-cars/react-icons';
export function ButtonIcons() { return ( <div className="flex flex-wrap gap-16 items-center"> <button className="button-filled" data-arrow="none" type="button"> <Icon icon="external-link" size={16} color="currentColor" /> <span>Open link</span> </button> <button className="button-outlined" data-arrow="none" type="button"> <span>Download</span> <Icon icon="download" size={16} color="currentColor" /> </button> <button className="button-text" data-arrow="none" type="button"> <Icon icon="heart" size={16} color="currentColor" /> <span>Favourite</span> </button> </div> );}Links as buttons
Section titled “Links as buttons”Apply the class directly to an <a> or your framework’s link component:
<div class="flex flex-wrap gap-16 items-center"> <a class="button-filled" href="#">Filled link</a> <a class="button-outlined" href="#">Outlined link</a> <a class="button-text" href="#">Text link</a></div>Grouping buttons
Section titled “Grouping buttons”Use the button-group class to align buttons horizontally. The group automatically handles spacing and responsive behavior.
<div class="button-group"> <button type="button" class="button-filled">Primary</button> <button type="button" class="button-filled" data-color="subtle"> Secondary </button></div>The group provides:
16pxgap between buttons- Automatic wrapping when buttons don’t fit
- Full-width buttons on viewports
< 768pxor containers< 30rem
Alignment
Section titled “Alignment”Use justify-* utility classes to position buttons on larger viewports.
<div class="flex flex-col gap-24"> <div class="button-group justify-start"> <button type="button" class="button-filled">Left</button> <button type="button" class="button-filled" data-color="subtle"> Aligned </button> </div> <div class="button-group justify-center"> <button type="button" class="button-filled">Center</button> <button type="button" class="button-filled" data-color="subtle"> Aligned </button> </div> <div class="button-group justify-end"> <button type="button" class="button-filled">Right</button> <button type="button" class="button-filled" data-color="subtle"> Aligned </button> </div></div>Button combinations
Section titled “Button combinations”Combine different variants for visual hierarchy.
<div class="button-group"> <button type="button" class="button-filled">Continue</button> <button type="button" class="button-text">Cancel</button></div><div class="button-group"> <button type="button" class="button-filled">Save</button> <button type="button" class="button-filled" data-color="subtle"> Preview </button> <button type="button" class="button-text">Cancel</button></div>States
Section titled “States”Loading
Section titled “Loading”Filled and outlined buttons support a loading indicator. Hide the label with invisible and add a <progress class="spinner">. The spinner is absolutely positioned and centered — keeping the label in the DOM preserves the button’s width.
<div class="flex flex-wrap gap-16 items-center"> <button class="button-filled" data-loading="true"> <span class="invisible">Loading</span> <progress class="spinner" aria-label="Loading"></progress> </button> <button class="button-outlined" data-loading="true"> <span class="invisible">Loading</span> <progress class="spinner" aria-label="Loading"></progress> </button></div>Disabled
Section titled “Disabled”Prefer aria-disabled="true" over the disabled attribute — it keeps the button focusable for screen reader users and keyboard navigation. Disabled buttons get reduced opacity automatically.
<div class="flex flex-wrap gap-16 items-center"> <button class="button-filled" aria-disabled="true">Filled</button> <button class="button-outlined" aria-disabled="true">Outlined</button> <button class="button-text" aria-disabled="true">Text</button></div>Avoid disabled buttons when possible — show an explanatory message instead. See Usability Pitfalls of Disabled Buttons.
Accessibility
Section titled “Accessibility”Key consumer responsibilities from the WCAG audit:
- Accessible name — every button needs visible text or
aria-label. Consistent labeling across the interface is your responsibility. aria-disabledoverdisabled— ensures the button remains in the tab order and is announced by screen readers.- Destructive actions — provide a confirmation step (dialog, etc.) before finalizing. The
data-color="destructive"styling alone is not sufficient. - Loading status — if you add a loading spinner, ensure the state change is announced to assistive technology (e.g., with
aria-label="Loading"on the<progress>). - Links styled as buttons —
<a>with a button class and a validhrefis fine. Do not use<a>withouthrefas a button; use<button>instead. - Color modes — use light-mode buttons on light backgrounds and dark-mode buttons on dark backgrounds. Never use accent or destructive colors on colored backgrounds or media.
SubmitButton (React)
Section titled “SubmitButton (React)”SubmitButton from @volvo-cars/react-forms is a form-aware button with type="submit" by default. It supports the same filled and outlined variants as the CSS button classes, plus built-in loading state management.
import { SubmitButton } from '@volvo-cars/react-forms';
export function SubmitButtonBasic() { return <SubmitButton type="button">Submit</SubmitButton>;}Loading state
Section titled “Loading state”Set loading to show a spinner and prevent resubmission. Always provide loadingLabel for screen readers.
import { SubmitButton } from '@volvo-cars/react-forms';
export function SubmitButtonLoading() { return ( <div className="flex flex-wrap gap-16"> <SubmitButton type="button" loading loadingLabel="Submitting"> Submit </SubmitButton> <SubmitButton type="button" variant="outlined" loading loadingLabel="Submitting" > Submit </SubmitButton> </div> );}Prefer handling the form onSubmit event rather than onClick on the submit button.
SubmitButton Props
Section titled “SubmitButton Props”| Prop | Type | Required | Default |
|---|---|---|---|
hidden | boolean | - | - |
id | string | - | - |
title | string | - | - |
dir | string | - | - |
lang | string | - | - |
slot | string | - | - |
translate | "yes" | "no" | - | - |
className | string | - | - |
style | CSSProperties | - | - |
tabIndex | number | - | - |
onPointerDown | (PointerEventHandler<Element> & PointerEventHan... | - | - |
onPointerEnter | (PointerEventHandler<Element> & PointerEventHan... | - | - |
onPointerLeave | (PointerEventHandler<Element> & PointerEventHan... | - | - |
onPointerMove | (PointerEventHandler<Element> & PointerEventHan... | - | - |
onPointerUp | (PointerEventHandler<Element> & PointerEventHan... | - | - |
variant | "filled" | "outlined" | - | filled |
| Which design variant to render. | |||
color | "accent" | "destructive" | "neutral" | "subtle" | - | neutral |
| The `accent` color can be used to add extra prominence to the button. Use the `destructive` color for actions that require caution. | |||
size | "medium" | "small" | - | medium |
| Use the small button in cards and other constrained containers. | |||
form | string | - | - |
| Id of a form element that this button should be associated with. Defaults to the containing form element. | |||
formAction | string | - | - |
| The URL that processes the information submitted by the button, overriding the `action` attribute of the button's form. | |||
formMethod | "post" | "get" | - | - |
| Specifies the HTTP method used to submit the form, overriding the `method` attribute of the button's form. | |||
name | string | - | - |
| The name of the button, submitted as a pair with the button's value as part of the form data. | |||
value | string | - | - |
| The value associated with the button's `name` in the form data when the form is submitted using this button. | |||
children | ReactNode | ✓ | - |
| The button label. | |||
disabled | boolean | - | - |
| Disables the button. Avoid using disabled buttons whenever possible - instead show error messages explaining the next steps the user should take. | |||
loading | boolean | - | - |
| Renders a spinner in place of the button label and prevents activating the button again. | |||
loadingLabel | string | - | - |
| Label for screen readers while showing the loading spinner. | |||
type | "submit" | "button" | - | submit |
onClick | MouseEventHandler<HTMLButtonElement> | - | - |
| Called when the button is clicked. Prefer to place the button within a `<form> and use the `onSubmit` event on the form instead. | |||
onFocus | FocusEventHandler<HTMLButtonElement> | - | - |
onBlur | FocusEventHandler<HTMLButtonElement> | - | - |
onKeyDown | KeyboardEventHandler<HTMLButtonElement> | - | - |
onKeyUp | KeyboardEventHandler<HTMLButtonElement> | - | - |