Reusing Styles
Decide when to keep utilities in markup, move values into theme tokens, or create reusable component classes.
Overview
Reusable styling starts with a decision, not a file. Keep local choices in markup, move shared values into theme tokens, and define component classes only when a repeated pattern has product meaning.
Writing .btn at the top level of a stylesheet creates a normal CSS rule. Writing btn inside @components defines an on-demand component class. Writing flow inside @utilities defines an on-demand utility class. All three can use @compose and @variant, but they serve different ownership needs.
Reuse decisions
Start with utilities
Use utility classes when the style belongs to one element, one state, or one local layout decision. This keeps the relationship between markup and style explicit while the pattern is still changing.
<button class="inline-flex align-items:center justify-content:center gap:xs px:md py:xs r:lg font:sm font:medium bg:blue fg:white"> Save changes</button>Do not abstract just to shorten a class list. Abstract when the name improves the product vocabulary: btn, card, field, toolbar, empty-state, or another role that appears across the interface.
Move shared values into the theme
When the repeated part is a value, make it a token instead of a class. Tokens keep markup semantic while still letting each component choose its own structure.
@theme { --color-brand: $color-blue-60; --spacing-action-x: 1rem;}<button class="px:action-x r:lg bg:brand fg:white"> Submit</button>Use Customizing your theme when the reusable part is color, spacing, typography, radius, shadow, motion, breakpoints, container sizes, or mode-specific values.
Create custom utilities
Use @utilities when your design system needs a reusable primitive that is still lower-level than a product component. Good utility names describe a styling capability, not a UI role.
@utilities { flow { @compose grid gap:md; } content-auto { content-visibility: auto; contain-intrinsic-size: auto 32rem; } print-hidden { @variant @print { display: none; } }}<section class="flow content-auto print-hidden">...</section>Custom utility definitions are compiled into the project manifest and generated only when the class appears in markup, is scanned from source, or is composed by another CSS-defined style. Use custom utilities for reusable primitives such as layout helpers, rendering hints, or print behavior. Use component classes when the name describes a product UI role such as .btn, .card, or .field.
Component classes
Define a component class
Use a component class when a repeated class list becomes a reusable UI pattern. Define components in @components, put Master CSS classes in @compose, and write native declarations directly in the named block.
@components { btn { @compose inline-flex align-items:center justify-content:center gap:xs px:md py:xs r:lg font:sm font:medium; @compose bg:blue fg:white; }}<button class="btn">Save changes</button>The generated CSS is emitted in the components layer:
@layer components { .btn { display: inline-flex; gap: 0.5rem; border-radius: 0.5rem; padding-left: 1rem; padding-right: 1rem; padding-top: 0.5rem; padding-bottom: 0.5rem; align-items: center; font-size: 0.875rem; font-weight: 500; justify-content: center; background-color: var(--color-blue); color: #fff; }}Component definitions are manifest inputs, not immediate CSS output. Defining .btn does not generate CSS until btn appears in scanned classes or is composed by another CSS-defined style.
You can still apply component classes alongside conditional utility classes when a variation belongs to one usage site:
<button class="btn btn-md btn-sm@<sm …">Submit</button>Generated CSS
@layer components { .btn { display: inline-flex; font-weight: 600 } .btn-md { border-radius: .375rem; padding-left: 1rem; padding-right: 1rem; font-size: .875rem; height: 2.5rem } @media (width<52.125rem) { .btn-sm\@\<sm { border-radius: .375rem; padding-left: .75rem; padding-right: .75rem; font-size: .75rem; height: 2rem } }}Add states and conditions
Put selector states in nested selectors and conditional variants in @variant blocks. This keeps the component definition readable and keeps the markup semantic.
@components { btn { @compose inline-flex align-items:center justify-content:center px:md py:xs r:lg; transition: background-color .15s ease, box-shadow .15s ease; &:hover { @compose bg:blue/.9; } &:focus-visible { @compose outline:2px|solid|blue outline-offset:0.125rem; } @variant @<sm { @compose block; } }}Equivalent markup stays short:
<button class="btn">Continue</button>Try clicking the button to see the outline effect
Keep variants composable
Avoid hiding every option behind a new component. Give each component class one responsibility, then combine it with tokens, utilities, or smaller component classes.
@components { btn { @compose inline-flex align-items:center justify-content:center; font-weight: 600; outline-offset: -0.0625rem; } btn-xs { @compose r:sm px:xs font:xs h:6x; } btn-sm { @compose r:md px:sm font:xs h:8x; } btn-md { @compose r:md px:md font:sm h:10x; } btn-lg { @compose r:lg px:lg font:md h:12x; } btn-xl { @compose r:xl px:lg font:md h:14x; }}<button class="btn btn-xs">Submit</button><button class="btn btn-sm">Submit</button><button class="btn btn-md">Submit</button><button class="btn btn-lg">Submit</button><button class="btn btn-xl">Submit</button>Round the same button with a separate class instead of duplicating every size:
<button class="btn btn-md rounded">Submit</button>If one class tries to own size, color, shape, layout, and interaction variants, overrides become fragile. Smaller classes compose better with utilities and rarely need !important.
Build shared structures
Create explicit classes for each meaningful part of a repeated structure. Avoid relying on descendant selectors when separate class names make the output easier to trace.
@components { card { border-radius: 0.5rem; } card-header { border-bottom: 0.0625rem solid oklch(0% 0 none); } card-content { padding: 1.25rem; } card-footer { border-top: 0.0625rem solid oklch(0% 0 none); }}<article class="card"> <header class="card-header">Title</header> <div class="card-content">Content</div> <footer class="card-footer">Actions</footer></article>Use nested selectors when the selector is truly part of the component behavior:
@components { empty-state { @compose grid align-items:center justify-content:center min-h:64x p:lg text-center; &::before { @compose block size:12x r:full; content: ''; background: color-mix(in oklab, currentColor 12%, transparent); } }}In this example, size:12x is generated for .empty-state::before, not for .empty-state.
Native CSS and files
Use native CSS when output should be native
@compose is not limited to component definitions. It can also be used in native style rules. The difference is output ownership:
@components,@defaults, and@utilitiescreate on-demand managed class definitions.- Native style rules lower to ordinary CSS in the stylesheet that contains them.
@utilities { flow { @compose grid gap:md; }}.marketing-card { @compose flow p:lg r:xl shadow:md surface:raised b:1px|solid|base; @dark { @compose shadow:none; }}Use native CSS when the selector is part of a stylesheet surface you already ship. Use components when the class should behave like a generated Master CSS class and only emit when it appears in markup or source scanning.
Use local CSS Modules and SFC styles
CSS Modules and SFC <style> blocks can use @compose directly. They do not need @master entry; because they are local composed stylesheets, not project CSS entries.
.button { @compose inline-flex align-items:center justify-content:center gap:xs px:md h:10x r:lg; @compose bg:blue fg:white;}.button:hover { @compose bg:blue/.9;}import styles from './Button.module.css'export function Button() { return <button className={styles.button}>Submit</button>}Master CSS lowers @compose to native declarations first. Then CSS Modules rewrites .button to its scoped class name.
SFC style blocks use the same model:
<template> <article class="card"> <slot /> </article></template><style scoped>.card { @compose grid gap:md p:lg r:xl shadow:md; @dark { @compose shadow:none; }}</style>Local composed stylesheets share the project CSS entry and plugin-provided manifest context, so theme tokens, custom variants, and managed component classes remain available. Local files do not define the global project manifest, do not insert generated Master CSS, and do not participate as native CSS pruning roots.
Organize component files
Split component definitions only when it helps ownership. Local CSS imports from the project CSS entry are compiled into one graph.
@import '@master/css';@import './styles/button.css';@import './styles/base.css';@components { yellow { @compose b:1px|solid|yellow-70 bg:yellow fg:yellow-95; } touch-yellow { &:hover { @compose bg:yellow-50; } }}<button class="btn btn-md yellow touch-yellow">Submit</button>@theme, @settings, top-level custom directives, managed definition directives, and native style rules with @compose or @variant are consumed by the Master CSS compiler. Ordinary CSS outside those managed targets, including top-level native @keyframes, remains native CSS in your app stylesheet pipeline.
Rules and summary
Component rules
- Put component definitions in
@components; use@defaultsonly when a definition intentionally belongs to the earlier defaults layer. - The first level inside
@defaults,@components, and@utilitiesmust be a single bare name such asbtn,card, orfield. - Put states and descendants inside the named block, such as
btn { &:hover { ... } }orprose { p { ... } }. @composeis valid inside managed class definitions and native style rules.- Component definitions accept native declarations,
@compose, nested selectors, native nested at-rules, and@variantcondition blocks. - CSS-defined managed classes can compose other CSS-defined managed classes. Circular
@composedependencies are invalid. - Standard HTML tag selectors such as
bodyorhtmlbelong in native CSS; first-level entries inside managed definition directives are always class names. - Utilities still win over components because
@master/css/base.cssdeclares@layer theme, base, defaults, components, utilities;.
Summary
- Keep local styling in markup until a pattern proves it should be shared.
- Move shared values into theme tokens before creating new classes.
- Use custom utilities for reusable primitives that are not product UI roles.
- Use component classes for reusable UI roles, not for every repeated declaration.
- Keep component classes small enough to combine with tokens, utilities, states, and conditions.
- Use native CSS when the selector belongs to a stylesheet you already ship; use managed component classes when the class should be generated on demand.