Interaction states
Some utility classes can be applied conditionally by adding a modifier prefix to the beginning of the class name, describing the condition you want to target. Only the combinations in Supported utility classes are available in @volvo-cars/css. Modifiers based on viewport size are described in Responsive design.
Interaction states
Section titled “Interaction states”Commonly used interaction states such as hover and active can get conditional styles depending on their state using the modifier prefixes hover: and active: – for a few select styles. For example, to set the border colour of an element when it is hovered, use the hover:border-primary class.
Foreground colors generally stay the same on hover. hover:text-primary is available for cases such as links in groups with selected states.
focus:border-accent-blue changes the border color when the element matches :focus-visible. It is the only focus: utility. The standard keyboard focus outline still applies; do not override it. Use semantic interactive elements, or tabindex="0" when a custom interactive element needs to enter the keyboard tab order.
Hover utilities require the hover stylesheet, included by @volvo-cars/css/styles_all-media.css. They apply only on devices that support hover.
Element states
Section titled “Element states”Design system styles for states such as selected, checked, active have similar modifiers, allowing you to conditionally apply styles to elements depending on their state.
States are mapped to standard HTML attributes and aria-attributes. Using standard attributes instead of toggling class names ensures that the visible behaviour and accessibility attributes stay in sync, giving you a single source of truth for the state of your components.
To style an element based on the state of an ancestor, some common interactive elements have ancestor modifiers. For example, use link-current:border-primary on a decorative child of a current link. checked: and selected: match ARIA attributes; they do not match native :checked or an <option>’s selected attribute.
All states
Section titled “All states”| Modifier | Applies |
|---|---|
active: | To the element while it matches :active |
hover: | To the hovered element, on devices with hover support |
focus: | To the element when it matches :focus-visible |
button-hover: | When an ancestor <button> is hovered |
link-hover: | When an ancestor <a> is hovered |
current: | To an element with aria-current="true", "page", or "step" |
link-current: | When an ancestor <a> has aria-current="true", "page", or "step" |
checked: | To an element with aria-checked="true" |
checkbox-checked: | When an ancestor has role="checkbox" and aria-checked="true" |
radio-checked: | When an ancestor has role="radio" and aria-checked="true" |
expanded: | To an element with aria-expanded="true" |
button-expanded: | When an ancestor <button> has aria-expanded="true" |
selected: | To an element with aria-selected="true" |
tab-selected: | When an ancestor has role="tab" and aria-selected="true" |
open: | To the element with an open attribute |
details-open: | To a direct child of details[open], or a descendant of its direct-child <summary> |
odd: | To an element that matches :nth-child(odd) |
empty: | To an element that matches :empty |
Supported utility classes
Section titled “Supported utility classes”Combine each prefix below with only the classes in the same row. For example, the checked: row permits checked:border-ring-2. Prefixes are not freely interchangeable or stackable with breakpoint prefixes.
Color utilities
Section titled “Color utilities”The Colors reference lists each complete class name and its token.
| Prefix | Available color utilities |
|---|---|
hover: | bg-primary-subtle, text-primary, border-primary |
focus: | border-accent-blue |
current:, checked:, selected: | bg-surface-neutral, text-primary, text-inverted, border-primary, border-accent-blue |
selected:, expanded: | bg-accent-blue-medium |
expanded: | border-primary, border-accent-blue |
link-current:, radio-checked:, checkbox-checked:, tab-selected:, button-expanded: | border-primary, border-accent-blue |
open: | border-primary |
odd: | bg-secondary |
hover:bg-primary-subtle is suppressed while aria-selected="true". bg-primary-subtle and bg-accent-blue-medium have no unprefixed classes.
Border widths and rings
Section titled “Border widths and rings”| Prefix | Available utilities |
|---|---|
checked:, selected: | border, border-2, border-ring, border-ring-2 |
link-current:, radio-checked:, checkbox-checked:, tab-selected: | border, border-2 |
Widths and rings use the current border color. See Borders for their layout behavior. Directional widths such as border-b have no state variants.
Transforms and visibility
Section titled “Transforms and visibility”| Prefix | Available utilities |
|---|---|
active: | scale-pressed |
hover:, button-hover:, link-hover: | scale-zoomed |
button-expanded:, details-open: | rotate-180 |
empty: | hidden, invisible |
See Transitions & transforms for motion examples and Display for responsive visibility variants.
Pointer and selection utilities
Section titled “Pointer and selection utilities”These classes apply directly, with no state or responsive prefixes.
| Class | Effect |
|---|---|
pointer-events-none | pointer-events: none |
pointer-events-auto | pointer-events: auto |
cursor-pointer | Pointer cursor |
cursor-auto | Browser-selected cursor |
select-none | Prevent text selection (user-select: none) |
pointer-events-none controls pointer targeting; it does not disable keyboard interaction. Use the control’s disabled state when disabling an action.
Examples
Section titled “Examples”For example, to set the border colour of an element when it is selected, use the selected:border-primary class and toggle the selected state using the aria-selected attribute. Click the examples below to see how the styles change with the attribute value.
Show code
<h2 class="font-medium" id="label">Select an option</h2><div class="flex gap-8 mt-16" role="radiogroup" aria-labelledby="label"> <div role="radio" class="border px-16 py-8 transition-colors checked:border-accent-blue checked:border-2 hover:border-primary" aria-checked="true" tabindex="0" > Option 1 </div> <div role="radio" class="border px-16 py-8 transition-colors checked:border-accent-blue checked:border-2 hover:border-primary" aria-checked="false" tabindex="-1" > Option 2 </div></div>Show code
<div role="tablist" class="flex gap-16"> <button role="tab" class="border-b-2 border-transparent selected:border-accent-blue p-8" aria-selected="true" > Tab 1 </button> <button role="tab" class="border-b-2 border-transparent selected:border-accent-blue p-8" aria-selected="false" > Tab 2 </button></div>CSS Examples
Section titled “CSS Examples”When creating your own utility classes or component styles you can use the same approach to apply styles based on the state of the element. If no native HTML or aria attribute is available you can use data- attributes.
.nav-link[aria-current='page'] {}.nav-button[disabled] {}.nav-button[data-size='large'] {}<> <a href="#" className="nav-link" aria-current={isActive ? 'page' : undefined}> Link </a> <button className="nav-button" disabled={isDisabled} data-size={size}> Button </button></>.nav-link.nav-link--is-active {}.nav-button.nav-button--is-disabled {}.nav-button.nav-button--large {}<> <a href="#" className={`nav-link${isActive ? ' nav-link--is-active' : ''}`}> Link </a> <button className={[ 'nav-button', isDisabled && 'nav-button--is-disabled', size && `nav-button--${size}`, ].filter(Boolean)} > Button </button></>