Skip to content

Layout

Edit on GitHub

The layout system is fluid by nature. Pages take advantage of the full viewport width, allowing content to breathe and scale naturally across breakpoints. A shift from earlier, more fixed principles where wider page margins left less room for content.

It is also gridless by principle. Instead of hard-coded columns, layouts are built around fluid content widths, responsive spacing, and component-level constraints. This keeps the foundation flexible while still producing consistent, well-aligned interfaces.

Every page is made up of a few structural parts:

  1. Content area: the main region where primary page content lives.
  2. Sidebar area: an optional secondary region alongside the content, typically used for navigation, filters, or supporting content.
  3. Page margins: fluid space between the content and the viewport edges. Controlled by --v-space-pagemargin (16 px on small screens, scaling up to 64 px on large screens).

The pagelayout class goes on the <main> element. It sets up a CSS Grid that handles page margins, sidebar placement, and bleed automatically. Every page should include it.

<main class="pagelayout">
<!-- page content goes here -->
</main>

The page layout uses slots to assign structural roles to direct children of the pagelayout element.

Use slot="page" for sections that take all available space inside the page margins.

Default page layout with content area and page margins

<main class="pagelayout">
<div slot="page">Content area</div>
</main>

Bleed removes the page margins, letting content expand to the full viewport width. Add a data-bleed attribute to any slotted child:

AttributeEffect
data-bleedBleeds both sides
data-bleed-startBleeds only the start side (left in LTR)
data-bleed-endBleeds only the end side (right in LTR)

Each attribute also accepts responsive values to limit the bleed to specific viewport ranges:

ValueMeaning
"true" (default)Always bleeds
"until-md"Bleeds below medium breakpoint only
"until-lg"Bleeds below large breakpoint only
"md"Bleeds from medium breakpoint and up
"lg"Bleeds from large breakpoint and up
<main class="pagelayout">
<div slot="page" data-bleed>Full bleed: extends to viewport edges</div>
</main>

Add slot="main" and slot="sidebar" (or slot="sidebar-narrow") to create a two-column layout. The sidebar position is determined by DOM order. Place it before main for a start-side sidebar, or after for end-side.

SlotWidthDescription
slot="main"Fluid (fills remaining space)Primary content area
slot="sidebar"368–464 px (fluid)Wide sidebar (--v-size-sidebar-wide)
slot="sidebar-narrow"240–304 px (fluid)Narrow sidebar (--v-size-sidebar-narrow)

On screens below the large breakpoint, the sidebar stacks with the main content. If you want to hide it on small screens instead (for example, relocating its content to a sheet), use conditional rendering in your application code.

Both slot="main" and slot="sidebar" support the same data-bleed attributes described above.

Sidebar page layout with main content and sidebar area

<main class="pagelayout">
<div slot="main">Main content</div>
<div slot="sidebar">Sidebar</div>
</main>

Swap the DOM order to place the sidebar before the main content:

<main class="pagelayout">
<div slot="sidebar">Sidebar</div>
<div slot="main">Main content</div>
</main>

Containers are centered, max-width constrained content areas. Use them inside pagelayout to limit content width while keeping page margins consistent.

ClassMax width token
container-xs--v-size-grid-xs
container-sm--v-size-grid-sm
container-md--v-size-grid-md
container-lg--v-size-grid-lg
container / container-xl--v-size-grid-xl
container-max160 rem

Containers also support data-bleed to go full-width at smaller viewports. Useful for content that should be constrained on desktop but edge-to-edge on mobile:

<main class="pagelayout">
<div class="container-lg" data-bleed="until-lg">
Constrained on large screens, full width on smaller ones
</div>
</main>

Use max-w-prose to constrain text content to a comfortable reading width (--v-size-contentmax).

<main class="pagelayout">
<div class="container-xl">Full-width container</div>
<div class="container-md">Medium container</div>
<div class="container-sm">Small container</div>
</main>

