CSS architecture
With @volvo-cars/css you get access to design system styles — typography, spacing, colors, and layout — without writing custom CSS or CSS-in-JS, in a way that is performant for end users.
The design system styles are unopinionated and work alongside various CSS methodologies and frameworks, including plain CSS, BEM, Tailwind, or CSS-in-JS. The approach we recommend — balancing design system adherence, resilience, and performance — is described below.
The three layers
Section titled “The three layers”The CSS architecture is built on three complementary layers. Each serves a distinct purpose, and together they cover the full range of styling needs.
1. Global styles
Section titled “1. Global styles”Global styles are applied by default to common HTML elements by @volvo-cars/css, much like a browser default stylesheet adapted for the design system. This means you can write plain HTML and get design system compliant output without adding any classes.
These styles have low specificity and are easy to override, getting out of your way as soon as you need something custom.
2. Utility classes
Section titled “2. Utility classes”A utility class is a CSS class that does one job well — applying a single CSS property or a concise group of related properties.
@volvo-cars/css provides utility classes for design tokens and common styling needs. The intention isn’t to be exhaustive, but to ensure that styles used on most pages are available in a small, cacheable stylesheet shared across components and pages.
<div class="flex gap-16 bg-secondary p-24 rounded"> <p class="font-medium text-primary">A simple card layout</p></div>What makes a good utility class?
- Applies a single CSS property or a small group of related properties.
- Is generic — can be applied to any element.
- Has an explicit, precise name —
hover:border-primarynotnav-item.
When a utility is missing, create a utilities.css file in your application. If a class sees wide usage across teams, it may be promoted to @volvo-cars/css.
.app-card-shadow { box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);}How this differs from Tailwind: Tailwind encourages utility classes for all styles, including one-off arbitrary values like top-[117px]. While convenient, this makes it easy to accumulate inconsistent values. Using a design system is about consistency — so we encourage dropping down to component-scoped CSS for one-off styles rather than inventing arbitrary utility values.
3. Component-scoped CSS
Section titled “3. Component-scoped CSS”For styles not covered by global styles or utility classes, use component-scoped CSS. In React, the most common approach is CSS Modules.
CSS Modules ensure styles are scoped to a single component with no class name collisions. They are also code-split, so only the CSS for components actually rendered on a page is loaded.
@import url('@volvo-cars/css/breakpoints.css');
.hero { aspect-ratio: 2 / 3;}
@media (--v-from-md) { .hero { aspect-ratio: 1 / 1; }}import styles from './hero.module.css';
export function Hero() { return ( <section className={`${styles.hero} bg-always-black`} data-color-mode="dark" > <div className="container stack-text"> <h1 className="font-medium">Meet the new fully electric SUV</h1> <p>...</p> </div> </section> );}For an even higher degree of encapsulation — such as embedding a component in third-party applications — use the Shadow DOM.
Recommendations
Section titled “Recommendations”Keep it simple
Section titled “Keep it simple”We’re used to following the axiom Don’t Repeat Yourself. While generally good practice, it sometimes comes at the cost of simplicity. Some code repetition is OK. Not creating reusable components for everything keeps things simple and flexible.
When a styling change is needed, it’s easily achieved by adding, changing, or removing utility classes directly on the HTML — rather than extending a component with props to achieve different variants.
The cost of complexity is often greater than the cost of repetition.
Composition
Section titled “Composition”Create components in your application by composing utility classes, optionally combined with component-scoped CSS:
function TileCard({ children }) { return <div className="bg-secondary py-32 px-24">{children}</div>;}You don’t need a React component for everything. When a component’s implementation is trivial, the cost of added indirection and DOM nesting may outweigh the benefits. Both of these are valid:
<!-- Utility classes directly in markup --><div class="button-group bg-secondary py-32 px-24"> <button class="button-filled">Button 1</button> <button class="button-filled">Button 2</button></div>// Composed React components<TileCard> <ButtonGroup> <button className="button-filled">Button 1</button> <button className="button-filled">Button 2</button> </ButtonGroup></TileCard>Further reading
Section titled “Further reading”- CSS Utility Classes and “Separation of Concerns” — Adam Wathan
- Layout patterns — web.dev
- Defensive CSS