Viewport-relative spacing
vw, vw-* and spacing-reset for spacing that scales with the viewport.
On this page
The vw utilities make Tailwind's spacing scale fluid inside an element: they redefine the --spacing unit as a fraction of the viewport width, so p-6, gap-4, mt-10 and every other spacing utility in that subtree grows and shrinks with the screen instead of staying at fixed pixels. spacing-reset puts the fixed 4px unit back.
Quick referenceLink to this section
| Class | Styles |
|---|---|
vw | --spacing: calc(4 / var(--screen-current) * 100vw); |
vw-1 | --spacing: calc(1 / var(--screen-current) * 100vw); |
vw-px | --spacing: calc(1 / var(--screen-current) * 100vw); |
spacing-reset | --spacing: 4px; |
vw-1-2xl | --spacing: calc(1 / 1920 * 100vw); |
vw-1-3xl | --spacing: calc(1 / 2560 * 100vw); |
vw-1-lg | --spacing: calc(1 / 1192 * 100vw); |
vw-1-md | --spacing: calc(1 / 768 * 100vw); |
vw-1-sm | --spacing: calc(1 / 390 * 100vw); |
vw-1-xl | --spacing: calc(1 / 1440 * 100vw); |
vw-2xl | --spacing: calc(4 / 1920 * 100vw); |
vw-3xl | --spacing: calc(4 / 2560 * 100vw); |
vw-lg | --spacing: calc(4 / 1192 * 100vw); |
vw-md | --spacing: calc(4 / 768 * 100vw); |
vw-px-2xl | --spacing: calc(1 / 1920 * 100vw); |
vw-px-3xl | --spacing: calc(1 / 2560 * 100vw); |
vw-px-lg | --spacing: calc(1 / 1192 * 100vw); |
vw-px-md | --spacing: calc(1 / 768 * 100vw); |
vw-px-sm | --spacing: calc(1 / 390 * 100vw); |
vw-px-xl | --spacing: calc(1 / 1440 * 100vw); |
vw-sm | --spacing: calc(4 / 390 * 100vw); |
vw-xl | --spacing: calc(4 / 1440 * 100vw); |
Basic usageLink to this section
--screen-current is the design width of the active breakpoint (390, 768 and 1192 with the default tokens). With vw, one spacing step is 4 design pixels at that width, so p-6 is exactly 24px when the viewport matches the design and scales in proportion everywhere else. Because --spacing is an inherited custom property, the class affects the element and everything inside it.
Switch the preview between phone, tablet and desktop and watch the two readouts: the fixed box keeps 24px, the vw box tracks the viewport and lands on 24px at each design width.
import { useEffect, useRef, useState } from 'react'
function PaddingBox({ fluid }: { fluid: boolean }) {
const ref = useRef<HTMLDivElement>(null)
const [padding, setPadding] = useState('')
useEffect(() => {
const update = () => {
if (ref.current) setPadding(getComputedStyle(ref.current).paddingLeft)
}
update()
window.addEventListener('resize', update)
return () => window.removeEventListener('resize', update)
}, [])
return (
<div className={fluid ? 'vw' : undefined}>
<div
ref={ref}
className="rounded-lg border border-(--color-foundations-surface-border) bg-(--color-foundations-surface-bg) p-6"
>
<div className="flex items-baseline justify-between gap-4 rounded bg-(--color-foundations-primary-bg) px-3 py-2 text-(--color-foundations-primary-text)">
<code className="text-small">{fluid ? 'vw p-6' : 'p-6'}</code>
<span className="text-small">padding {padding}</span>
</div>
</div>
</div>
)
}
export default function Demo() {
return (
<div className="flex w-full flex-col gap-4">
<PaddingBox fluid={false} />
<PaddingBox fluid />
</div>
)
}In markup the class goes on a wrapper, and the spacing utilities inside it follow:
<section class="vw">
<div class="px-6 py-10 gap-4">…</div>
</section>One design pixel per stepLink to this section
vw keeps Tailwind's 4px step, which suits values from a 4px grid. When a design uses arbitrary pixel values, vw-px (or its alias vw-1) makes one step equal one design pixel, so the number in the class is the number in Figma: pt-37 is 37 design pixels.
<div class="vw-px pt-37 pb-52 gap-18">…</div>Pinning one design widthLink to this section
vw-<breakpoint> ignores --screen-current and divides by a fixed design width instead, for example vw-lg scales every step against 1192px at all viewports. The vw-px-<breakpoint> and vw-1-<breakpoint> forms do the same with one-pixel steps. Use them when a block was drawn only at one size and should scale from that drawing everywhere.
Responsive and state variantsLink to this section
vw re-bases at every breakpoint on its own, because --screen-current changes there. To stop fluid spacing from some width up, reset it with a breakpoint prefix:
<section class="vw xl:spacing-reset">
<!-- fluid up to xl, fixed 4px steps from xl up -->
</section>spacing-reset also turns fluid spacing off for one subtree inside a vw block, for example a form whose controls should keep their pixel sizes. To pin a design width per breakpoint, combine prefixes: vw-sm md:vw-md lg:vw-lg is what vw computes on its own; vw-md lg:vw-md keeps the tablet drawing on desktops.
CustomizingLink to this section
The design widths come from config.screenSizes in your tokens, and --screen-current follows the breakpoints that have a responsiveSizing mode. With the default tokens that ends at lg, so above 1192px vw keeps dividing by 1192 and spacing keeps growing. Defining customTokens.responsiveSizing.xl (and up) moves --screen-current to 1440 and beyond. The breakpoints reference lists the design width of every breakpoint.
The vw classes scale Tailwind's spacing utilities only. Token-driven sizes (container width, grid gaps, section padding) become fluid through the spacing rules in systhema.config, described in Responsive sizing.