Simple column grid utilities for equal-width layouts. Use gap-x-gutter for consistent column spacing.

ClassColumns
gridGrid container without setting columns
grid-cols-11 column
grid-cols-22 equal columns
grid-cols-33 equal columns
grid-cols-44 equal columns

Supports responsive prefixes: md:grid-cols-2, lg:grid-cols-3, xl:grid-cols-4. Use grid-cols-1 to drop back to a single column at a breakpoint, such as grid-cols-1 md:grid-cols-3.

<div class="grid-cols-2 gap-x-gutter gap-y-24">
<div>Column</div>
<div>Column</div>
</div>
<div class="grid-cols-3 gap-x-gutter gap-y-24">
<div>Column</div>
<div>Column</div>
<div>Column</div>
</div>

col-span-* makes a grid item span several columns. These classes apply to grid items, so unlike grid-cols-* they never set display themselves.

ClassEffect
col-span-1Span 1 column
col-span-2Span 2 columns
col-span-3Span 3 columns
col-span-4Span 4 columns
col-span-fullSpan every column, whatever the count is

Supports responsive prefixes: md:col-span-2, lg:col-span-full.

<div class="grid-cols-4 gap-x-gutter gap-y-24">
<h2 class="col-span-full">Heading across the grid</h2>
<div class="col-span-3">Main content</div>
<div>Aside</div>
</div>

Web experiences should be built mobile first, with layouts fluidly adjusting using flexbox, grid, container queries, and viewport units. Use breakpoints only when you need to significantly shift the layout at specific viewport sizes.

Our breakpoints cover the most common points where you might need to significantly change the layout. Design handovers (delivery) will be in the middle of these breakpoints and are intended to cover the entire range up until the next breakpoint.

BreakpointTokenMinDeliveryMax
Small (sm)—0402px479px
Medium (md)30rem480px768px1023px
Large (lg)64rem1024px1280px1599px
Extra large (xl)100rem1600px1920px∞

sm

Delivery (402px)
Max (479px)

md

Min (480px)
Delivery (768px)
Max (1023px)

lg

Min (1024px)
Delivery (1280px)
Max (1599px)

xl

Min (1600px)
Delivery (1920px)
Max (∞)

Some layout classes have responsive modifier prefixes. Fluid utilities that already adapt to the viewport typically don’t need them.

Common modifiers: apply from the breakpoint and up:

PrefixMedia query
md:@media (--v-from-md)
lg:@media (--v-from-lg)
xl:@media (--v-from-xl)

Range modifiers: available for some classes:

PrefixMedia query
until-md:@media (--v-until-md)
until-lg:@media (--v-until-lg)
until-xl:@media (--v-until-xl)
md:until-lg:@media (--v-from-md) and (--v-until-lg)
md:until-xl:@media (--v-from-md) and (--v-until-xl)
lg:until-xl:@media (--v-from-lg) and (--v-until-xl)
<!-- Centered on small screens, left aligned on medium, right aligned on large -->
<p class="text-center md:text-start lg:text-end">
We want to provide you with the freedom to move in a personal, sustainable and
safe way.
</p>

Media query breakpoints are exported as Custom Media Queries, a draft CSS specification. They require a CSS postprocessor such as PostCSS. Import the breakpoints in each file that uses them:

@import url('@volvo-cars/css/breakpoints.css');
.component {
--size: 2rem;
width: var(--size);
height: var(--size);
}
@media (--v-from-lg) {
.component {
--size: 3rem;
}
}

To avoid fragmenting styles across a stylesheet, keep component styles in a single place and only change custom properties at different breakpoints.

QueryRange
--v-from-md≥ 30 rem (480 px)
--v-from-lg≥ 64 rem (1024 px)
--v-from-xl≥ 100 rem (1600 px)
--v-until-md< 30 rem
--v-until-lg< 64 rem
--v-until-xl< 100 rem
--v-only-md30 rem – 63.99 rem
--v-only-lg64 rem – 99.99 rem
--v-only-xl≥ 100 rem

