fuz_css

CSS with more utility 🪴

CSS framework and design system for semantic HTML

repo 0.55.0 npm

npm i -D @fuzdev/fuz_css

introduction #

fuz_css is a CSS framework and design system for semantic HTML. It styles elements by default and integrates custom properties, themes, and utility classes into a complete system. It's Svelte-first but works with plain HTML/JS/TS, React, Preact, Solid, and other JSX frameworks. For more see the framework support docs, and for the companion Svelte components, see fuz_ui.

The only required parts are a reset stylesheet with the semantic defaults and a replaceable theme stylesheet containing the variables used in the reset, and these require no dependencies. There's also utility classes for composition and convenience with a Vite plugin, and the library exports the full API for complex usage.

More at the docs and repo.

Usage
#

npm i -D @fuzdev/fuz_css

Use the Vite plugin or Gro generator for bundled CSS that includes theme variables, base styles, and utility classes in a single import:

// bundled mode (recommended) // includes only used base styles, variables, and utilities import 'virtual:fuz.css'; // Vite plugin // or import './fuz.css'; // Gro generator

For projects managing their own theme or base styles, use utility-only mode with separate imports:

// utility-only mode - full package CSS, only used utilities import '@fuzdev/fuz_css/style.css'; // all base styles import '@fuzdev/fuz_css/theme.css'; // all variables import 'virtual:fuz.css'; // with base_css: null, variables: null

See the classes reference for setup details and configuration options.

Details
#

  • plain CSS
  • minimal dependencies, all optional -- none needed if you only use the stylesheets
  • exports a reset stylesheet with semantic defaults that styles HTML elements, and also exports the underlying data, helpers, and types for open-ended usage
  • supports themes with a basic theme stylesheet, 🗎 @fuzdev/fuz_css/theme.css, that can be replaced with your own -- dark mode is a first-class concept, not a theme; instead, each theme can support light and/or dark color-schemes
  • supports optional utility classes with three types (token, composite, CSS-literal) and modifiers for responsive, state, color-scheme, and pseudo-elements
  • uses its own concept of style variables, a specialization of CSS custom properties and design tokens that integrate with the other systems (e.g. the reset stylesheet and token classes use variables, and themes are groups of variables)
  • the stylesheets work with any framework and plain HTML; utility class generation supports Svelte, JSX, and TypeScript/JS -- see the utility class framework support, and for the companion Svelte integration see Themed in fuz_ui
  • see the comparison to alternatives to understand fuz_css relative to TailwindCSS and UnoCSS

api #

Browse the full api docs.

examples #

The example repos demonstrate the classes system using the Vite plugin:

For projects using Gro, see fuz_template for integration with gen_fuz_css.ts.

semantic #

fuz_css styles HTML elements in its 🗎 reset stylesheet, so semantic markup gets themed and color-scheme-aware styling automatically -- utility classes optional. The goal is to be accessible and attractive out of the box, minimal yet extensible.

Low specificity
#

All opinionated styles use :where() selectors, giving them zero specificity beyond the element itself. Your styles and utility classes override defaults without specificity battles.

/* any styles you apply will override these */ :where(a:not(.unstyled)) { color: var(--link_color); font-weight: 700; } :where(button:not(.unstyled)) { background-color: var(--button_fill); border-radius: var(--border_radius_sm); }

.unstyled escape hatch
#

Add the .unstyled builtin class to opt out of decorative styling while keeping reset normalizations. Works for both decorative containers and interactive elements like links, buttons, inputs, and summary.

<a href="/home">styled link</a> <a href="/home" class="unstyled">unstyled link</a>

styled link vs unstyled link

<button>styled button</button> <button class="unstyled">unstyled button</button>

Document flow by default
#

Block elements get margin-bottom via :not(:last-child), creating natural vertical rhythm without trailing margins.

:where( :is(p, ul, ...[many others]) :not(:last-child):not(.unstyled) ) { margin-bottom: var(--flow_margin, var(--space_lg)); }

The --flow_margin variable is unset by default, falling back to var(--space_lg). Override classes like .compact set --flow_margin to tighten vertical rhythm for all flow elements and headings.

For elements not in the flow list, use the .mb_flow and .mt_flow composite classes to get the same compact-responsive spacing. Use .mb_lg when you want a fixed value that ignores .compact.

Flex containers reset flow margins
#

The layout composites .row, .box, and .column reset margins on their direct children. Flow margins make less sense in flex layout -- for spacing prefer gap utilities like .gap_md and var(--gap_sm) instead.

:where(.row, .box, .column) > * { margin: 0; }

Element-specific docs
#

See the related docs for specifics:

  • buttons - button states, colors, variants
  • elements - links, lists, tables, code, details
  • forms - inputs, labels, checkboxes, selects
  • typography - headings, fonts, text styles

themes #

fuz_css supports both the browser's color-scheme and custom themes based on variables, which use CSS custom properties.

fuz_css works with any JS framework, but it provides only stylesheets, not integrations. This website uses the companion Svelte UI library fuz_ui to provide the UI below to control the fuz_css color scheme and themes.

Color scheme
#

fuz_css supports color-scheme with dark and light modes. To apply dark mode manually, add the dark class to the root html element.

The Fuz integration detects the default with prefers-color-scheme, and users can also set it directly with a component like this one:

The builtin themes support both dark and light color schemes. Custom themes may support one or both color schemes.

Builtin themes
#

A theme is a simple JSON collection of variables that can be transformed into CSS that set custom properties. Each variable can have values for light and/or dark color schemes. In other words, "dark" isn't a theme, it's a mode that any theme can implement.

