CSS is Awesome

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 (19) 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.

centered
// 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.

1
2
3
@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. Four opt-in flags (all default off) go further: $legible darkens the body-text tokens (--ink, --muted…) so dark-only themes like Terminal stay readable on white — code blocks and accents untouched; $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.

@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($legible: true, $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.

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 fitm.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:

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-1heading-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.

heading-1bodyoverline
@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 (05).

level 1
level 3
@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.

@mixin input-base($py: 1, $px: 2, $r: md, $border-width: 1px, $bg, $border-color);
@mixin select-base(...);
@mixin textarea-base(...);

@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;

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

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.

Theme