Mixins
Mixins are the primary API of css-is-awesome. Every visual decision is token-driven — mixin bodies read from CSS custom properties that the active theme defines, so the same @include produces a sketchbook button, a brutalist button or a corporate button depending on the loaded theme. Consume mixins from your own SCSS and the system stays small, fast and easy to re-skin.
Import setup
Two @use lines cover the whole surface. _mixins.scssis the barrel for core layout, typography, colour, interactive and effect mixins — the atomic vocabulary. Each component partial in scss/components/* exports its own composite mixins (btn, card-base, input-base, etc.) so you only import the components you need and your compiled CSS stays lean.
// your-app.scss @use 'cia/scss/mixins' as m; @use 'cia/scss/components/buttons' as b; @use 'cia/scss/components/forms' as f; .my-cta { @include b.btn(primary); @include m.elevation(2); }
The rest of this page is a reference of every public mixin, grouped by category. Internal helpers (anything prefixed with _ or used only by _generator.scss) are omitted.
Bare-tag styling (opt-in)
By default the library does not touch bare HTML — drop the CSS and you get tokens, utilities and React components, but raw <button>, <table> and <h1> stay browser-default. That keeps the library non-invasive: dropping it into an existing project never silently restyles your nav links or third-party components.
If you want a Pico-style "drop-in and it looks decent" experience, add the bare-tags recipe in one line at the top of your app's SCSS entry:
// your-app.scss @use 'css-is-awesome/scss/recipes/bare-tags'; // done — h1-h6, p, code, pre, hr, ul, ol, table, // button, input, select, textarea, label, details // all styled with the active theme.
The recipe uses normal selectors (specificity 0,0,1) — no @layer or :where() machinery. Override anything with any class-based selector and it wins automatically:
.checkout button { background: gold; // (0,1,1) > (0,0,1) — wins }
Heads up: bare-tag styling is global. If you mount third-party React components that render their own <button> internally (Radix, react-select, react-datepicker, cmdk), they will inherit these rules. Either skip the recipe and write your own scoped wrappers, or copy the recipe contents into a .cia-prose wrapper in your own SCSS to scope it.
Prefer to roll your own? The recipe is just one-line includes per tag — copy the file or write your own:
@use 'css-is-awesome/scss/mixins' as m; @use 'css-is-awesome/scss/components/buttons' as b; @use 'css-is-awesome/scss/components/data' as d; button { @include b.btn(primary); } table { @include d.table-base; } h1 { @include m.type(heading-1); }
Layout
Layout mixins cover flex helpers, responsive grids, page-level scaffolding, containers and dividers. All spacing arguments accept a numeric space token (1–9) or a t-shirt alias (xs/sm/md/lg/xl).
flex
The one flex primitive. Pass only what differs from the defaults (row / center cross-axis / start main-axis / nowrap / no gap). $justify and $align accept the shorthand start / end / center / between / around / evenly (mapping to flex-start, space-between, etc.). Full CSS values still pass through.
// signature @mixin flex($direction: row, $gap: null, $align: center, $justify: start, $wrap: nowrap, $inline: false); // perfectly centered children .hero { @include m.flex($justify: center); } // header bar / accordion trigger .toolbar { @include m.flex($justify: between, $gap: 3); } // vertical stack with gap .feed { @include m.flex($direction: column, $gap: 4); } // inline-flex chip lockup .chip { @include m.flex($inline: true, $gap: 2); }
flow
One-word switchable flex default — a preset layer over the existing flex engine (no duplicated property logic). Pass a preset keyword to switch the whole config; optionally pass $at + $then to swap the axis at a breakpoint. Presets: row (default), col, wrap, center, between, around. The semantic primitives stack / cluster / toolbar stay — they read better as named patterns; flow is the generic switchable workhorse.
@mixin flow($preset: row, $gap: 4, $at: null, $then: null); // sensible flex row .bar { @include m.flow; } // switch row → column .menu { @include m.flow(col); } // pushed apart .toolbar { @include m.flow(between); } // responsive axis switch — row desktop, column below md .cardrow { @include m.flow(row, $at: md, $then: col); }
flow-switchable
Runtime flex switching with zero recompile. Drop on any element; toggle data-flow="..." at runtime. Emits one selector-based rule per preset in the registry. Pairs with the flow presets above.
@mixin flow-switchable($gap: 4); .bar { @include m.flow-switchable; } // HTML: <div class='bar' data-flow='between'>…</div> // JS: el.dataset.flow = 'col';
wrap
Page-width wrap with responsive horizontal padding. Sizes: sm, md, lg, xl, 2xl, full. (Renamed from container in v0.8 — the namer mixin for container queries kept the container name; this layout-wrap mixin became wrap to avoid the collision.)
@mixin wrap($size: xl, $px: null); .page { @include m.wrap(lg); }
grid
Responsive CSS grid. Pass a column count, auto for auto-fit, or a minimum track size. Collapses to one column below the supplied breakpoint.
@mixin grid($cols: 1, $gap: 4, $bp: md, $min: null); .gallery { @include m.grid(3, $gap: 5); } .cards { @include m.grid(auto, $min: 250px); }
subgrid
Child inherits the parent grid's column or row tracks. Direction: columns, rows, both.
@mixin subgrid($direction: columns); .card { @include m.subgrid; grid-column: span 3; }
page-layout
Full-page grid with sticky footer + automatic mobile collapse below the breakpoint. Variants: default, sidebar-left, sidebar-right, holy-grail. Pair with page-header, page-main, page-footer, page-sidebar, page-nav, page-aside grid-area helpers. For runtime switching, see page-layout-switchable; for custom region names, see layout.
@mixin page-layout($variant: default, $collapse-at: md); body { @include m.page-layout(sidebar-left); } header { @include m.page-header; } aside { @include m.page-sidebar; } main { @include m.page-main; } footer { @include m.page-footer; }
layout
Build any page layout from your own region names. Single-column stack or rows of columns — the engine guarantees a valid rectangular grid-template-areas. A row with fewer names than the widest row stretches its last region to fill (so a single-name row becomes a full-width band). Accepts $gap, $tracks for custom column tracks, and $wire to auto-place children by [data-area].
@mixin layout($rows...); // single-column stack .app { @include m.layout(nav, body, foot); } // rows of columns .dashboard { @include m.layout((header), (sidebar main), (footer)); } // custom column tracks + auto-place via [data-area] .app-shell { @include m.layout((nav), (side body), (foot), $tracks: 16rem 1fr, $wire: true); }
named-layout
Reusable named layout from the built-in registry (default, sidebar-left, sidebar-right, holy-grail). Forwards $gap, $tracks, $wire to layout. Use when you want a preset by name with the option to auto-place children by [data-area].
@mixin named-layout($name, $gap: 4, $tracks: null, $wire: null); .app { @include m.named-layout(holy-grail, $wire: true); }
area
Place a child element into a named grid region. Equivalent to writing grid-area: <name> by hand.
@mixin area($name); .sidebar { @include m.area(sidebar); }
page-layout-switchable
Runtime layout switching with zero recompile. Drop on body; toggle <body data-layout="..."> at runtime (e.g. document.body.dataset.layout = 'holy-grail'). All four built-in layouts plus auto mobile collapse, baked into one selector-based rule per layout. Selector-based (not custom properties) because Sass strips quotes from grid-template-areas when interpolated into a custom prop.
@mixin page-layout-switchable($collapse-at: md); body { @include m.page-layout-switchable; } // HTML: <body data-layout='sidebar-left'> // JS: document.body.dataset.layout = 'holy-grail';
section
Vertical page section with consistent top/bottom padding.
@mixin section($py: 8, $px: null); section { @include m.section($py: 9); }
inset, inset-x, inset-y, squish
Padding helpers. inset applies even padding on all sides, inset-x/inset-y split axes, squish takes a Y/X pair.
@mixin inset($size: 4); @mixin inset-x($size: 4); @mixin inset-y($size: 4); @mixin squish($y: 2, $x: 4);
divider / divider-vertical
Horizontal or vertical divider with token-aware spacing.
above
below
@mixin divider($color: border-default, $spacing: 4); @mixin divider-vertical($color: border-default, $spacing: 4);
Breakpoints & container queries
Responsive at viewport and component level. Breakpoint mixins take a token (sm, md, lg, xl,2xl) or a literal width. Container-query mixins pair with container to make components respond to their own width.
media, media-down, media-between
Viewport media queries — min-width, max-width, and ranged.
@mixin media($size); @mixin media-down($size); @mixin media-between($min, $max); .hero { font-size: 2rem; @include m.media(md) { font-size: 3rem; } }
Alias mixins
Convenience aliases for common breakpoint ranges: mobile-only, tablet, tablet-only, desktop, wide.
.nav { @include m.mobile-only { display: none; } @include m.desktop { display: flex; } }
container, contain, contain-down, contain-between
Container queries. Name the container with container, then query it with contain variants. (Renamed from cq / cq-down / cq-between in v0.8 to read better; the namer container kept its name.)
@mixin container($name: null, $type: inline-size); @mixin contain($size, $name: null); @mixin contain-down($size, $name: null); @mixin contain-between($min, $max, $name: null); .card { @include m.container(card); @include m.contain(md, card) { display: grid; } }
Print / PDF
Turn any page into a faithful PDF with nothing but a print stylesheet — zero JS, no library, no server. The page is the PDF source, and the browser's native Print → “Save as PDF” is the generator. Full walkthrough in the print-to-pdf recipe. See it work at the print-to-pdf example — a real invoice you Ctrl+P: the chrome drops, the invoice fills the sheet, links print as full URLs, and a build-time, print-only QR code links the paper back to the live page.
print-base
Page-level defaults, on by default. Include once at the stylesheet root (it emits @page, which is invalid nested in a selector). Sets the page box, collapses animations to their final frame so nothing prints invisible, and emits the print variable control plane. It does not force opacity: 1 / transform: none — deliberate translucency and rotation survive.
In @media print it always sets color-scheme: light, so paired light-dark() themes print their light branch for free. It also defines a themeable print palette — --print-ink, --print-paper, --print-line, --print-muted (default ink-on-white) — and rebinds the theme’s own colour tokens (--ink, --surface-*, --border-*, --code-*) onto it, so every theme — dark-only like Terminal included — prints legible ink-on-paper; override a --print-* token to restyle paper. Three opt-in flags (all default off) go further: $link-urls prints each link’s destination via attr(href); $link-origin prepends an origin to internal links so they resolve to full URLs on paper; $page-numbers puts counter(page) in the @page bottom-center margin box. ($legible is a deprecated no-op — the rebind supersedes it.) Full walkthrough on the Print page.
@mixin print-base($freeze-animations: true, $size: letter, $margin: 0.5in, $legible: false, $link-urls: false, $link-origin: null, $page-numbers: false); @include m.print-base; // at root @include m.print-base($link-urls: true, $link-origin: 'https://example.com', $page-numbers: true);
print, print-hidden, print-only
print wraps a bare @media print block so an override sits next to the rule it changes. print-hidden drops chrome on paper (the “hide the nav” case); print-only reveals paper-only content (an inline URL footer, a “printed on” stamp).
.site-nav { @include m.print-hidden; } .url-foot { @include m.print-only; } .subtitle { @include m.print { color: m.color(text-secondary); } }
Why these emit !important. cia avoids it everywhere else — the print layer is a deliberate exception. @media contributes no specificity, so print-hidden produces a rule with exactly the specificity of the selector you included it in. A later declaration at equal specificity wins, in print too — and it is usually not even yours, but a utility class or a component library cia cannot see. Measured in a browser: with !important the element hides; without it, it prints anyway. The failure is silent and paper-only.
@layer would be worse here. Layered CSS always loses to unlayered CSS, so a layered print rule would lose to any consumer stylesheet that isn’t layered — which is most of them. (!important also inverts layer order, so the two don’t compose as you’d expect.) cia ships unlayered by design. The blast radius stays small: 8 declarations, all inside @media print, all variable-driven — override --print-hide / --print-show to change what happens, rather than fighting the rule to win.
The variable filter system
print-base emits three custom properties that are the control plane — flip them and the output changes with no rule rewrite. --is-print is 0 on screen / 1 on paper (read it in calc(), opacity, or @container style()); --print-hide and --print-show are the display values applied to hidden / paper-only elements. Re-aim either on a single element for a per-element exception.
.legal { @include m.print-hidden; --print-hide: revert; } // keep this one on paper .cover { @include m.print { opacity: var(--is-print); } } // fades in only on paper
Geometric utilities
Pure functions for sizes that aren't a design choice — they're geometric truth shared across Figma, the design system, and the codebase. NOT themeable; consumers don't tune the 4px grid. Distinct from m.space() which IS themeable (consumer can re-tune the spacing scale per theme).
Both functions return rem (so user zoom + browser default font-size still scale) and inline at the call site — they emit nothing on their own. The functions exist so AI tools (MCP resolve_size, Figma → Code) have a deterministic surface from design intent to cia code.
grid
Explicit geometric size on cia's 4px coordinate system. n is the step count (n × 0.25rem). Use for icon widths, control heights, fixed dimensions that must align with the design grid.
@function grid($n, $base: 0.25rem); .icon-sm { width: m.grid(4); } // 16px .control-md { height: m.grid(10); } // 40px .avatar-xl { inline-size: m.grid(20); }// 80px
px
Raw pixel value converted to rem, intentionally off-grid. Use for the rare one-off value that doesn't fit any cia scale — a 17px hero margin, a 33px badge offset. Prefer m.grid() / m.space() / the typed token functions when they fit — m.px() is the escape hatch, not the default.
@function px($value); .hero { margin-block-start: m.px(33); } // 2.0625rem .badge { margin-inline-end: m.px(17); } // 1.0625rem
grid-from-px
Snap a px value to the nearest cia grid step. For tooling (Figma → Code, MCP, codegen) — pure math, no rendering output. AI agents call this to map a design's px value to the cia step, then emit m.grid(step) in generated code.
@function grid-from-px($px, $base-px: 4); // Returns the step number (integer). Compose with m.grid() // to emit the rem value, e.g. m.grid( m.grid-from-px(24) ) // // m.grid-from-px(24) → 6 // m.grid-from-px(17) → 4 (rounds to nearest step) // m.grid-from-px(100) → 25
Which function should I use?
The decision tree for picking the right size mechanism in cia:
- color / type / radius / shadow / motion → themed tokens:
m.color(),m.font-size(),m.radius(),m.shadow() - margin / padding / gap →
m.space(n)(themeable spacing scale) - explicit geometric size on the grid →
m.grid(n)(4px grid, not themed) - off-grid pixel value (rare) →
m.px(value)(raw rem conversion) - component →
m.btn()/m.modal()/ etc., overriding parameters as needed
Full decision tree with composition fallbacks: /docs/composition.
Typography
All typography is token-driven. font composes weight, style, size, line-height and letter-spacing in one mixin; type pulls from the named scale (display,heading-1…heading-4, body,body-sm, caption, overline).
font
Sets weight, style, size, line-height, letter-spacing from one call. Size/lh/ls accept a token name or a literal value. Pass $family to set a font-family — if it's a name registered via font-load, the registered fallback is auto-applied; if it's a CSS-native value (with comma or var()), it passes through; if it's a single-word name that isn't loaded, the build fails with @error so typos surface at compile time.
@mixin font($type: reg, $size: null, $lh: null, $ls: null, $family: null); .lede { @include m.font(medium, $size: 4, $lh: 2); }
font-load & font-load-local
Two lines to add any custom font: declare the slug on :root in a global stylesheet, then consume from any component. font-load handles the @import url(...) plumbing for hosted fonts; font-load-local wraps @font-face for self-hosted. Both are called from a global Sass file (not .module.scss).
// --- 1) Declare on :root in your global stylesheet --- // globals.css :root { --font-meme: 'Helvetica Neue', Helvetica, Arial, sans-serif; } // --- 2) Use from any component --- .logo { @include m.font($lh: 0.95, $family: meme); } .stamp { font-family: var(--font-meme); } // --- Override anywhere — set the variable in a tighter scope --- .landing-page { --font-meme: 'Caveat', cursive; } // --- Hosted font (Google / CDN) — call from a global .scss file --- // src/styles/fonts.scss (imported from layout.tsx) @include m.font-face('Pacifico', 'https://fonts.googleapis.com/css2?family=Pacifico'); // → emits @import url(...) // then declare the variable in globals.css and consume like step 2 // --- Self-hosted font (woff2 / ttf) --- @include m.font-face-local('Untitled Sans', '/fonts/UntitledSans.woff2');
Tip: m.font($family: meme) emits font-family: var(--font-meme). The slug is just a CSS variable name — no Sass-side registry, no validation. As long as --font-meme is declared somewhere in scope, the browser resolves it.
type
Applies a named type-scale preset: size + weight + line-height + letter-spacing in one include.
@mixin type($preset); h1 { @include m.type(heading-1); } .label { @include m.type(overline); }
truncate
Single-line ellipsis by default, or multi-line clamp with $lines.
@mixin truncate($lines: 1); .headline { @include m.truncate(2); }
Colour, borders & effects
These wrap raw theme values in runtime-override-capable custom properties, so a consumer can re-skin a single site without rebuilding the library.
border
Applies a border on all sides, one side, or a list of sides, with token-aware colour.
@mixin border($width: 1px, $style: solid, $color: border-default, $sides: all); .panel { @include m.border($sides: (top, bottom)); }
elevation
Applies a theme-driven shadow level (0–5).
@mixin elevation($level: 2); .card { @include m.elevation(3); }
Interactive states
Focus ring, hover transitions, disabled state, and a composite interactive mixin that wires hover, active and disabled in one call.
focus-ring
Accessible focus-visible ring using border-focus by default.
@mixin focus-ring($color: border-focus, $width: 3px, $offset: 0); .btn { @include m.focus-ring; }
hover
One-property transition + hover change in a single line.
@mixin hover($prop, $value, $speed: fast); .link { @include m.hover(color, m.color(action-primary-hover)); }
interactive
Composite: transitions background-color, applies hover + active backgrounds, and disables the element when disabled.
@mixin interactive($bg-hover: interactive-hover, $bg-active: interactive-active); .row { @include m.interactive; }
transition
Variadic — pass any number of CSS properties plus an optional speed (instant/fast/normal/slow/slower) and easing token (smooth/bounce/etc.). Respects prefers-reduced-motion.
@mixin transition($props...); .btn { @include m.transition(background-color, color, fast, smooth); }
disabled
Standard disabled styling — dimmed, not-allowed cursor, pointer events off.
@mixin disabled($opacity: 0.5);
sr-only
Visually hide an element while keeping it available to screen readers.
@mixin sr-only; .skip-link { @include m.sr-only; }
Resets
Strip user-agent styling from common elements.
@mixin button-reset; // appearance, background, border, padding, cursor @mixin list-reset; // list-style + margin + padding @mixin header-reset; // h1..h6 within scope @mixin form-reset; // input/select/textarea full-width
Animation
Keyframes are declared once and referenced by name. All animation mixins respect prefers-reduced-motion.
animate
Trigger a named animation with configurable speed, delay, iteration and timing. Names live in _animations.scss (fade-in, fade-out, slide-up, slide-down, spin, pulse, shimmer, etc.).
@mixin animate( $name, $speed: normal, $delay: 0s, $iteration: 1, $fill: both, $timing: var(--ease, cubic-bezier(0.33, 0.66, 0.33, 1)) ); .modal { @include m.animate(slide-up); } .spinner { @include m.animate(spin, $iteration: infinite, $timing: linear); }
animate-on
Interaction-triggered animations. Events: hover, focus, active. Effects: lift, glow, press, fade.
@mixin animate-on($event: hover, $effect: lift); .card { @include m.animate-on(hover, lift); }
Shortcuts
Pre-baked animation helpers that inject the keyframes in the same call.
@mixin spin($duration: 1s); @mixin pulse($duration: 2s); @mixin fade-in($duration: normal); @mixin slide-up($duration: normal, $distance: 1rem);
Icons
SVG and Font Awesome helpers. Prefer svg for inline icon styling, svg-bg for background-image masks, svg-text for inline-text alignment. Font Awesome mixins require fa-load once at the root to inject the shared @font-face rules.
@mixin svg(...); @mixin svg-bg(...); @mixin svg-text(...); @mixin fa-load; @mixin fa($name); @mixin fa-icon($name); @mixin fa-text($name); @mixin fa-spin($name, $size, $style); :root { @include m.fa-load; } .icon-check { @include m.fa-icon(check); }
Component mixins
Composite mixins live in scss/components/*.scss and compose the atomic mixins above into real UI primitives. Every base mixin wraps its override-controlled properties in var(--<key>, <default>), so a theme can tweak padding, radius, shadow or colour without a rebuild.
Buttons
From components/buttons. Import as @use '.../components/buttons' as b;.
@mixin btn-base($py: 1, $px: 4, $r: md, $font-weight: medium, $font-size: null); @mixin btn($variant, $bg, $bg-hover, $bg-active, $color, $border, $args...); // $variant: primary | secondary | outline | ghost | info | success | warning | error | disabled @mixin btn-icon($size: 2.5rem, $r: md); // Author your own class — variant is a mixin arg, not a --modifier suffix .hero-cta { @include b.btn(primary); } .checkout-cancel { @include b.btn(outline); }
Data display — cards, lists, tables, avatars
From components/data.
Warm paper
Card-base composes padding, radius, shadow and surface colour.
Themable
Swap the theme — every card re-skins without markup changes.
@mixin table-base($striped: false, $hover: false, $bordered: false, $compact: false); @mixin table-responsive; @mixin card-base($p: 4, $r: lg, $shadow: 1, $bg: surface-default); @mixin card-header($pb: 2); @mixin card-footer($pt: 2); @mixin card-interactive; @mixin list-base($gap: 0, $dividers: false); @mixin list-item($py: 2, $px: 4, $interactive: false); @mixin avatar($size: 2.5rem, $r: full); @mixin avatar-placeholder($size, $r, $bg, $color); @mixin avatar-group($overlap: -0.5rem);
Feedback — alerts, toasts, badges, tags, progress
From components/feedback. Status-coloured variants read status-* tokens.
@mixin alert-base($py: 2, $px: 4, $r: md, $border-width: 1px); @mixin alert($status: info, $py: 2, $px: 4, $r: md); @mixin toast-base($py: 2, $px: 4, $r: lg, $shadow: 3); @mixin badge-base($py, $px, $r: full, $font-size: 1); @mixin badge($status: info); @mixin tag($py: 2xs, $px: 2, $r: md, $font-size: 2, $removable: false); @mixin progress-track-base($height, $r: full, $bg: surface-muted); @mixin progress-fill-base($color: action-primary-default); @mixin progress($height, $r: full, $bg: surface-muted, $fill: action-primary-default); @mixin spinner(...); @mixin skeleton(...);
Forms — inputs, selects, checks, radios, switches, sliders
From components/forms. All form primitives share the same focus treatment and disabled contract.
Two things worth knowing about textarea-base. $min-height is measured in lh units, so 4lh is four lines of this textarea's own line-height — it stays four lines when a theme changes the type scale, which a rem value would not. And $grow: true adds field-sizing: content, so the box grows with what is typed instead of scrolling inside a fixed height. That is the whole feature: no resize observer, no JS, no shadow element to measure. It is opt-in because a textarea that changes height moves everything below it, which is right for a comment box and wrong inside a fixed-height form panel. Browsers without field-sizing keep the $min-height box and scroll, which is today's behaviour.
@mixin input-base($py: 1, $px: 2, $r: md, $border-width: 1px, $bg, $border-color); @mixin select-base(...); @mixin textarea-base($min-height: 4lh, $grow: false, ...); @mixin check-base($size: 1.125rem, $r: sm, $color: action-primary-default); @mixin radio-base($size: 1.125rem, $color: action-primary-default); @mixin switch-base(...); @mixin slider-base(...); @mixin label-base($size: 2, $weight: medium, $color: text-primary); @mixin form-layout($columns: 1, $gap: 4); @mixin form-group($gap: 1, $direction: column); @mixin form-row($gap: 2, $align: center); @mixin form-help($color: text-muted); @mixin form-error;
Navigation — navbar, nav, breadcrumb, tabs, pagination, mobile toolkit
From components/navigation. The mobile toolkit — hamburger, drawer, sheet, dock — is zero-JS on the native Popover API: the browser manages aria-expanded, Esc and light-dismiss. The full playbook is /docs/mobile.
@mixin navbar-base(...); @mixin navbar-brand($gap: 2); @mixin navbar-nav($gap: 1); @mixin navbar-link($py: 1, $px: 2, $r: md); @mixin nav-base($direction: row, $gap: 1); @mixin breadcrumb($gap: 1, $separator: "/"); @mixin tabs-base($gap: 0, $border: true); @mixin tab-item($py: 2, $px: 4, $active-color: action-primary-default); @mixin pagination($gap: 2xs); @mixin pagination-item($size: 2.25rem, $r: md); // mobile toolkit — zero-JS, native Popover API @mixin hamburger($color: text-primary, $bar-width: 1.375rem, $bar-height: 2px, $gap: 5px, $target: 44px); @mixin hamburger-open; @mixin drawer($side: end, $size: 20rem, $bg: surface-default, $backdrop: rgba(0, 0, 0, 0.4), $p: 5, $shadow: lg); @mixin sheet($size: auto, $max: 72dvh, $bg: surface-default, $backdrop: rgba(0, 0, 0, 0.4), $p: 5, $r: xl); @mixin dock($slots: 3, $bg: surface-default); @mixin dock-item($accent: action-primary-default);
Overlays — modals, tooltips, popovers, dropdowns
From components/overlay. tooltip and dropdown wrap their base mixins with native [popover] semantics — pair with a popovertarget trigger and the browser handles open, light-dismiss and Escape with zero JS. dropdown also guards its own closed state, re-asserting display: flex only under :popover-open so the menu never renders permanently open.
@mixin modal-backdrop($bg: rgba(0, 0, 0, 0.5)); @mixin modal-base($p: 5, $r: xl, $shadow: 5, $max-width: 500px); @mixin modal-header($pb: 2); @mixin modal-footer($pt: 2); @mixin tooltip-base($py, $px, $r: md, $bg, $color); @mixin tooltip; // tooltip-base + [popover] semantics @mixin popover-base($p: 4, $r: lg, $shadow: 3, $max-width: 320px); @mixin dropdown-menu($py: 1, $r: md, $shadow: 2, $min-width: 12rem); @mixin dropdown-item($py: 1, $px: 4); @mixin dropdown-divider($spacing: 1); @mixin dropdown; // dropdown-menu + [popover] semantics + closed-state guard @mixin anchor-to($area, $flip: flip-block, $gap: null);
anchor-to pins a popover to the element that opened it, and you wire nothing to make it work. Any element with popovertarget is automatically the implicit anchor of the popover it targets, so there is no anchor-name to declare and no second selector to keep in step — which is why the older two-sided setup is no longer the thing to teach. $area is a position-area value such as block-end or block-start span-inline-start.
Every value routes through a custom property, so overriding one is a single declaration rather than a specificity fight:
.my-menu { --cia-anchor-area: block-start span-inline-start; }
The whole mixin sits inside @supports (anchor-name: --cia). An engine without anchor positioning gets exactly the behaviour it gets today — cia sets nothing and your own positioning applies — so adding this to an existing component cannot move anything in a browser that does not support it. dropdown, tooltip and popover-base already call it for you.
Writing your own mixins
When you extend the system, follow the same contract: read every visual value from a token helper (color(), space(), radius(), shadow(), font-size()), wrap override-controlled properties in var(--<key>, <default>) so themes can tweak them at runtime, and compose atomic mixins rather than duplicating their bodies.
// good — token-driven, override-capable @mixin note-base($p: 4, $r: md, $bg: surface-muted) { padding: var(--note-padding, #{m.space($p)}); border-radius: var(--note-radius, #{m.radius($r)}); background: m.color($bg); @include m.border($sides: left, $color: border-focus); }
The full contributor guide — naming conventions, parameter order, how to add a mixin to the barrel — lives in CONTRIBUTING.md alongside the component authoring guide.
Full index
Every public mixin at a glance. Jump to the section above for usage examples.
- Layout:
flex,flow,flow-switchable,pad,pad-x,pad-y,pad-asym,wrap,grid,subgrid,page-layout,page-header,page-main,page-footer,page-sidebar,page-nav,page-aside,layout,named-layout,area,page-layout-switchable,stack,cluster,switcher,cover,frame,sidebar,toolbar,wizard-shell,section,divider,divider-vertical - Geometric utilities (functions):
grid,px,grid-from-px - Breakpoints:
media,media-down,media-between,mobile-only,tablet,tablet-only,desktop,wide - Container queries:
container,contain,contain-down,contain-between - Print / PDF:
print,print-base,print-hidden,print-only(+--is-print/--print-hide/--print-showvars) - Typography:
font,type,truncate - Borders & effects:
border,elevation - Interactive:
focus-ring,hover,interactive,transition,disabled,sr-only - Resets:
button-reset,list-reset,header-reset,form-reset - Animation:
animate,animate-on,spin,pulse,fade-in,slide-up - Icons:
svg,svg-bg,svg-text,fa-load,fa,fa-icon,fa-text,fa-spin - Buttons:
btn-base,btn,btn-icon(variantsprimary/secondary/outline/ghost/ status are parameters tobtn(), not separate mixins) - Data:
table-base,table-responsive,card-base,card-header,card-footer,card-interactive,list-base,list-item,avatar,avatar-placeholder,avatar-group - Feedback:
alert-base,alert,toast-base,tag,progress,progress-track-base,progress-fill-base,spinner,skeleton - Data:
table-base,card-base,badge-base,badge - Forms:
input-base,select-base,textarea-base,check-base,radio-base,switch-base,slider-base,label-base,form-layout,form-group,form-row,form-help,form-error - Navigation:
navbar-base,navbar-brand,navbar-nav,navbar-link,nav-base,breadcrumb,tabs-base,tab-item,pagination,pagination-item,hamburger,hamburger-open,drawer,sheet,dock,dock-item - Overlays:
modal-backdrop,modal-base,modal-header,modal-footer,tooltip-base,tooltip,popover-base,dropdown-menu,dropdown-item,dropdown-divider,dropdown,anchor-to