The tables below describe utilities from @volvo-cars/css. A placeholder such as {size} accepts only the values listed with that table.

Responsive support is specific to each family. md:, lg:, and xl: variants require their corresponding stylesheets, all included by @volvo-cars/css/styles_all-media.css. Each section lists its supported prefixes; prefixes cannot be combined freely.

The design system provides two spacing scales. fixed values for precise control and fluid values that scale with the viewport.

Fixed spacing: pixel-based (expressed in rem), used for component-level spacing:

TokenValue
--v-space-22 px
--v-space-44 px
--v-space-88 px
--v-space-1212 px
--v-space-1616 px
--v-space-2424 px
--v-space-3232 px
--v-space-4848 px
--v-space-6464 px

Fluid spacing: scales between a min and max based on viewport width, used for vertical section spacing:

TokenRange
--v-space-xs8 – 20 px
--v-space-sm16 – 32 px
--v-space-md24 – 48 px
--v-space-lg32 – 56 px
--v-space-xl48 – 72 px
--v-space-2xl64 – 128 px

Other tokens:

TokenDescription
--v-space-pagemarginFluid page margins (16 – 64 px)
--v-space-pageoffsetPage margin plus centering space beyond the maximum page width
--v-space-gutterColumn gutter spacing
--v-space-gridded-element-gapMicro gap between gridded UI elements

Use .stack-* classes to create vertical rhythm between sibling elements. Stack applies margin-top to all children except the first.

Fixed stacks:

ClassGap
stack-44 px
stack-88 px
stack-1616 px
stack-2424 px
stack-3232 px
stack-4848 px
stack-6464 px

Fluid stacks:

ClassGap
stack-s--v-space-s
stack-m--v-space-m
stack-l--v-space-l

stack-text starts as stack-16 but includes additional rules for heading compositions, ensuring consistent typographic spacing with little effort.

<div class="stack-text">
<h1 class="statement-3">For a better future.</h1>
<p class="heading-2">We want to provide you with the freedom to move.</p>
</div>

Override individual gaps with mt-* classes or the --stack-gap custom property.

Stack classes have no responsive prefixes.

Use .gap-* classes to create separation between children in flex or grid layouts.

ClassValue
gap-00
gap-22 px
gap-44 px
gap-88 px
gap-1212 px
gap-1616 px
gap-2424 px
gap-3232 px
gap-4848 px
gap-6464 px
gap-x-gutter--v-space-gutter (column gutter)
gap-gridded-element--v-space-gridded-element-gap
gap-y-xs--v-space-xs (fluid: 8–20 px)
gap-y-sm--v-space-sm (fluid: 16–32 px)
gap-y-md--v-space-md (fluid: 24–48 px)
gap-y-lg--v-space-lg (fluid: 32–56 px)
gap-y-xl--v-space-xl (fluid: 48–72 px)
gap-y-2xl--v-space-2xl (fluid: 64–128 px)
gap-y-s--v-space-s (legacy spacing token)
gap-y-m--v-space-m (legacy spacing token)
gap-y-l--v-space-l (legacy spacing token)

Fixed sizes 0, 2, 4, 8, 12, 16, 24, 32, 48, 64, and gridded-element are also available as gap-x-{size} (column only) and gap-y-{size} (row only). Fluid and legacy spacing tokens are available only with gap-y-; gutter is available only with gap-x-.

Every gap class supports md:, lg:, and xl: prefixes.

<div class="flex-row gap-16">
<div>A</div>
<div>B</div>
<div>C</div>
</div>

Use m{direction}-{size} classes for precise spacing control. Direction is one of t (top), b (bottom), y (vertical), l (inline-start), r (inline-end), x (horizontal), or omitted for all sides.

