Responsive design
WovenUI is mobile first. A breakpoint means “this width and up”: every responsive class, every apply() breakpoint modifier and media-up() compile to a min-width media query. One map, $breakpoints, drives all of them.
Mobile first
Section titled “Mobile first”A class without a suffix applies at every width. A class with a suffix applies from that breakpoint upward.
<div class="p-4 p-6@md">...</div>.p-4 { padding: var(--wu-space-4);}@media (min-width: 768px) { .p-4\@md { padding: var(--wu-space-4); }}In HTML the class is written p-4@md; the CSS escapes the @ as \@. Start with the small screen layout, then add the classes that change it at larger widths.
Wrong versus right
Section titled “Wrong versus right”A reader who thinks desktop first writes the large layout and then a suffix for “small screens”. The suffix means the opposite:
| Written | Below the breakpoint | From the breakpoint up |
|---|---|---|
flex row col@sm (wrong) |
Row on phones | Column from 640px, so the desktop gets the stacked layout |
flex col row@md (right) |
Column on phones | Row from 768px |
<div class="flex col row@md gap-3"> <div class="p-3 bg-brand-subtle rounded-md">One</div> <div class="p-3 bg-brand-subtle rounded-md">Two</div> <div class="p-3 bg-brand-subtle rounded-md">Three</div></div>.layout { @include apply(flex, col, row md, gap(3));}The preview is a frame with its own window, so its breakpoints read the width of the frame, not of the browser. Pick Tablet or wider to see the three items in a row; pick Phone or drag the handle below 768px to see them stacked.
The breakpoints
Section titled “The breakpoints”| Suffix | Media query |
|---|---|
@xs | @media (min-width: 480px) |
@sm | @media (min-width: 640px) |
@md | @media (min-width: 768px) |
@lg | @media (min-width: 1024px) |
@xl | @media (min-width: 1280px) |
@2xl | @media (min-width: 1536px) |
The defaults come from $breakpoints in config/_breakpoints.scss:
| Name | Width | Class suffix | apply() modifier |
|---|---|---|---|
xs |
480px | @xs |
xs |
sm |
640px | @sm |
sm |
md |
768px | @md |
md |
lg |
1024px | @lg |
lg |
xl |
1280px | @xl |
xl |
2xl |
1536px | @2xl |
2xl |
Below 480px, only the classes without a suffix apply.
Classes
Section titled “Classes”- Suffix last. The breakpoint is written after the class:
p-4@md,cols-3@lg. - State prefix first. A class with a state takes the prefix at the start and the breakpoint at the end:
hover:opacity-75@md,group-hover:translate-y-1@lg. See States. - One breakpoint per class. There is no
p-4@md@lg; writep-4@md p-6@lg. - Later breakpoints win. The build emits each breakpoint block after the smaller ones, so
cols-2@md cols-3@lggives 3 columns from 1024px.
Which families are responsive
Section titled “Which families are responsive”Every family below has a @{breakpoint} form at each breakpoint, counted in the compiled build:
| Family | Examples |
|---|---|
| Display and visibility | hide@md, block@md, flex@lg, invisible@sm |
| Overflow | overflow-hidden@md |
| Flex and grid | col@md, between@lg, cols-2@md, col-span-2@lg, gap-4@md |
| Spacing | p-4@md, mx-auto@lg, space-y-2@md |
| Sizing (keys, percentages, keywords) | w-full@md, w-50p@lg, h-full@md (see the gaps below) |
| Position and z-index | relative@md, top-0@lg, z-10@md |
| Typography | text-center@md, text-lg@md, font-bold@lg, uppercase@md, truncate@md |
| Border width, radius, shadow | border@md, border-2@md, rounded-lg@md, shadow-md@lg |
| Opacity, transforms, transitions | opacity-75@md, translate-x-4@md, scale-150@lg, duration-300@md |
| Backdrop filters, animations | backdrop-blur-md@md, animate-spin@md |
Families with no responsive classes:
- Colors: every
bg-*,text-*color andborder-*color class, with or without ahover:prefix.hover:bg-brand@mddoes not exist. - Border styles and sides:
border-dashed,border-t-2,border-s,border-xand the other side classes. Onlyborderandborder-{width}are responsive. - Side and corner radii:
rounded-t-*,rounded-s-*,rounded-tl-*and the rest. Onlyroundedandrounded-{key}are responsive. - Inset shadows and elevation:
shadow-inset-*,elevation-*, and theactive:shadow-*classes. - Sizing by breakpoint name and full-screen keywords:
w-lg,min-w-md,max-w-xl,w-screen,h-screen,min-w-full,max-w-full,min-h-full,max-h-full. - Others:
cursor-*,whitespace-*,break-*,flow-*,auto-fit-*,auto-fill-*, thecontainerclasses,sr-only,hover-ready.
For these, use apply() with a breakpoint in your own SCSS: bg(surface) md works even though .bg-surface@md does not exist. The class names are listed on Utility classes.
Hide and show
Section titled “Hide and show”hide sets display: none everywhere; block@md turns the element back on from 768px.
<div class="space-y-2"> <div class="p-3 bg-surface border rounded-md">Always visible</div> <div class="hide block@md p-3 bg-brand-subtle rounded-md">Visible from md (768px and up)</div> <div class="block hide@md p-3 bg-alternate rounded-md">Visible below md only</div></div>.sidebar { @include apply(hide, block md);}Responsive grid
Section titled “Responsive grid”<div class="grid cols-1 cols-2@md cols-3@lg gap-3"> <div class="p-3 bg-brand-subtle rounded-md">1</div> <div class="p-3 bg-brand-subtle rounded-md">2</div> <div class="p-3 bg-brand-subtle rounded-md">3</div> <div class="p-3 bg-brand-subtle rounded-md">4</div> <div class="p-3 bg-brand-subtle rounded-md">5</div> <div class="p-3 bg-brand-subtle rounded-md">6</div></div>.gallery { @include apply(grid, cols(1), cols(2) md, cols(3) lg, gap(3));}Responsive text alignment
Section titled “Responsive text alignment”<div class="text-center text-start@md p-4 bg-surface border rounded-md"> <strong class="font-semibold">Welcome back</strong> <p class="text-sm text-muted">Centered below 768px, aligned to the start from 768px.</p></div>.intro { @include apply(text-center, text-start md);}apply()
Section titled “apply()”In apply(), the breakpoint is a modifier written after the utility, without @:
@use "@wovenui/core" as *;
.card { @include apply(p(4), p(6) md);}.card { padding: var(--wu-space-4);}@media (min-width: 768px) { .card { padding: var(--wu-space-6); }}- After the utility.
p(6) md, nevermd:p(6)orp(6)@md. - Either order with a state.
shadow(md) hover lgandshadow(md) lg hovergive the same rule. - One breakpoint per item.
p(4) md lgraises an error (see Errors). Write two items:p(4) md, p(6) lg. - Smallest breakpoint first. Items without a modifier come first, then each breakpoint from the smallest up, whatever the written order.
.menu { @include apply(shadow(md) hover lg);}@media (min-width: 1024px) { .menu:hover { box-shadow: var(--wu-shadow-md); }}The written order does not change the cascade. flex md is written before hide here:
.panel { @include apply(flex md, hide);}.panel { display: none;}@media (min-width: 768px) { .panel { display: flex; }}The panel is hidden below md and a flex container from md up. Each item produces its own @media block; items at the same breakpoint are not merged.
Media query mixins
Section titled “Media query mixins”For your own CSS, the media query mixins take a breakpoint name or a length. Ranges end 0.02px below the upper breakpoint, so two ranges never overlap.
| Mixin | Media query | Applies |
|---|---|---|
media-up(md) |
(min-width: 768px) |
768px and up |
media-down(md) |
(max-width: 767.98px) |
Below 768px |
media-between(sm, lg) |
(min-width: 640px) and (max-width: 1023.98px) |
640px up to 1024px |
media-only(md) |
(min-width: 768px) and (max-width: 1023.98px) |
From md to the next breakpoint |
@use "@wovenui/core" as *;
.a { @include media-up(md) { display: flex; }}.b { @include media-down(md) { display: none; }}.c { @include media-between(sm, lg) { padding: 1rem; }}.d { @include media-only(md) { padding: 2rem; }}.e { @include media-up(900px) { gap: 2rem; }}.f { @include media-only(2xl) { padding: 3rem; }}@media (min-width: 768px) { .a { display: flex; }}@media (max-width: 767.98px) { .b { display: none; }}@media (min-width: 640px) and (max-width: 1023.98px) { .c { padding: 1rem; }}@media (min-width: 768px) and (max-width: 1023.98px) { .d { padding: 2rem; }}@media (min-width: 900px) { .e { gap: 2rem; }}@media (min-width: 1536px) { .f { padding: 3rem; }}- Lengths work too.
media-up(900px)uses the length as written. - The largest breakpoint is open-ended.
media-only(2xl)has no upper limit. media-only()takes the breakpoint unit. A length in another unit raises an error.
Breakpoint names elsewhere
Section titled “Breakpoint names elsewhere”The breakpoint names also work as widths:
| Input | Output |
|---|---|
width(lg), .w-lg |
width: 1024px |
max-width(md), .max-w-md |
max-width: 768px |
container, .container |
max-width of each breakpoint minus 100px: 380px, 540px, 668px, 924px, 1180px, 1436px |
get-breakpoint-value(md) |
768px |
@use "@wovenui/core" as *;
.narrow { @include apply(width(lg), max-width(md));}.x { --md: #{get-breakpoint-value(md)};}.narrow { width: 1024px; max-width: 768px;}.x { --md: 768px;}See Container for the full container() output.
Container queries
Section titled “Container queries”Media queries follow the window. When a component must adapt to the space its parent gives it (a card in a sidebar and the same card in the main column), use container queries instead: container-type on the parent and container-up(), container-down() and the other container mixins on the children. They take the same breakpoint names or a length. See Container queries and Container.
Custom breakpoints
Section titled “Custom breakpoints”Set $breakpoints in with(). The map replaces the defaults, so list every breakpoint you keep. A new key works as a class suffix, an apply() modifier and a media query mixin argument.
@use "@wovenui/core" as * with ( $generate-utility-classes: true, $breakpoints: ( "xs": 480px, "sm": 640px, "md": 768px, "tablet": 900px, "lg": 1024px, "xl": 1280px, "2xl": 1536px, ));
.sidebar { @include apply(hide, block tablet);}@media (min-width: 900px) { .p-4\@tablet { padding: var(--wu-space-4); }}.sidebar { display: none;}@media (min-width: 900px) { .sidebar { display: block; }}In HTML, p-4@tablet applies from 900px. The @media block above is shortened to one class; the build emits every responsive class for the new key.
See Configuration for the other maps.
Known limit
Section titled “Known limit”The responsive truncate@{bp} and overflow-label@{bp} classes come after the responsive display, overflow and sizing classes of every breakpoint in the build. truncate@sm overflow-visible@lg stays truncated at lg, because .truncate\@sm is emitted later than .overflow-visible\@lg. Undo it in your own styles.
Errors
Section titled “Errors”The messages below are printed by the Sass compiler for the input shown.
| Problem | Input | Error |
|---|---|---|
| A number without a unit | media-up(800) |
Invalid breakpoint: 800 is not a breakpoint name (xs, sm, md, lg, xl, 2xl). Write a unit for a custom width (800px). |
| Two breakpoints on one item | apply(p(4) md lg) |
apply(p(4) md lg): more than one breakpoint (md, lg). Use one breakpoint per item. |
The names in the messages come from your $breakpoints map.
Related
Section titled “Related”- apply() covers the modifiers and the output order in full.
- States covers the state prefixes and modifiers that combine with breakpoints.
- Utility classes explains how the classes are named and generated.
- Media queries and Container queries document every query mixin.