These docs are a work in progress, for now see theme.ts and themes.ts.

  • variables #

    Style variables, or just "variables" in fuz_css, are CSS custom properties that can be grouped into a theme. Each variable can have values for light and/or dark color-schemes. They're design tokens with an API.

    The goal of the variables system is to provide runtime theming that's efficient and ergonomic for both developers and end-users. Variables can be composed in multiple ways:

    • by CSS classes, both utility and component
    • by other variables, both in calculations and to add useful semantics (e.g. button_fill_hover uses shade_50 but can be themed independently)
    • in JS like the Svelte components in fuz_ui

    Variables also provide an interface that's generally secure for user-generated content, if you're into that kind of thing.

    The result is a flexible system that aligns with modern CSS to deliver high-capability UX and DX with low overhead.

    export interface Theme { name: string; variables: StyleVariable[]; } export interface StyleVariable { name: string; light?: string; dark?: string; summary?: string; }

    All 674 style variables
    #

    classes #

    fuz_css has two categories of CSS classes: utilities and builtins. Builtins are baked into the main stylesheet; utility classes have three types, are optional, and require build tool integration.

    Utility classes complement semantic styles and style variables. Use them to compose styles across component boundaries, or when you prefer classes to the <style> tag and style attribute. They're optional and generated on-demand to include only what you use.

    Compared to TailwindCSS and UnoCSS, fuz_css utility classes follow the grain of semantic HTML rather than being foundational to the design, and the DSL is currently more limited, with interpreters providing a programmatic escape hatch -- see the comparison below.

    Compared to the <style> tag, classes:

    • offer shorthand for style variables (p_lg vs padding: var(--space_lg))
    • compose across component boundaries, avoiding fragile :global() selectors
    • let you avoid noisy class names like foo-wrapper and bar-inner

    Compared to the style attribute, classes:

    • support powerful modifiers for responsive widths, interaction states (like hover), and dark mode
    • provide more control over specificity
    • compose ergonomically with libraries like clsx, which Svelte supports natively

    For cases where classes lack clear advantages, style and <style> are simpler and avoid generating class definitions, which can bloat your builds when overused.

    Usage
    #

    npm i -D @fuzdev/fuz_css

    Use the Vite plugin or Gro generator to generate bundled CSS that includes theme variables, base styles, and utility classes:

    Vite plugin
    #

    The Vite plugin extracts classes and generates CSS on-demand. It works with Svelte and plain HTML/TS/JS out of the box. JSX frameworks (React, Preact, Solid) require the acorn-jsx plugin -- see React and JSX below.

    // vite.config.ts import {defineConfig} from 'vite'; import {sveltekit} from '@sveltejs/kit/vite'; import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js'; export default defineConfig({ plugins: [sveltekit(), vite_plugin_fuz_css()], });

    Import the virtual module in your entry file, src/routes/+layout.svelte for SvelteKit:

    // +layout.svelte - bundled mode (recommended, default config) // includes only used base styles, variables, and utilities import 'virtual:fuz.css';

    For projects managing their own theme or base styles, disable them and import separately:

    // vite.config.ts - utility-only mode vite_plugin_fuz_css({ base_css: null, variables: null, }), // +layout.svelte - full package CSS, only used utilities import '@fuzdev/fuz_css/style.css'; // all base styles import '@fuzdev/fuz_css/theme.css'; // all variables import 'virtual:fuz.css';

    The plugin extracts classes from files as Vite processes them, including from node_modules dependencies. It supports HMR -- changes to classes in your code trigger automatic CSS updates.

    Plugin options

    • acorn_plugins - required for JSX frameworks, e.g. acorn-jsx
    • additional_classes - classes to always include (for dynamic patterns that can't be statically extracted)
    • exclude_classes - classes to exclude from output
    • class_definitions - custom class definitions to merge with defaults; can define new classes or override existing ones (see composite classes)
    • include_default_classes - set to false to use only your own class_definitions, excluding all default token and composite classes
    • class_interpreters - custom interpreters for dynamic class generation; replaces the default interpreters entirely if provided (most users don't need this)
    • filter_file - custom filter for which files to process. Receives (id: string) and returns boolean, e.g. (id) => !id.includes('/fixtures/')
    • on_error - 'log' or 'throw'; defaults to 'throw' in CI, 'log' otherwise
    • on_warning - 'log', 'throw', or 'ignore'; defaults to 'log'
    • cache_dir - cache location; defaults to .fuz/cache/css
    • base_css - customize or disable base styles; set to null for utility-only mode, or provide a callback to modify defaults
    • variables - customize or disable theme variables; set to null for utility-only mode, or provide a callback to modify defaults
    • additional_elements - elements to always include styles for (for runtime-created elements), or 'all' to include all base styles
    • additional_variables - variables to always include in theme output, or 'all' to include all theme variables

    TypeScript setup

    Add the virtual module declaration, like to vite-env.d.ts:

    /// <reference types="vite/client" /> declare module 'virtual:fuz.css' { const css: string; export default css; }

    Gro generator
    #

    For projects using Gro, the gen_fuz_css.ts generator creates a *.gen.css.ts file anywhere in src/:

    // src/routes/fuz.gen.css.ts for SvelteKit, or src/fuz.gen.css.ts, etc. import {gen_fuz_css} from '@fuzdev/fuz_css/gen_fuz_css.js'; export const gen = gen_fuz_css();

    Then import the generated file, in src/routes/+layout.svelte for SvelteKit:

    // +layout.svelte - bundled mode (recommended, default config) // includes only used base styles, variables, and utilities import './fuz.css';

    For projects managing their own theme or base styles, disable them and import separately:

    // fuz.gen.css.ts - utility-only mode export const gen = gen_fuz_css({ base_css: null, variables: null, }); // +layout.svelte - full package CSS, only used utilities import '@fuzdev/fuz_css/style.css'; // all base styles import '@fuzdev/fuz_css/theme.css'; // all variables import './fuz.css';

    Generator options

    The Gro generator accepts the same options as the Vite plugin, plus additional options for batch processing:

    • include_stats - include file statistics in output (file counts, cache hits/misses, class counts)
    • project_root - project root directory; defaults to process.cwd()
    • concurrency - max concurrent file processing for cache reads and extraction; defaults to 8
    • cache_io_concurrency - max concurrent cache writes and deletes; defaults to 50

    Class detection
    #

    The extractor scans your source files and extracts class names using three automatic mechanisms, plus manual hints for edge cases:

    1. Direct extraction from class attributes

    String literals and expressions in class contexts are extracted directly:

    • class="..." - static strings
    • class={[...]} - array syntax (for clsx-compatible frameworks like Svelte)
    • class={{...}} - object syntax (for clsx-compatible frameworks like Svelte)
    • class={cond ? 'a' : 'b'} - ternary expressions
    • class={(cond && 'a') || 'b'} - logical expressions
    • class:name - class directives (Svelte)
    • clsx(), cn(), cx(), classNames() - utility function calls

    2. Naming convention

    Variables ending with class, classes, className, classNames, class_name, or class_names (case-insensitive) are always extracted, regardless of where they're used:

    // extracted because of naming convention const buttonClasses = 'color_d font_size_lg'; const buttonClass = active ? 'active' : null; const snake_class = 'snake'; const turtle_class_name = 'turtle';

    3. Usage tracking

    Variables used in class attributes are tracked back to their definitions, even if they don't follow the naming convention:

    <script> const styles = 'some-class'; // tracked from class={styles} const variant = 'other-class'; // tracked from clsx() </script> <div class={styles}></div> <button class={clsx('color_d', variant)}></button>

    Usage tracking works for variables inside clsx(), arrays, ternaries, and logical expressions within class attributes. Note that standalone clsx() calls outside class attributes don't trigger tracking -- use the naming convention for those cases.

    4. Manual hints

    For dynamically constructed classes that can't be statically analyzed, use the @fuz-classes comment:

    // @fuz-classes opacity:50% opacity:75% opacity:100% const opacity_classes = [50, 75, 100].map((n) => `opacity:${n}%`); /* @fuz-classes color_a_50 color_b_50 color_c_50 */ const color = get_dynamic_color();

    A common case is iterating over variant arrays to generate demos or UI. The extractor sees class="shadow_alpha_{variant}" but can't resolve what variant will be at runtime:

    <script> import {shadow_alpha_variants} from '@fuzdev/fuz_css/variable_data.js'; // @fuz-classes shadow_alpha_00 shadow_alpha_05 shadow_alpha_10 ... shadow_alpha_100 </script> {#each shadow_alpha_variants as variant} <div class="shadow_alpha_{variant}">...</div> {/each}

    Alternatively, use the additional_classes option in your config to the Vite plugin or Gro generator:

    vite_plugin_fuz_css({ additional_classes: ['opacity:50%', 'opacity:75%', 'opacity:100%'], });

    Use exclude_classes to filter out false positives from extraction. This also suppresses warnings for these classes, even if they were explicitly annotated:

    vite_plugin_fuz_css({ exclude_classes: ['some:false:positive'], });

    Element hints

    Similar to @fuz-classes, use @fuz-elements to declare elements that should be included even when they can't be statically detected:

    // @fuz-elements dialog const el = document.createElement('dialog');

    CSS variable detection

    CSS variables are detected via simple regex scan of var(--name patterns in all source files. Only theme variables are included; unknown variables are silently ignored. This approach catches usage in component props like size="var(--icon_size_xs)" that AST-based extraction would miss.

    When variable names are constructed at runtime (e.g. with template literals), use @fuz-variables to explicitly include them:

    <script> import {shade_scale_variants} from '@fuzdev/fuz_css/variable_data.js'; // @fuz-variables shade_min shade_00 shade_05 shade_10 ... shade_100 shade_max </script> {#each shade_scale_variants as variant} <div style:background="var(--shade_{variant})">...</div> {/each}

    5. Build-time limitations

    Class and element detection happens at build time via static analysis. Content created dynamically at runtime (document.createElement(), innerHTML, framework hydration) won't be detected.

    Use additional_elements to force-include element styles for runtime-created elements:

    vite_plugin_fuz_css({ additional_elements: ['dialog', 'details', 'datalist'], });

    Utility class types
    #

    Token classes
    #

    Token classes are technically composite classes with a close relationship to style variables -- each maps design tokens to CSS properties. They're generated programmatically from variant data, making them predictable and systematic. The composites documented below are hand-written and typically represent higher-level semantic concepts. For raw CSS values, use literal classes instead.

    <p class="pl_xl3 color_g_50">some token classes</p>

    some token classes

    Token classes use snake_case because style variables are designed for optional use in JS (imported from variables.ts, but costing nothing otherwise), so each name is consistent across both JS and CSS, instead of converting between kebab-case and camelCase. This also makes token classes visually distinct from literal classes; we find this improves readability.

    Spacing

    See layout.

    • .p_{xs5-xl15} .p_0
    • .pt_{xs5-xl15} .pt_0
    • .pr_{xs5-xl15} .pr_0
    • .pb_{xs5-xl15} .pb_0
    • .pl_{xs5-xl15} .pl_0
    • .px_{xs5-xl15} .px_0
    • .py_{xs5-xl15} .py_0
    • .m_{xs5-xl15} .m_0 .m_auto
    • .mt_{xs5-xl15} .mt_0 .mt_auto
    • .mr_{xs5-xl15} .mr_0 .mr_auto
    • .mb_{xs5-xl15} .mb_0 .mb_auto
    • .ml_{xs5-xl15} .ml_0 .ml_auto
    • .mx_{xs5-xl15} .mx_0 .mx_auto
    • .my_{xs5-xl15} .my_0 .my_auto
    • .gap_{xs5-xl15}
    • .column_gap_{xs5-xl15}
    • .row_gap_{xs5-xl15}
    • .top_{xs5-xl15}
    • .right_{xs5-xl15}
    • .bottom_{xs5-xl15}
    • .left_{xs5-xl15}
    • .inset_{xs5-xl15}

    Sizing

    See layout.

    • .width_{xs5-xl15}
    • .height_{xs5-xl15}
    • .width_atmost_{xs-xl} .width_atleast_{xs-xl}
    • .height_atmost_{xs-xl} .height_atleast_{xs-xl}

    Colors

    See colors, shading, and typography.

    • .color_{a-j}_{00-100}
    • .bg_{a-j}_{00-100}
    • .text_min .text_max .text_{00-100}
    • .shade_min .shade_max .shade_{00-100}
    • .hue_{a-j}
    • .darken_{00-100} .lighten_{00-100}

    Typography

    See typography.

    • .font_family_sans.font_family_serif.font_family_mono
    • .font_size_{xs-xl9}
    • .line_height_{xs-xl}
    • .icon_size_{xs-xl3}

    Borders

    See borders.

    • .border_color_{00-100}
    • .border_color_{a-j}_{00-100}
    • .border_width_{1-9}
    • .border_radius_{xs3-xl}
    • .border_top_left_radius_{xs3-xl}
    • .border_top_right_radius_{xs3-xl}
    • .border_bottom_left_radius_{xs3-xl}
    • .border_bottom_right_radius_{xs3-xl}
    • .outline_width_{1-9}
    • .outline_width_focus.outline_width_active
    • .outline_color_{00-100}
    • .outline_color_{a-j}_{00-100}

    Shadows

    See shadows.

    • .shadow_{xs-xl}
    • .shadow_top_{xs-xl}
    • .shadow_bottom_{xs-xl}
    • .shadow_inset_{xs-xl}
    • .shadow_inset_top_{xs-xl}
    • .shadow_inset_bottom_{xs-xl}
    • .shadow_color_umbra.shadow_color_highlight.shadow_color_glow.shadow_color_shroud
    • .shadow_color_{a-j}_{00-100}
    • .shadow_alpha_{00-100}

    Composite classes
    #

    Composites let you name and reuse patterns, extending the class system with your own vocabulary. They have four forms: raw CSS declarations, compositions of other classes, a combination of both, or full rulesets as an escape hatch for multi-selector patterns (child selectors, sibling combinators, etc.).

    Four definition forms

    All four of these produce the same CSS output for .centered, with the ruleset form additionally demonstrating child selectors (> * + *) which can't be expressed with the other forms.

    import type {CssClassDefinition} from '@fuzdev/fuz_css/css_class_generation.js'; export const custom_composites: Record<string, CssClassDefinition> = { // 1. `declaration` only - custom CSS properties centered: { declaration: ` display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; `, }, // 2. `composes` only - compose existing token/composite classes centered: { composes: ['box', 'text-align:center'], }, // 3. `composes` + `declaration` - compose then extend centered: { composes: ['box'], declaration: 'text-align: center;', }, // 4. `ruleset` - full CSS with multiple selectors (not composable) centered: { ruleset: ` .centered { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; } /* child selectors, pseudo-classes on children, etc */ .centered > * + * { margin-top: var(--space_md); } `, }, };

    Generated CSS:

    .centered { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }

    And the ruleset form (4) includes the additional selector:

    .centered > * + * { margin-top: var(--space_md); }

    Nesting

    Composites can compose other composites, enabling layered abstractions. Resolution is depth-first: nested composes are fully resolved before the parent's declaration is appended. Circular references are detected and produce an error.

    What composes can reference

    The composes property resolves referenced classes and combines their declarations. When both composes and declaration are present, the explicit declaration comes last (winning in the cascade for duplicate properties).

    • token classes (p_lg, color_a_50) - resolved to their declarations
    • composites with declaration - the declaration is included
    • composites with composes - recursively resolved
    • unmodified CSS literals (text-align:center, margin:0~auto, --my-var:value) - parsed and included as declarations

    Not allowed: Composites with ruleset cannot be referenced in composes because they define their own selectors. Modified classes (like hover:opacity:80% or md:p_lg) cannot be used in composes arrays because they require wrapper selectors (apply them directly in markup instead). The composes property merges declarations into a single rule, but multi-selector patterns like .clickable:hover { ... } cannot be inlined. These limitations may be revisited in the future; feedback is welcome in the discussions.

    Modifiers

    Composites support modifiers like any other class. For composes and declaration composites, declarations are combined and wrapped. For ruleset composites, modifiers are applied to each selector (with smart conflict detection):

    <!-- hover:foo resolves foo's `composes`, applies :hover --> <div class="hover:foo md:dark:foo md:clickable">

    Registering composites

    Register custom composites with the Vite plugin or Gro generator:

    Vite plugin
    // vite.config.ts import {custom_composites} from './src/lib/composites.js'; vite_plugin_fuz_css({ class_definitions: custom_composites, }),
    Gro generator
    // fuz.gen.css.ts import {custom_composites} from '$lib/composites.js'; export const gen = gen_fuz_css({ class_definitions: custom_composites, }); See Usage for more details.

    Builtin composites

    Composable (can be used in composes arrays):

    • .box - centered flex container
    • .row - horizontal flex row
    • .column - vertical flex column
    • .ellipsis - text overflow ellipsis
    • .pane - pane container
    • .panel - panel container
    • .icon_button - icon button styling
    • .pixelated - crisp pixel-art rendering
    • .circular - 50% border-radius
    • .compact - tighter sizing, cascading to children
    • .mb_flow - flow-aware margin-bottom
    • .mt_flow - flow-aware margin-top

    Ruleset-based (multi-selector, cannot be used in composes arrays):

    • .selectable - selectable element styling
    • .clickable - clickable element styling
    • .plain - plain/reset styling
    • .menu_item - menu item styling
    • .chevron - chevron indicator
    • .chip - chip/tag styling

    Literal classes
    #

    Fuz supports an open-ended CSS-literal syntax: property:value. Any CSS property and value works, offering arbitrary styles without a DSL.

    <!-- basic syntax: property:value --> <div class="display:flex justify-content:center"> <!-- multi-value properties use ~ for spaces --> <div class="margin:1px~3rem"> <!-- numeric values --> <div class="opacity:50% font-weight:700 z-index:100"> <!-- arbitrary CSS values --> <div class="width:calc(100%~-~20px)"> <!-- custom properties --> <div class="--foo-bg:#abc">

    The ~ character represents a space in class names (since CSS classes can't contain spaces). Use it for multi-value properties like margin:1px~auto.

    Custom properties work directly: --my-var:value sets the property on the element. This is useful for scoped variables or passing values to child components.

    Modifiers
    #

    Modifiers prefix any class type -- token, composite, or literal -- to apply styles conditionally based on viewport, state, or color scheme. This is what makes utility classes more powerful than inline styles.

    Responsive modifiers

    Mobile-first breakpoints:

    PrefixWidthCSS
    sm:40rem (640px)@media (width >= 40rem)
    md:48rem (768px)@media (width >= 48rem)
    lg:64rem (1024px)@media (width >= 64rem)
    xl:80rem (1280px)@media (width >= 80rem)
    2xl:96rem (1536px)@media (width >= 96rem)
    <!-- stack on mobile, row on medium screens and up --> <div class="display:flex flex-direction:column md:flex-direction:row"> <!-- hide on mobile --> <nav class="display:none md:display:flex"> <!-- max-width variant --> <div class="max-md:display:none"> <!-- arbitrary breakpoints --> <div class="min-width(800px):color:red max-width(600px):color:blue">

    State modifiers

    Pseudo-class modifiers for interaction and form states:

    <button class="hover:opacity:80% focus:outline-color:blue"> <input class="disabled:opacity:50% invalid:border-color:red"> <li class="first:font-weight:bold odd:background-color:lightgray">

    Available state modifiers include:

    • interaction: hover: focus: focus-visible: focus-within: active: link: visited: any-link: target:
    • form: autofill: blank: disabled: enabled: checked: indeterminate: required: optional: valid: invalid: user-valid: user-invalid: in-range: out-of-range: placeholder-shown: read-only: read-write: default:
    • structural: first: last: only: first-of-type: last-of-type: only-of-type: odd: even: empty: nth-child(N): nth-last-child(N): nth-of-type(N): nth-last-of-type(N):
    • UI states: fullscreen: modal: open: popover-open:
    • media: playing: paused:

    Color-scheme modifiers

    Apply styles in dark or light mode:

    <div class="shadow_lg dark:shadow_sm"> <div class="color:black light:color:gray">

    dark: and light: use :root.dark and :root.light selectors, matching fuz_css's color scheme mechanism.

    Pseudo-element modifiers

    Style generated content and element parts:

    <span class="before:content:'→' before:margin-right:0.5rem"> <input class="placeholder:opacity:50%">

    available: before: after: cue: first-letter: first-line: placeholder: selection: marker: file: backdrop:

    Media feature modifiers

    Accessibility and context-aware styles:

    <div class="motion-reduce:animation:none"> <nav class="print:display:none">

    available: print: motion-safe: motion-reduce: contrast-more: contrast-less: portrait: landscape: forced-colors:

    Combining modifiers
    #

    Combined modifiers follow a canonical order enforced with errors that guide you. Multiple states must be alphabetical (focus:hover: not hover:focus:) because both generate equivalent CSS -- canonical ordering prevents duplicates.

    [media:][ancestor:][...state:][pseudo-element:]class
    1. media - one of md:, lg:, print:, etc
    2. ancestor - one of dark: or light: (likely rtl:/ltr: in the future)
    3. state - any of hover:, focus:, disabled:, etc, sorted alphabetically
    4. pseudo-element - one of before:, after:, placeholder:, etc
    <!-- media + ancestor + state --> <div class="md:dark:hover:opacity:83%"> <!-- media + state + pseudo-element --> <div class="md:hover:before:opacity:100%"> <!-- multiple states must be alphabetical --> <button class="focus:hover:outline:2px~solid~blue">

    Generated CSS for md:dark:hover:opacity:83%:

    @media (width >= 48rem) { :root.dark .md\:dark\:hover\:opacity\:83\%:hover { opacity: 83%; } }

    Builtin classes
    #

    fuz_css's main stylesheet provides styles for base HTML elements using style variables, acting as a modern CSS reset that adapts to dark mode. It includes CSS classes that provide common generic functionality -- these are called builtin classes.

    .unstyled

    Default list (styled):

    <ul> <li>1</li> <li>2</li> </ul>
    • 1
    • 2

    With .unstyled:

    <ul class="unstyled"> <li>a</li> <li>b</li> </ul>
    • a
    • b

    The .unstyled class lets fuz_css provide solid default element styles with a simple opt-out:

    :where(:is(ul, ol, menu):not(.unstyled)) { padding-left: var(--space_xl4); }

    This strategy supports semantic hooks for theming:

    :where(:is(ul, ol, menu):not(.unstyled)) { padding-left: var(--list_padding_left, var(--space_xl4)); }

    See the specific docs sections for more about .unstyled.

    Other builtin classes

    Framework support
    #

    fuz_css is Svelte-first, but the base styles (style.css, theme.css) work with any framework and plain HTML. The utility class generator has varying detection support:

    frameworkdetectionnotes
    Sveltefullall patterns including class: directives and array/object syntax
    plain HTMLfullstatic class="..." attributes, script variables
    React / JSXfullwith acorn-jsx plugin - className
    Preactfullwith acorn-jsx plugin - class
    Solidfullwith acorn-jsx plugin - class, classList
    Vue JSXfullwith acorn-jsx plugin - class
    Vue SFC, Angular, etc.nonetemplate syntax not parsed; use clsx/cx/cn in JS/TS

    The additional_classes plugin config option is an escape hatch for classes that can't be statically detected. Acorn plugins can be added via acorn_plugins for additional syntax support like JSX.

    Out of the box, class generation works only with TypeScript/JS, Svelte, and JSX. Angular is not supported; Vue JSX is supported but their recommended SFC format is not. We could revisit this if there's demand.

    Svelte-first
    #

    The extractor parses and analyzes the AST to understand Svelte's class syntax. Supported constructs:

    • attributes: class="...", class={[...]}, class={{...}} (identifier and string-literal keys), class:name
    • expressions: logical (&&, ||, ??), ternaries, template literals (complete tokens only -- `color_a_50 ${base}` extracts color_a_50, but `color_${hue}_50` cannot be extracted; use @fuz-classes or additional_classes)
    • Svelte 5 runes: $derived() and $derived.by() for class variables
    • utility calls: clsx(), cn(), cx(), classNames() with nested arrays, objects, and utility calls
    • scripts: both <script> and <script module>, with naming convention and usage tracking

    React and JSX
    #

    To enable JSX support for React, Preact, Solid, etc, install acorn-jsx and pass it to the plugin or generator:

    npm i -D acorn-jsx

    Vite plugin

    // vite.config.ts import {defineConfig} from 'vite'; import jsx from 'acorn-jsx'; import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js'; export default defineConfig({ plugins: [ vite_plugin_fuz_css({ acorn_plugins: [jsx()], }), ], });

    Gro generator

    // fuz.gen.css.ts import {gen_fuz_css} from '@fuzdev/fuz_css/gen_fuz_css.js'; import jsx from 'acorn-jsx'; export const gen = gen_fuz_css({ acorn_plugins: [jsx()], });

    Supported JSX patterns:

    • className="..." and class="..." - static strings
    • className={clsx(...)} - utility function calls
    • className={cond ? "a" : "b"} - ternary and logical expressions
    • classList={{active: cond}} - Solid's classList
    • usage tracking: variables in className, class, and classList are tracked back to their definitions (has limitations, room for improvement)
    // variable tracking works in JSX too const styles = 'box hover:shadow_lg'; const Component = () => <div className={styles} />;

    The acorn_plugins option accepts any Acorn-compatible plugin, so other syntax extensions can be supported the same way.

    Custom interpreters
    #

    Interpreters dynamically generate CSS for class names that aren't in the static definitions (which can be extended via class_definitions or replaced with include_default_classes: false). The default CSS-literal syntax and modifier support are both implemented as interpreters, which you can extend or replace.

    For advanced use cases, you can define custom interpreters that generate CSS from arbitrary class name patterns. This is similar to UnoCSS's dynamic rules, which also use regex + function patterns. An interpreter has a regex pattern and an interpret function that returns CSS (or null to pass):

    import type {CssClassDefinitionInterpreter} from '@fuzdev/fuz_css/css_class_generation.js'; // Example: grid-cols-N classes like "grid-cols-4" // Unlike composites, interpreters can parameterize values const grid_cols_interpreter: CssClassDefinitionInterpreter = { pattern: /^grid-cols-(\d+)$/, interpret: (matched) => { const n = parseInt(matched[1]!, 10); if (n < 1 || n > 24) return null; return `.grid-cols-${n} { grid-template-columns: repeat(${n}, minmax(0, 1fr)); }`; }, };

    This generates grid-cols-1 through grid-cols-24 on-demand -- something that would require 24 separate composite definitions. Note the classes for this example could also be created as composites with a helper function -- fuz_css uses this strategy internally to create its token classes in css_class_definitions.ts.

    Register with the Vite plugin or Gro generator:

    import {css_class_interpreters} from '@fuzdev/fuz_css/css_class_interpreters.js'; vite_plugin_fuz_css({ class_interpreters: [grid_cols_interpreter, ...css_class_interpreters], })

    The interpreter context provides access to class_definitions, css_properties (for validation), and diagnostics (for errors/warnings). This enables full programmatic control over class-to-CSS generation.

    Compared to alternatives
    #

    TailwindCSS and UnoCSS are utility-first frameworks where classes have primacy. fuz_css is semantic-first: utilities complement HTML defaults rather than being the primary styling mechanism.

    TailwindCSSUnoCSSfuz_css
    primary syntaxDSL-firstconfig-firsttoken DSL + CSS literals
    multi-property@apply, pluginsshortcutscomposites
    arbitrary valuesDSL (bg-[#abc])any (presets)CSS syntax (background:#abc)
    detectionregexregexAST (more capable, slower)
    token sourceCSS (@theme)JS/TS configTS variables (importable)
    extensibilitypluginsrules, variants, presetsinterpreters

    fuz_css's modifier system is less expressive than TailwindCSS's variants. Missing: parent/sibling/descendant state (group-hover:, peer-invalid:, has-checked:), arbitrary variants ([&.is-dragging]:), child selectors (*:), container queries (@md:), data/ARIA variants, and more. When you need these patterns, fuz_css currently expects you to use rulesets or <style> tags, but the API is still a work in progress, and a more powerful and potentially more TailwindCSS-aligned system is on the table.

    For extensibility, all three frameworks allow custom class-to-CSS mappings. UnoCSS's dynamic rules use regex + function patterns similar to fuz_css interpreters, plus separate variants for modifiers. TailwindCSS uses JS plugins and UnoCSS has the more mature extensibility story; fuz_css offers comparable power with interpreters but it's still evolving -- feedback is welcome!

    fuz_css fits best when you prefer semantic HTML with styled defaults. Design tokens are defined in TypeScript, naturally adapt to dark mode, and can be imported in TS for typesafe runtime access. The tradeoffs include a more limited DSL and more verbose literal syntax, which nudges you toward <style> tags, tokens when appropriate, or composites for repeated patterns.


    fuz_css is still early in development. Your input is welcome in the discussions!

    colors #

    fuz_css provides color variables that adapt to the color-scheme, working naturally in both light and dark modes. Each theme can customize the 10 hues (a-j) and their intensity variants (00-100).

    Hues use letters so themes can reassign colors without breaking semantics -- "a" is blue by default but could be any color. Each hue has 13 intensity variants tuned independently for visual balance across color schemes.

    Hue variables
    #

    Hue variables contain a single hue number. Each color variable combines a hue variable with saturation and lightness values for light and dark modes.

    Hue variables therefore provide a single source of truth that's easy to theme, but to achieve pleasing results, setting the hue alone is not always sufficient. Custom colors generally need tuning for saturation and lightness.

    Hue variables are also useful to construct custom colors not covered by the color variables. For example, fuz_css's base stylesheet uses hue_a for the semi-transparent ::selection. (try selecting some text -- same hue!)

    Hue variables are the same in both light and dark modes (non-adaptive).

    • NaN
      primary
    • NaN
      success
    • NaN
      error/danger
    • NaN
      secondary/accent
    • NaN
      tertiary/highlight
    • NaN
      quaternary/muted
    • NaN
      quinary/decorative
    • NaN
      senary/caution
    • NaN
      septenary/info
    • NaN
      octonary/flourish

    Color variables
    #

    There are 13 intensity variants per hue (00, 05, 10, 20, ..., 80, 90, 95, 100), from subtle to bold. The 50 variant of each color is used as the base for things like buttons.

    Unlike the shade and text scales (which are separate), color variables can be used for both text and backgrounds via utility classes: .color_a_50 sets text color, .bg_a_50 sets background color.

    Each color exists in two forms:

    • Adaptive (color_a_50) -- switches between light and dark values based on color scheme. Use for most UI work.
    • Absolute (color_a_50_light, color_a_50_dark) -- stable values that never change. Use when you need a pinned color.

    Adaptive colors
    #

    The colors you'll use most often. They automatically adjust to maintain visual consistency across color schemes. Note that these values differ between light and dark modes! See the discussion above for why.

      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()

    Absolute colors
    #

    Sometimes you need a color that doesn't adapt, like logos, charts, color-coded data, or elements that must match across screenshots. Every adaptive color has two absolute variants:

    • color_a_50_light - the value used in light mode
    • color_a_50_dark - the value used in dark mode

    These are stable regardless of color scheme. Light and dark variants are tuned independently for visual balance -- achieving equivalent appearance across color schemes requires different saturation and lightness values.

      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()
      • rgb()

    shading #

    fuz_css offers a shading model built on adaptive style variables that respond to the color-scheme. Adaptive means the underlying values change between light and dark modes to maintain consistent prominence -- low numbers stay subtle, high numbers stay strong. Each theme can implement light mode, dark mode, or both.

    Light mode's starting point is plain white documents (like paper) where we subtract light to create contrast and shape. Black shadows on white make natural sense; white glows are near invisible.

    Dark mode's starting point is a lightless void where we add light. Elements emit light to fill the darkness. White glows make natural sense; black shadows are near invisible.

    The shade scale
    #

    The shade scale is the primary system for backgrounds and surfaces. All numbered shades (shade_00 through shade_100) are tinted using the theme's tint_hue and tint_saturation for visual cohesion. The scale also includes two untinted extremes (shade_min and shade_max) for maximum contrast needs.

    Key values
    #

    • shade_min: untinted surface-side extreme -- white in light mode, black in dark mode; used for input backgrounds
    • shade_00: the base background
    • shade_05: very subtle: hover states on surface
    • shade_10: subtle elevation: panels, cards, aside, blockquote, code
    • shade_20: more elevated: active/pressed states
    • shade_30: default border intensity
    • shade_100: maximum tinted contrast
    • shade_max: untinted contrast-side extreme: black in light mode, white in dark mode; rarely needed

    Adaptive alpha overlays (fg/bg)
    #

    The fg and bg variables provide alpha-based overlays that adapt to the color scheme. Unlike the opaque shade scale, these stack when nested and are used by composites like .panel and .chip.

    • fg_NN (foreground direction) - darkens in light mode, lightens in dark mode; use for elevated surfaces like panels, cards, and hover states
    • bg_NN (background direction) - lightens in light mode, darkens in dark mode; use for surfaces that blend toward the background

    In light mode, fg is the same as darken and bg is the same as lighten. In dark mode, they're swapped.

    fg (toward foreground)
    #

    Adaptive alpha overlays that add contrast with the surface.

    bg (toward background)
    #

    Adaptive alpha overlays that reduce contrast with the surface.

    Stacking behavior
    #

    Unlike the opaque shade scale, alpha overlays stack when nested. Each layer adds more contrast:

    <div class="fg_10 p_sm"> <div class="fg_10 p_sm"> <div class="fg_10 p_sm"> <div class="fg_10 p_sm"> ... </div> </div> </div> </div>
    fg_10
    nested fg_10
    triple nested
    quadruple nested

    This is useful for nested UI elements like cards within cards, or hover states inside elevated containers. Composites like .panel, .chip, and .menu_item use fg_10 for this stacking behavior.

    Darken/lighten alpha overlays
    #

    The non-adaptive darken_NN and lighten_NN variables provide consistent darkening or lightening regardless of color scheme. These are the underlying primitives that fg and bg reference.

    Darken
    #

    Alpha overlays that always add black.

    Lighten
    #

    Alpha overlays that always add white.

    Visual balance
    #

    Both scales are tuned for visual balance, not mathematical spacing: 0%, 3%, 6%, 12%, 21%, 32%, 45%, 65%, 80%, 89%, 96%, 98%, 100%. This provides visually even steps across the range.

    When to use which
    #

    Use fg_NN when you need stacking behavior or are building nested UI:

    /* elevated panel (stacks when nested) */ background-color: var(--fg_10); /* hover state (stacks on any background) */ background-color: var(--fg_10); /* active/pressed state */ background-color: var(--fg_20);

    Use shade_NN when you need explicit, predictable opaque surfaces:

    /* base page background */ background-color: var(--shade_00); /* opaque border */ border-color: var(--shade_30); /* input backgrounds (untinted for contrast) */ background-color: var(--shade_min);

    The composites (.panel, .chip, .menu_item) use fg_NN for stacking. The page background uses shade_00 as the opaque base.

    Text colors
    #

    For text colors, see the text scale (text_00 through text_100). Both scales use the same "prominence" semantics for light and dark modes: low numbers are subtle, high numbers are strong. They're separate scales because text and backgrounds have different contrast requirements.

    buttons #

    The <button> element is styled by default without adding classes. Classes like .selected and .plain and .color_a modify the base style.

    Buttons have a .selected state that can be used for various UI purposes, like showing a selected item in a menu or a styling button's aria-pressed state. Instead of having two distinct styles of buttons with outlined and filled variants, fuz_css makes outlined buttons the default, and selected buttons are filled. There's also the .deselectable modifier class for buttons that remain clickable when selected. Themes can customize this behavior.

    <button>a button</button>

    Colorful buttons
    #

    <button class="color_a">
    <button class="color_b">
    <button class="color_c">
    <button class="color_d">
    <button class="color_e">
    <button class="color_f">
    <button class="color_g">
    <button class="color_h">
    <button class="color_i">
    <button class="color_j">

    With disabled attribute
    #

    <button disabled> :| </button>

    With .selected
    #

    <button class="selected">...</button>

    .selected buttons with .deselectable continue to be clickable when selected:

    <button class="selected deselectable"> ... </button>

    With .plain and .icon_button
    #

    <button class="plain"> + </button> <button class="icon_button"> + </button> <button class="plain icon_button"> + </button>

    .selected variants

    <button class="plain selected"> + </button> <button class="icon_button selected"> + </button> <button class="plain icon_button selected"> + </button>

    .selected and .deselectable variants

    <button class="plain selected deselectable"> + </button> <button class="icon_button selected deselectable"> + </button> <button class="plain icon_button selected deselectable"> + </button>

    With .compact
    #

    The .compact composite class sizes things more tightly with smaller fonts, inputs, padding, border radii, and flow margins.

    <button class="compact">compact</button>

    .compact with .plain and .icon_button:

    <button>+++</button> <button class="compact">+++</button> <button class="compact plain">+++</button> <button class="compact icon_button">+++</button> <button class="compact plain icon_button">+++</button>

    .compact with colors:

    <button class="compact color_h">color_h</button> <button class="compact color_g">color_g</button> <button class="compact color_d selected">color_d</button>

    .compact overrides custom properties, so children inherit compactness:

    <div class="compact row gap_sm"> <button>one</button> <button class="plain">to</button> <button class="color_a">3</button> </div>

    chips #

    The .chip class creates a small inline label or tag, useful for displaying metadata, categories, or status indicators. Chips work on any element but are commonly used with <span> and <a>.

    Chips have color variants (.color_a through .color_j) that tint both the text and background. Links (a.chip) have slightly bolder text.

    <span class="chip">a chip</span> a chip
    <a class="chip">a link chip</a> a link chip

    Colorful chips
    #

    <span class="chip color_a"> .chip.color_a a.chip.color_a
    <span class="chip color_b"> .chip.color_b a.chip.color_b
    <span class="chip color_c"> .chip.color_c a.chip.color_c
    <span class="chip color_d"> .chip.color_d a.chip.color_d
    <span class="chip color_e"> .chip.color_e a.chip.color_e
    <span class="chip color_f"> .chip.color_f a.chip.color_f
    <span class="chip color_g"> .chip.color_g a.chip.color_g
    <span class="chip color_h"> .chip.color_h a.chip.color_h
    <span class="chip color_i"> .chip.color_i a.chip.color_i
    <span class="chip color_j"> .chip.color_j a.chip.color_j

    With .compact
    #

    The .compact composite class provides tighter sizing with smaller fonts, inputs, padding, border radii, and flow margins.

    <span class="chip compact">compact</span>
    compact normal
    <span class="chip compact color_a">color_a</span> <span class="chip compact color_b">color_b</span> <span class="chip compact color_c">color_c</span>
    color_a color_b color_c

    .compact overrides custom properties, so children are compact too:

    <div class="compact row gap_sm"> <span class="chip">one</span> <span class="chip color_d">two</span> <a class="chip color_e">three</a> </div>
    one two three

    elements #

    fuz_css applies default styles to semantic HTML elements in its 🗎 reset stylesheet. The styles use variables and include appropriate spacing, so plain HTML gets user-friendly styling and theme integration automatically. The defaults are low specificity using :where so they're easy to override, and you can opt out by adding .unstyled to an element.

    #

    Paragraph elements are unstyled except for spacing. Divs are totally unstyled.

    p

    p

    div

    p

    p

    This paragraph has no bottom margin because default spacing is omitted for the :last-child of all otherwise-spaced elements, streamlining the common case. This has some unfortunate edge cases that can usually by solved by adding .mb_lg. Coupling markup structure to styles like this may be something we change, feedback is welcome.

    #

    a link

    a link with .selected

    a link with .unstyled

    #

    code

    code with .unstyled

    #

    a pre is
      preformatted
    					text

    #

    Click this summary to see the rest of the details

    The children of the details excluding the summary.

    <details> <summary> Click this <code>summary</code> to see the rest of the <code>details</code> </summary> <p>The children of the <code>details</code> excluding the <code>summary</code>.</p> <Code code={'...'} /> </details>
    details and summary with .unstyled unstyled details content

    #

    blockquote
    blockquote with .unstyled

    #

    #


    #

    <header>header</header>
    header

    #

    <footer>footer</footer>
    footer

    #

    <section>section</section>

    Sections have a bottom margin, except for the last in the list.

    section
    section
    section

    ul
    #

    • a
    • b
    • see

    ul with .unstyled

    • a
    • b
    • see

    ol
    #

    1. one
    2. two
    3. etc

    ol with .unstyled

    1. one
    2. two
    3. etc

    menu
    #

  • 1
  • 2
  • 3
  • menu with .unstyled

  • 1
  • 2
  • 3
  • #

    <table> <thead> <tr> <th>th</th> <th>th</th> <th>th</th> </tr> </thead> <tbody> <tr><td>td</td><td>td</td><td>td</td></tr> <tr><td>td</td><td>td</td><td>td</td></tr> <tr><td>td</td><td>td</td><td>td</td></tr> </tbody> </table>
    ththth
    tdtdtd
    tdtdtd
    tdtdtd
    <table class="width:100%"> ... </table>
    ththth
    tdtdtd
    tdtdtd
    tdtdtd

    TODO more!

    forms #

    Form elements have basic default styles that can be omitted with .unstyled.

    #

    <form> <fieldset> <legend> a <MdnLink path="Web/HTML/Element/legend" /> </legend> <label> <div class="title"> username </div> <input bind:value={username} placeholder=">" /> </label> ... </fieldset> ... </form>
    This is a legend

    More info can be included in <p> tags like this one. Here we could include info about passwords.

    form with range input
    #

    <input type="range" />
    <input type="range" disabled />

    form with checkboxes
    #

    form with radio inputs
    #

    With .compact
    #

    The .compact composite class provides tighter sizing with smaller fonts, inputs, padding, border radii, and flow margins. Apply directly or on a container to cascade to children.

    <form class="compact"> ... </form>
    .compact
    normal

    typography #

    h1

    paragraph

    h2

    paragraph

    h3

    paragraph

    h4

    paragraph

    h5

    paragraph

    h6

    paragraphs

    paragraphs

    paragraphs

    p with some small text

    p sub p sup p

    show code

    Font families
    #

    =
    =
    =

    Font sizes
    #

    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =

    Font weights
    #

    Font weight values can be any integer from 1 to 1000.

    There are no variables for font-weight but there are utility classes.

    .font-weight:100
    .font-weight:200
    .font-weight:300
    .font-weight:400
    .font-weight:500
    .font-weight:600
    .font-weight:700
    .font-weight:800
    .font-weight:900
    .font-weight:950
    .font-weight:234
    .font-weight:555
    .font-weight:997

    Text colors
    #

    The text scale (text_00 through text_100) provides tinted neutral colors optimized for text legibility. The scale uses "prominence" semantics for light and dark modes: low numbers are subtle, high numbers are strong. This matches the shade scale pattern.

    • text_00 - surface-side endpoint: essentially invisible on surface
    • text_10-text_30 - very subtle/faint text: watermarks, hints
    • text_50 - disabled text: text_disabled
    • text_80 - default body text: --text_color
    • text_90-text_100 - high emphasis/headings
    • text_min/text_max - knockout text (pure white/black without tint)

    The text scale is separate from the shade scale because text and backgrounds have different contrast requirements. Use text_* for text colors and shade_* for backgrounds. For colored text, use color_a_50 etc.

    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =

    Line heights
    #

    Icon sizes
    #

    = 18px
    🐢
    = 32px
    🐢
    = 48px
    🐢
    = 80px
    🐢
    = 128px
    🐢
    = 192px
    🐢
    = 256px
    🐢

    With .compact
    #

    The .compact composite class makes sizing tighter with smaller fonts, inputs, padding, border radii, and flow margins. Apply on a container to cascade to children.

    <div class="compact"> <h3>compact heading</h3> <p>compact paragraph</p> <p>compact paragraph</p> </div>

    compact

    Paragraph in a compact container with tighter flow margins between elements.

    Another paragraph showing the reduced spacing.

    • list item one
    • list item two

    normal

    Paragraph in a normal container with default flow margins between elements.

    Another paragraph showing the default spacing.

    • list item one
    • list item two

    borders #

    Border variables integrate with the theme system and adapt to color scheme. Alpha borders are tuned for visual balance -- dark mode uses higher alpha because light-on-dark has lower perceived contrast.

    Tinted alpha borders
    #

    The border_color_NN variables provide tinted alpha borders that integrate with the theme. They use tint_hue for cohesion.

    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =

    Opaque borders with shades
    #

    For opaque borders, use shade variables directly. This avoids alpha transparency but requires inline styles or custom classes:

    /* inline style */ border-color: var(--shade_30); /* or set the contextual variable */ --border_color: var(--shade_30);

    Border colors
    #

    Use color variables like color_a_50 for colored borders. The intensity controls the color's prominence.

    =
    =
    =
    =
    =
    =
    =
    =
    =
    =

    Border widths
    #

    =
    =
    =
    =
    =
    =
    =
    =
    =

    Outlines
    #

    Each border utility class has a corresponding outline variant using the same border variables (like outline_color_b, outline_width_4, and outline-style:solid), and there are also two special outline variables:

    =
    =

    Border radius
    #

    Border variables with token classes:

    =
    =
    =
    =
    =
    =
    =
    .border_top_left_radius_lg
    .border_top_right_radius_sm
    .border_bottom_left_radius_md
    .border_bottom_right_radius_xl

    Custom values
    #

    Border literal classes for open-ended values:

    .border-radius:0
    .border-radius:14%
    .border-radius:32%
    .border-radius:100%
    .border-top-left-radius:26%
    .border-top-right-radius:100%
    .border-bottom-left-radius:100%
    .border-bottom-right-radius:77%

    shadows #

    fuz_css provides four semantic shadow types that build on the light model in the shading docs: umbra for natural depth, highlight for rim lighting, glow for light emphasis, and shroud for dark overlays.

    Umbra
    #

    Umbras are adaptive shadows that darken in light mode and lighten in dark mode. This is the default shadow behavior, creating natural depth perception in both color schemes. In light mode umbra is untinted (pure black); in dark mode it's tinted using tint_hue/tint_saturation.

    shadow_alpha

    Highlight
    #

    Highlights are adaptive shadows that lighten in light mode and darken in dark mode. In light mode highlight is tinted using tint_hue/tint_saturation; in dark mode it's untinted (pure black).

    shadow_alpha

    Glow
    #

    Glows are non-adaptive shadows that lighten in both light and dark mode. Glow colors are tinted using the theme's tint_hue and tint_saturation.

    shadow_alpha

    Shroud
    #

    Shrouds are non-adaptive shadows that darken in both light and dark mode. Unlike glow, shroud is untinted (pure black) because dark shadows typically don't benefit from tinting. This asymmetry is intentional but subject to change.

    shadow_alpha

    Colored shadows
    #

    Use shadow_color_{hue}_{intensity} classes to apply colored shadows. The intensity controls the color's prominence -- 60 is a fine starting point for visible colored shadows.

    shadow_color_a_60
    #

    shadow_alpha
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60
    .shadow_color_a_60

    shadow_color_b_60
    #

    shadow_alpha
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60
    .shadow_color_b_60

    shadow_color_c_60
    #

    shadow_alpha
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60
    .shadow_color_c_60

    shadow_color_d_60
    #

    shadow_alpha
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60
    .shadow_color_d_60

    shadow_color_e_60
    #

    shadow_alpha
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60
    .shadow_color_e_60

    shadow_color_f_60
    #

    shadow_alpha
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60
    .shadow_color_f_60

    shadow_color_g_60
    #

    shadow_alpha
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60
    .shadow_color_g_60

    shadow_color_h_60
    #

    shadow_alpha
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60
    .shadow_color_h_60

    shadow_color_i_60
    #

    shadow_alpha
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60
    .shadow_color_i_60

    shadow_color_j_60
    #

    shadow_alpha
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60
    .shadow_color_j_60

    layout #

    fuz_css provides space and distance variables for consistent sizing across your UI. Space variants work with utility classes like .p_md and .gap_lg.

    Space variables
    #

    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =
    • =

    Space variants are used in classes like .p_md for padding, margin, other forms of spacing like gap, positioning, dimensions, etc.

    Distance variables
    #

    • =
    • =
    • =
    • =
    • =

    Distance variants have classes like .width_atmost_sm and .width_atleast_md.