Class patternSupported values
m{direction}-{size}Sizes: 0, 2, 4, 8, 12, 16, 24, 32, 48, 64; all directions
-m{direction}-{size}Negative fixed margins: the same sizes except 0; all directions
m{direction}-{size}Fluid sizes: xs, sm, md, lg, xl, 2xl; directions t, b, y only
m{direction}-{size}Legacy sizes: s, m, l; directions t, b, y only
mx-pagemargin, ml-pagemargin, mr-pagemarginFluid page margin on both inline edges, start, or end
mx-pageoffset, ml-pageoffset, mr-pageoffsetPage margin plus centering space beyond the maximum page width
m-auto, mx-auto, my-auto, mt-auto, mb-auto, ml-auto, mr-autoAutomatic margin

Every margin class supports md:, lg:, and xl: prefixes. Put the breakpoint before the negative sign, for example md:-mt-8. Negative margins are available only for the nonzero fixed sizes.

Prefer stack for vertical rhythm and flexbox distribution (justify-between, justify-evenly) for horizontal spacing. Use margin utilities when you need precise control over individual elements.

Use p{direction}-{size} classes. Same direction pattern as margin: t, b, y, l, r, x, or omitted.

Class patternSupported values
p{direction}-{size}Sizes: 0, 2, 4, 8, 12, 16, 24, 32, 48, 64; all directions
p{direction}-{size}Fluid sizes: xs, sm, md, lg, xl, 2xl; directions t, b, y only
p{direction}-{size}Legacy sizes: s, m, l; directions t, b, y only
px-pagemargin, pl-pagemargin, pr-pagemarginFluid page margin on both inline edges, start, or end
px-pageoffset, pl-pageoffset, pr-pageoffsetPage margin plus centering space beyond the maximum page width

Every padding class supports md:, lg:, and xl: prefixes. Padding has no negative or auto variants.

<section class="py-xl px-16">
Fluid vertical padding, fixed horizontal padding
</section>
ClassEffect
blockBlock outer layout
inlineInline outer layout
inline-flexInline outer layout with a flex container inside
inline-gridInline outer layout with a grid container inside
flow-rootEstablishes a new block formatting context (inline-block equivalent with inline flow-root)
contentsElement’s children participate in the parent layout directly
hiddendisplay: none
invisiblevisibility: hidden (preserves layout space)
sr-onlyVisually hidden, accessible to screen readers

inline-flex and inline-grid set the outer display only, so combine them with flex-col, flex-wrap, grid-cols-* and the rest as usual.

inline can also be combined with flex and grid directly: inline flex-row and inline-flex flex-row are equivalent, as are inline grid-cols-2 and inline-grid grid-cols-2.

block, inline, inline-flex, inline-grid, flow-root, and contents support md:, lg:, and xl: prefixes. sr-only has no variants.

Both hidden and invisible support every prefix below, for example md:hidden or md:until-lg:invisible:

PrefixApplies
md:, lg:, xl:From the named breakpoint upward
until-md:, until-lg:, until-xl:Below the named breakpoint
md:until-lg:, md:until-xl:, lg:until-xl:Within the named breakpoint range
empty:When the element matches :empty

Use the HTML hidden attribute for always-hidden content. Use hidden="until-found" if the content should be searchable with find-in-page. Use empty:hidden to remove an empty element from layout, or empty:invisible to preserve its space.

ClassEffect
flexFlex container without changing its direction
flex-rowHorizontal direction (default)
flex-colVertical direction
flex-row-reverseReversed horizontal
flex-col-reverseReversed vertical
flex-wrapAllow wrapping
flex-nowrapPrevent wrapping
flex-growAllow item to grow
flex-grow-0Prevent growing
flex-shrinkAllow item to shrink
flex-shrink-0Prevent shrinking
flex-1Grow and shrink from zero
flex-noneNeither grow nor shrink

Alignment:

ClassEffect
items-startAlign items to start
items-centerCenter items
items-endAlign items to end
items-stretchStretch items (default)
items-baselineAlign item text baselines
self-startOverride alignment for one item
self-centerCenter one item
self-endAlign one item to end
self-stretchStretch one item

