Utility classes
Every WovenUI utility class follows one grammar: a property, a value, an optional state in front and an optional breakpoint at the end. Learn it once and you can read any class on the utility pages, and write the matching mixin in SCSS.
<div class="flex between x-center p-4 p-6@md bg-surface text-regular rounded-lg">...</div>The grammar
Section titled “The grammar”property-valueproperty-value@breakpointstate:property-valuestate:property-value@breakpoint| Class | Reads as |
|---|---|
bg-brand |
Background in the brand color |
p-4@md |
Padding key 4, from the md breakpoint up |
hover:bg-brand-subtle |
Brand subtle background on hover |
hover:opacity-75@sm |
Opacity key 75 on hover, from the sm breakpoint up |
- The state comes first, with a colon.
hover:,group-hover:,active:andfocus:are the class prefixes. See States and breakpoints in classes for which families have them. - The breakpoint comes last, with
@.@mdmeans “this width and up” (mobile first). The default names arexs,sm,md,lg,xland2xl. - In CSS,
:and@are escaped. You writeclass="p-4@md"; the stylesheet selects.p-4\@md. Write the class in markup exactly as listed, without the backslashes.
Names you can read
Section titled “Names you can read”Class names describe the result, not the CSS property. Output from the compiled build:
| Class | CSS |
|---|---|
between (on .flex) |
justify-content: space-between |
x-center (on .flex) |
align-items: center |
col (on .flex) |
flex-direction: column |
z-modal |
z-index: var(--wu-z-modal) |
bg-surface |
background-color: var(--wu-bg-surface) |
text-muted |
color: var(--wu-text-muted) |
ps-4 |
padding-inline-start: var(--wu-space-4) |
rounded-lg |
border-radius: var(--wu-radius-lg) |
In flex classes, no prefix means the main axis and x- means the cross axis. x does not mean horizontal: with col, the axes swap.
Numbers are keys
Section titled “Numbers are keys”A number in a class is a key of a configuration map, never a length. p-4 is spacing key 4, which outputs var(--wu-space-4), which is 1rem by default. p-16 is key 16 (4rem), not 16 pixels.
.p-4 { padding: var(--wu-space-4);}- Only configured keys have classes. The default spacing keys are 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56 and 64. There is no
.p-7; a custom value belongs in SCSS (p(7px)). - Decimal keys escape the dot. A key
0.5added to$spacingsgives the classp-0.5, selected as.p-0\.5, and the token--wu-space-0_5. - Percent keys end in
p.w-50poutputswidth: 50%. The keys 33 and 66 are exact thirds (33.333%,66.667%).
Scales and keys per family: Spacing, Sizing.
Color words
Section titled “Color words”Color classes take a word, and the word is the same in the class and the mixin.
| Kind | Words | Classes |
|---|---|---|
| Roles | brand, success, danger, warning, info |
bg-brand, text-danger, border-brand, bg-success-subtle |
| Hues | gray, red, blue, green, yellow, purple, pink |
bg-red, text-red, bg-blue-subtle |
| Theme words | surfaces, text, borders | bg-surface, bg-alternate, text-muted, border-regular |
| Stops | any role or hue with 50, 100 to 900, 950 | bg-red-300, bg-brand-100 |
| On a fill | roles only | text-on-brand |
Every color class outputs a token, never a hex value:
.bg-surface { background-color: var(--wu-bg-surface); }.text-danger { color: var(--wu-text-danger); }.bg-red { background-color: var(--wu-red-500); }.bg-red-300 { background-color: var(--wu-red-300); }.text-on-brand { color: var(--wu-text-on-brand); }Role and theme tokens are redefined in dark mode and under data-theme, so bg-brand, text-danger, text-red and bg-surface follow the theme with no extra class. Palette stops (bg-red-300) and the bare hue background (bg-red, stop 500) stay fixed. text-on-{name} exists for roles only; there is no text-on-red. Full lists: Colors and Tokens.
Compound layout classes
Section titled “Compound layout classes”Layout modifiers only work together with their container class. The compiled selectors show why:
.flex.between { justify-content: space-between; }.flex.col { flex-direction: column; }.flex > .grow { flex: 1 1 0%; }.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }.flex:where(.inline) { display: inline-flex; }
@media (min-width: 768px) { .flex.row\@md { flex-direction: row; }}(Each selector also lists the responsive containers, such as .flex\@md.between.)
- Flex modifiers sit on a
.flexelement. Writeclass="flex between x-center";betweenalone does nothing. - Item classes sit on direct children.
grow,no-growandfill-6apply only inside a.flexparent. - Grid templates sit on a
.gridelement.class="grid cols-3".col-span-*androw-span-*are standalone. - Inline layouts are two classes.
flex inlineandgrid inline; there is no.inline-flexor.inline-gridclass. - A parent made flex in SCSS does not enable the child classes.
@include apply(flex)on.toolbargives no.flexclass; add the class, or use the item mixins (grow,fill(6)) on the children.
<div class="flex between x-center gap-2 p-3 bg-surface border rounded-md"> <strong>Invoices</strong> <span class="text-muted">12 open</span> <span class="bg-brand text-on-brand px-3 py-1 rounded-sm">New</span></div>.toolbar { @include apply(flex, between, x-center, gap(2), p(3), bg(surface), border(1), rounded(md));}Logical and physical
Section titled “Logical and physical”Logical classes follow the writing direction. Under dir="rtl" the start side is the right side, and these classes mirror with it:
| Logical | CSS |
|---|---|
ps-4, pe-2 |
padding-inline-start, padding-inline-end |
ms-2, me-2, ms-auto |
margin-inline-start, margin-inline-end |
start-0, end-0 |
inset-inline-start, inset-inline-end |
border-s, border-e |
border-inline-start-*, border-inline-end-* |
rounded-s-md |
border-start-start-radius, border-end-start-radius |
text-start, text-end |
text-align: start, text-align: end |
px-*, py-*, mx-*, my-* and space-x-* are logical too. Physical classes keep their side in both directions: pl-4 is padding-left, mr-2 is margin-right, left-0 is left, text-left is text-align: left. Use them only for content that must not mirror.
<div class="ps-4 pe-2 border-s-4 border-brand rounded-s-md bg-brand-subtle text-start p-2"> Logical: starts on the reading side</div><div class="pl-4 text-left bg-alternate p-2 mt-2"> Physical: always padded on the left</div>States and breakpoints in classes
Section titled “States and breakpoints in classes”State prefixes exist only where a state is common. The rest are written in SCSS with apply().
| Prefix | Families | Also with @breakpoint |
|---|---|---|
hover: |
background, text and border colors, border width and style, width and height, opacity, transforms, shadows | width and height, opacity, transforms, shadows |
group-hover: |
background, text and border colors, border width and style, width and height, opacity, transforms | width and height, opacity, transforms |
active: |
border colors, border width and style, opacity, shadows | opacity |
focus: |
border colors, border width and style, opacity, shadows | opacity, shadows |
group-hover:reacts to a parent. Putgroupon the parent:.group:hover .group-hover\:bg-blue.- Color states have no breakpoint form.
hover:bg-brand-subtleexists;hover:bg-brand-subtle@mddoes not. - Spacing, display and layout classes have no state prefix. Use
apply():@include apply(p(6) hover). - Most classes take a breakpoint suffix: spacing, sizing, display, flex and grid, typography and more (
p-4@md,row@md,hidden@lg).text-on-{role}is one of the exceptions.
Full rules: States and Responsive design.
Where classes come from
Section titled “Where classes come from”- Generated from configuration. Classes are emitted only with
$generate-utility-classes: true(the prebuiltdist/wovenui.csssets it). Each class comes from a configuration map: a key added to$spacingsgetsp-*,m-*,gap-*and the rest; a removed key loses them. $parent-selectorscopes them. With$parent-selector: ".app",.p-4is emitted as.app .p-4.- Feature flags switch groups off.
$feature-flags: ("animations": false)removes the animation classes; each flag owns its group. - Classes are unlayered. Reset, base and component styles live in
@layer reset, base, components; utility classes do not. Unlayered CSS beats layered CSS, so a class overrides a base style without!important.
@use "@wovenui/core" as * with ( $generate-utility-classes: true, $parent-selector: ".app", $feature-flags: ("animations": false));Every setting: Configuration.
Class to mixin
Section titled “Class to mixin”Every class has a mixin of the same name for apply(). The value moves into parentheses, a stop becomes a second argument, and the state and breakpoint move after the item without : or @.
| Class | Mixin |
|---|---|
p-4 |
p(4) |
mt-4@sm |
mt(4) sm |
bg-brand-100 |
bg(brand, 100) |
text-muted |
text-color(muted) |
hover:opacity-75@sm |
opacity(75) hover sm |
group-hover:bg-blue |
bg(blue) group-hover |
@use "@wovenui/core" as *;
.item { @include apply(p(4), mt(4) sm, bg(brand, 100), text-color(muted), opacity(75) hover sm, bg(blue) group-hover);}.item { padding: var(--wu-space-4); background-color: var(--wu-brand-100); color: var(--wu-text-muted);}.group:hover .item { background-color: var(--wu-blue-500);}@media (min-width: 640px) { .item { margin-top: var(--wu-space-4); }}@media (min-width: 640px) { .item:hover { opacity: var(--wu-opacity-75); }}Text colors use text-color(), not text(), and the color word never repeats the family: bg(surface), not bg(bg-surface). More: apply().
Mistakes the compiler cannot see
Section titled “Mistakes the compiler cannot see”A class in markup is plain text to Sass. A misspelled class (p-44, bg-primery, justify-between) matches no rule: the element renders unstyled and nothing reports it. Check the class against the utility page, or move the styles to SCSS.
The SCSS side is checked. @include apply(p(7)), bg(primery) or p(4) hovr stops the compile with a message that names the valid options. See Errors.