Graffiti is a CSS theming and primitives package: design tokens, fluid typography, layout primitives, utilities, and component patterns. It is consumed both as an NPM import and as a CLI that copies drop-in.css into a project.
Token:
A CSS custom property declared on :root or another scoping element. Tokens carry meaning the framework or a consumer can override.
Avoid: variable, custom property (use these only when speaking strictly about the CSS feature, not Graffiti's design system).
Literal token:
A token whose value is a concrete primitive — a color, a length, a duration. Examples: --blue, --pad-l, --vs-base, --lh-s. Literals are the source colors and spacings the rest of the system composes from.
Semantic token:
A token whose value resolves through one or more literals to express purpose. Examples: --primary, --accent, --focus-ring, --border-1, --shadow-2. Semantic tokens are what components should consume; literals are what consumers may override at the root.
Alpha scale:
A nine-step color scale built by varying alpha, not lightness — e.g. --blue-1 (10% alpha of --blue) through --blue-9 (full opacity). Monotonic: 1 is faintest, 9 is full opacity. Designed for tints, overlays, and surfaces that should adapt to the underlying background and color-scheme. Pairs with the opaque scale for cases where alpha-on-background fails (photo backdrops, stacked transparency, print, contrast-stable text).
Avoid: lightness scale, opacity scale (opacity is a property; alpha is a channel — they're not the same).
Opaque scale:
A nine-step symmetric color scale built with color-mix(in oklab, …) rather than alpha — e.g. --blue-opaque-1 (lightest tint, mixed toward --bg) through --blue-opaque-9 (darkest shade, mixed toward --fg), with --blue-opaque-5 = var(--blue) (the raw base). Ships for every base color that has an alpha scale. Use when a surface must block content underneath (photo backdrop), survive printing, or stack without compounding alphas; use the alpha scale when overlays should adapt to whatever is below. Step -3 does not mean the same thing in the two scales — the alpha scale is monotonic strength, the opaque scale is symmetric around the base color. See ADR-0007.
Avoid: "lightness scale" (overloaded with the historical Tailwind v3 mental model where steps shift lightness without mixing).
Layout primitive:
A class in @layer layouts that establishes structural frame and responsive behavior. A class belongs in the layout layer if it has responsive collapse behavior (a @container or @media query that reshapes the class itself) or explicit child contracts (selectors like > * that style direct children). Examples: .layout-sidebar, .layout-card, .layout-readable, .layout-holy-grail, .split, .stack, .carousel, .reel. Layout primitives are the outermost shapes a page is built from; their breakpoint behavior is part of the contract. Per-instance breakpoint overrides are intentionally not supported — consumers needing different collapse thresholds compose with @container queries (wrap the layout in a container-type: inline-size parent of the desired size) or pick a different layout primitive. See ADR-0009.
Utility:
A class in @layer utilities that toggles a single property or a tightly coupled pair, with no responsive behavior of its own and no child rules. Examples: .flex, .grid, .cluster, .text-center, .full, .transition, .aspect-square. Utilities are atomic and may be applied inside any layout, but cannot override a layout primitive's structural decisions (see ADR-0002, ADR-0009).
Component:
A class in @layer components that ships a finished visual pattern — surface, padding, border, motion. Examples: .card, .feature-card, .stat-card, .toc, .newsletter. Components consume semantic tokens; consumers customise components by overriding tokens, not by overriding component rules.
Avoid: "element" (historical docs-nav label for the same thing — see ADR-0014); "UI block" (historical bucket that mixed Components with composed recipes — split per ADR-0014).
Fluid level (--fl):
A scalar (typically -1 through 6) that selects a step on Graffiti's modular type scale. Setting --fl: 3 on any element resizes its text to that step, scaled fluidly between viewport breakpoints.
Theme axis:
A single override dimension exposed for theming. Today's surfaced axes (see src/docs/ThemeControls.svelte) are: color palette, font family, type scale, and border-radius scale. Axes are orthogonal — a selection on one axis does not constrain selections on the others. Motion graduates to a first-class axis with the duration scale shipped in ADR-0008; a motion-axis selector in ThemeControls.svelte is now token-shaped and follow-on work. Latent axes that exist as tokens but are not yet exposed as preset axes include shadow and density. Z-index is intentionally not a theme axis — stacking tiers are a framework contract, not an aesthetic dimension.
Avoid: "theme" used alone for these (ambiguous — the docs site currently calls the color-palette axis "Theme").
Aesthetic preset:
A coordinated override stack that couples multiple theme axes (color + type + radius + shadow + font family, plus optional typographic selector rules) to deliver a single recognisable visual personality — e.g. brutalist, editorial, soft-consumer, neon-arcade, paper. Distinguished from an axis selection by being non-orthogonal: an aesthetic preset commits a constellation of decisions together. The intended job of an aesthetic preset is to absorb taste-level decisions on behalf of a consumer (human or AI) so that "use Graffiti, walk away" produces a coherent look.
Aesthetic presets ship as CSS classes (.theme-brutalist, .theme-editorial, etc.) via an opt-in import path separate from drop-in.css, so consumers only pay bytes for the presets they choose to load. Their selector rules live in @layer themes, slotted between base and components, so a preset cannot accidentally restyle component internals. The same class can be applied at :root/html for app-wide effect or on any container element for scoped theming. See ADR-0003.
Graffiti does not ship algorithmic palette derivation (a single --brand seed expanded into --accent/--secondary/surface tints). Coherence is the job of a preset author committing a constellation of decisions, not the job of a complementary-hue formula. Consumers seeking brand coherence apply the closest-fitting preset and override --primary — the preset class is implicitly a theme scope, so the alpha scale follows the override and the rest of the preset's coordinated tokens survive.
Avoid: "theme preset" (collides with the current color-only "themes" in ThemeControls.svelte).
Theme scope:
A container element marked as a re-derivation boundary for Graffiti's derived color scales (--primary-1..9, --error-1..9, --fg-1..9). Applied via the .theme-scope utility class, or implicitly by any aesthetic preset class. Inside a theme scope, overriding --primary/--error/--fg causes the corresponding alpha scale to re-derive on that element, rather than inheriting the :root-computed scale. Outside a theme scope, those scales are :root-stable: overriding the base on a non-scope element changes only the base, not the derived steps. See ADR-0006.
Avoid: "theme container" (overlaps with layout primitive terminology).
- A literal token may feed one or more semantic tokens (
--blue→--primary). - A component consumes semantic tokens, never literal tokens directly.
- A layout primitive wins over a utility applied within it (ADR-0002).
.layout-three-col(equal-width columns) and.layout-holy-grail(centered readable content with optional.rail-start/.rail-endchildren, ADR-0010) serve distinct intents — equal-width grids vs. editorial reading layouts. Pick by what the middle column should do.- A utility wins over a component for atomic property toggles.
.auto-coloris a text-contrast-only utility — it setsbackground-colorand a contrast-safecolor, but does not set--bg,--fg, or propagate to derived scales (alpha or opaque). Use it for tags, chips, and badges with text-only content. For a fully coordinated colored surface (border, muted text, scaled tokens), apply.theme-scopeand set--bg/--fgdirectly. Expanding.auto-colorto do the bigger job was considered and rejected — system integrity prefers a small, clear utility over a quiet contract expansion.
The docs site at src/routes/(docs)/ exposes sections to consumers. Nav labels are anchored to the architectural terms above; the full IA rule is in ADR-0014.
- Tokens (nav) = literal token + semantic token
- Base (nav) = classless defaults — no architectural term; this is "what the browser gets before any Graffiti class is applied"
- Layouts (nav) = layout primitive
- Utilities (nav) = utility
- Components (nav) = component
- Forms (nav) = a task domain spanning Base / Components / Recipes — promoted to a top-level section via the three-test gate in ADR-0014. Not an architectural primitive.
- Recipes (nav) = topics that ship no class of their own; documented compositions of existing artifacts. Not an architectural primitive.
"Alpha scale" vs a future "lightness scale"— resolved by ADR-0007. Graffiti now ships both an alpha scale (monotonic,--{base}-1..9) and an opaque scale (symmetric,--{base}-opaque-1..9). The-opaque-suffix is the disambiguator; we deliberately did not adopt Radix's-a1..a9alpha suffix because it would have been a breaking change to existing consumers."Layout" historically overlapped with "utility"— resolved by ADR-0009. The rule is now explicit: responsive collapse or explicit child contracts → layout layer; atomic single-property toggle → utility layer..splitmoves to layouts (has@containercollapse);.clustermoves to utilities (atomic)..flexand.gridstay utilities;.stack,.carousel,.reelstay layouts.ThemeControls UX when a preset is selected— resolved: hydrate-then-layer. Selecting an aesthetic preset inThemeControls.svelte(a) applies the.theme-*class on<html>so the preset's selector rules (drop caps, opentype features, character treatments) take effect, and (b) hydrates the color / type-scale / radius / font-family controls with the preset's nominal values. Any subsequent change to one of those controls is treated as a user override and continues to win via inline-style on:root, while the preset's class-level rules survive. Selecting "None" removes the class. This keeps preset choices inspectable, allows tweaking without "breaking out" of the preset, and reuses the existingCustomcolor path for full control.