Justify:

ClassEffect
justify-startPack items to start
justify-centerCenter items
justify-endPack items to end
justify-betweenEven spacing, no edges
justify-aroundEven spacing, half edges
justify-evenlyEven spacing, equal edges

All flex, alignment, and justify classes above support md:, lg:, and xl: prefixes.

<nav class="flex-row items-center justify-between gap-16">
<a href="/">Logo</a>
<div class="flex-row gap-8">
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
</nav>

Apply these classes to a flex item to set its initial size before growing or shrinking.

Class or patternEffect
basis-autoUse the item’s main-axis size or content
basis-{size}Fixed sizes: 0, 8, 16, 24, 32, 40, 48, 64 px, expressed in rem
basis-1/2flex-basis: 50%
basis-fullflex-basis: 100%
basis-{size}Grid sizes: xs, sm, md, lg, xl, using the matching --v-size-grid-{size} token

Every flex-basis class supports md:, lg:, and xl: prefixes, including grid sizes.

The layout-* slice classes are deprecated and retained only for compatibility with existing pages. New layouts should combine a container with responsive grid or flex utilities so their behavior is explicit and can adapt to the content.

<div class="container-xl grid lg:grid-cols-2 gap-x-gutter gap-y-24">
<div>Primary content</div>
<div>Secondary content</div>
</div>

There is no one-to-one utility replacement for the legacy asymmetric slice geometry or its data-reversed behavior. Preserve it locally while migrating an existing page, then redesign that layout using its actual content requirements.

Class or patternEffect
w-{size}Fixed sizes: 0, 8, 16, 24, 32, 40, 48, 64 px, expressed in rem
w-1/2, w-fullWidth of 50% or 100%
w-fit, w-min, w-autoWidth of fit-content, min-content, or auto
min-w-0, min-w-fit, min-w-minMinimum width of 0, fit-content, or min-content
max-w-1/2, max-w-fullMaximum width of 50% or 100%
max-w-fit, max-w-minMaximum width of fit-content or min-content
w-xs, w-sm, w-md, w-lg, w-xlFluid width using the matching --v-size-grid-{size} token; not centered
max-w-proseReading width from --v-size-contentmax

All rows except grid widths (w-xs through w-xl) and max-w-prose support md:, lg:, and xl: prefixes.

Class or patternEffect
h-{size}, min-h-{size}, max-h-{size}Height, minimum, or maximum; fixed sizes: 0, 8, 16, 24, 32, 40, 48, 64 px, expressed in rem
h-full, min-h-full, max-h-fullHeight, minimum, or maximum of 100%
h-fit, min-h-fit, max-h-fitHeight, minimum, or maximum of fit-content
h-autoAutomatic height
h-1lhOne line height (1lh, with a 1.5em fallback)
max-h-inheritInherit the parent’s maximum height

All height classes except h-1lh and max-h-inherit support md:, lg:, and xl: prefixes. Use h-auto to release a fixed height, such as h-64 md:h-auto.

These utilities work on any element. See Media for image and video examples.

ClassRatio
aspect-1/1Square, 1:1
aspect-4/5Portrait, 4:5
aspect-2/3Portrait, 2:3
aspect-9/16Portrait, 9:16
aspect-4/3Landscape, 4:3
aspect-3/2Landscape, 3:2
aspect-16/9Landscape, 16:9
aspect-21/9Landscape, 21:9

All aspect-ratio classes support md:, lg:, and xl: prefixes.

Use these on images or videos with a constrained box. See Media for usage guidance.

ClassEffect
object-coverFill the box, cropping overflow
object-containFit the whole source inside the box
object-fillStretch to fill the box, allowing distortion

Object-fit classes have no responsive prefixes.

Border widths use the design system’s global solid border style and default --v-color-ornament-primary color. Add a border color class to change the color, for example border-b border-primary.

