Skip to content

Overview

Design system · MUI replacement

One vocabulary. HTML, CSS and a sprite. No framework.

This site is the reference implementation of the QFlow design system as plain HTML and CSS. Every element in the catalog is rendered here from tokens up, so it can be lifted into React, a server-rendered page or anything else that emits markup. It does not depend on Material UI and is not meant to run beside it.

Principles

Tokens or nothing No literal color, size, radius, shadow or duration exists outside tokens.css. Dark mode is a pure token override. The geometry the classic skin needs beyond tokens (chip tabs, 20px menu rows, the dialog frame) lives in css/classic/ rather than in attribute overrides.
Three tiers, strict dependencies A primitive depends on tokens only and owns one concern. A component is composed of primitives. A composite assembles components into a whole interaction and may be domain-aware. Nothing reaches down more than one tier.
State is an attribute, not a class Hover, selected, pressed, expanded, invalid and disabled are expressed with aria-*, data-state and native pseudo-classes. Framework bindings set attributes; they never compute class strings.
Configuration is a data attribute Every axis in the catalog (variant, color, size, severity…) is data-{axis}="{value}" on the block. The React prop is the attribute, one to one.
Native first Dialogs are <dialog>, menus and popovers use the popover attribute, accordions are <details>, toggles are real inputs. Behavior that the platform provides is not re-implemented.
Icons are a sprite One icons.svg of <symbol>s, referenced with <use>. Adding an icon is a line in icons.txt; nothing is bundled per icon and no icon library ships to the client.

The markup contract

Class names follow qf-{block} and qf-{block}__{element}. Modifiers are never classes. This example is a complete, valid Button and shows every kind of hook the system uses.

Anatomy
Hook Form Meaning
Block class="qf-button" Which element of the catalog this is. Exactly one per element.
Element class="qf-button__label" A named child that the block styles. Only used where the child has no meaning on its own.
Axis data-variant="outlined" Configuration. Enumerated values only; documented per element.
Flag data-loading, data-block Boolean axis. Presence is true.
State aria-pressed="true", :disabled, data-state="open" Runtime state. Prefer the ARIA attribute the element already needs; data-state only when none exists.
Child element <svg class="qf-icon"><use href="…#id"/></svg> Composition. A component contains primitives by nesting their markup; the parent only adjusts size and color.

Consuming it

The system has one skin, classic: one complete stylesheet set. Link css/classic/index.css and copy sprites/icons.svg and fonts/ beside it. That is the entire runtime. Set data-theme="dark" on <html> for the dark theme. Pages also carry data-skin="classic" on <html> for scripts; no rule depends on it.

Plain HTML
<link rel="stylesheet" href="design/css/classic/index.css">

<button class="qf-button" data-variant="contained" data-color="primary">
  <svg class="qf-icon"><use href="design/sprites/icons.svg#plus"/></svg>
  New task
</button>
React binding: the prop is the attribute
type ButtonProps = {
  variant?: 'contained' | 'outlined' | 'text' | 'ghost' | 'soft';
  color?: 'primary' | 'info' | 'success' | 'warning' | 'error';
  size?: 'xs' | 'sm' | 'md';
  iconOnly?: boolean;
  loading?: boolean;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

export const Button = ({ variant, color, size, iconOnly, loading, ...rest }: ButtonProps) => (
  <button
    className="qf-button"
    data-variant={variant}
    data-color={color}
    data-size={size}
    data-icon-only={iconOnly || undefined}
    data-loading={loading || undefined}
    {...rest}
  />
);

export const Icon = ({ name, size, color }: IconProps) => (
  <svg className="qf-icon" data-size={size} data-color={color} aria-hidden="true">
    <use href={`${SPRITE_URL}#${name}`} />
  </svg>
);
Tokens from code, without a theme object
// Inline styles keep working; the cascade resolves the variable.
<div style={{ color: 'var(--qf-color-primary-500)', padding: 'var(--qf-space-4)' }} />

// A generated tokens.ts can expose the same names typed:
export const color = { primary: { 500: 'var(--qf-color-primary-500)' /* … */ } } as const;

Files

Path What it is
css/classic/ The one complete stylesheet set (the classic skin). No rule has a [data-skin] selector; every file stays at 600 lines or fewer.
css/classic/index.css Imports the set in order. The only file a consumer links.
css/classic/tokens.css Every value. Light on :root, dark as [data-theme='dark']; hex only in --qf-palette-*.
css/classic/base.css Font faces, reset, focus ring, scrollbars, reduced-motion.
css/classic/primitives/ P-01 … P-14: layout.css and behavior.css.
css/classic/components/ C-01 … C-29, grouped by catalog section: actions, content, inputs, navigation, overlays, utility.
css/classic/composites/ The app shell, X-01 OverlaySystem and X-02 … X-11: data, documents, feedback.
sprites/icons.svg Generated sprite. icons.txt is the curated list; build-sprite.mjs regenerates from Tabler.
fonts/ Avenir Next (300–700, italic) and Twemoji Mozilla.
css/site/, js/docs.js, js/trace.js This site only. Not part of the system.
serve.mjs bun run dev serves the site on port 5190 with live reload and no dependencies.

Status

Visual contract complete for all four layers. Behavioral primitives (VirtualScroller, DragSource, useCombobox) have no visual output and are documented as contracts on the primitives page. Composites show geometry and states; their data behavior lands with the framework binding.

Foundation: done Primitives: done Components: done Composites: static only React binding: not started

Accessibility: each element's WCAG 2.2 AA claim is on Test status; see also the accessibility statement and the conformance report.