EdgeClasses for 0 / 1 / 2 px
All sidesborder-0, border, border-2
Topborder-t-0, border-t, border-t-2
Bottomborder-b-0, border-b, border-b-2
Inline startborder-l-0, border-l, border-l-2
Inline endborder-r-0, border-r, border-r-2
Both inline edgesborder-x-0, border-x, border-x-2
Both block edgesborder-y-0, border-y, border-y-2

l and r follow the writing direction: start is left in LTR and right in RTL. All width classes above support md:, lg:, and xl: prefixes, such as border-b md:border-b-0.

Inset rings:

ClassEffect
border-ringA 1 px inset box-shadow border
border-ring-2A 2 px inset box-shadow border

Rings use the same border color classes and ornament default, but do not take up layout space. They replace box-shadow and have no responsive prefixes. See supported state utilities for conditional border widths and rings.

ClassBorder radius
shape-default--v-shape-default
shape-emphasis--v-shape-emphasis
rounded-none0
rounded-full--v-shape-emphasis, the same as shape-emphasis

Shape and radius classes have no responsive or state prefixes.

ClassEffect
staticDefault positioning
relativeRelative to normal position
absoluteRemoved from flow, relative to positioned ancestor
fixedRelative to viewport
stickySticks when scrolling past threshold

Position classes have no responsive prefixes. Their offsets do:

Class patternsSupported suffixes
top-{value}, bottom-{value}, start-{value}, end-{value}Fixed sizes: 0, 2, 4, 8, 12, 16, 24, 32, 48, 64 px, expressed in rem
top-{value}, bottom-{value}, start-{value}, end-{value}1/2 (50%), full (100%), auto

Every offset supports md:, lg:, and xl: prefixes. start and end respect RTL direction. There are no negative offset utilities.

Containment:

ClassEffect
contain-contentLayout, paint, and style containment (contain: content)
contain-noneDisable containment

Use contain-content on a parent to scope absolutely positioned children. It also clips painting outside the container. Neither containment class has responsive prefixes.

<div class="contain-content">
<div class="absolute top-0 end-0">Positioned element</div>
</div>
ClassToken
z-spinner--v-index-spinner
z-overlay--v-index-overlay
z-navigation--v-index-navigation
z-default--v-index-default
z-deep--v-index-deep

These set z-index within the element’s stacking context and have no responsive prefixes.

ClassEffect
overflow-autoScroll when needed
overflow-hiddenClip overflow
overflow-visibleShow overflow
overflow-clipClip with no scrolling

Each value (auto, hidden, visible, clip) is also available as overflow-x-{value} and overflow-y-{value}. Every overflow class supports md:, lg:, and xl: prefixes, for example overflow-hidden lg:overflow-auto.

ClassEffect
scrollbar-noneHide the native scrollbar unless the element matches :focus-visible
snap-xMandatory horizontal scroll snapping on a container
snap-yMandatory vertical scroll snapping on a container
snap-startAlign a child’s start edge at its snap point
snap-centerAlign a child’s center at its snap point
snap-endAlign a child’s end edge at its snap point

Pair a container’s snap-x or snap-y with alignment classes on its children, for example overflow-x-auto snap-x with snap-start items. Snapping does not create overflow by itself. Scrolling classes have no responsive prefixes.

Use scrollbar-none only when you provide a visual scroll indicator and clickable controls. Scrolling remains available, and the scrollbar returns on keyboard focus.

Reposition elements using the CSS translate property. Horizontal translations automatically flip in RTL.

ClassEffect
translate-x-0Reset horizontal translation
translate-y-0Reset vertical translation
translate-x-1/2Move right 50%
-translate-x-1/2Move left 50%
translate-y-1/2Move down 50%
-translate-y-1/2Move up 50%
translate-x-fullMove right 100%
-translate-x-fullMove left 100%
translate-y-fullMove down 100%
-translate-y-fullMove up 100%

Directions above describe LTR; horizontal directions reverse in RTL. Translation classes have no responsive or state prefixes.