System Model
Design philosophy, token strategy, Tailwind-first implementation model, and preset rules for the design system.
Philosophy
This system is built for restraint, clarity, and repeatable product UI.
It borrows the strongest ideas from mature systems — hierarchy discipline from Carbon, structural layers from Atlassian, behavior thinking from Material, consistency rules from Polaris — without copying their full complexity.
The result is a system strict enough to stay clean and flexible enough for real product work.
Design Principles
1. Fewer choices, stronger rules
A small set of components is easier to use, easier to maintain, and harder to misuse. When the system has fewer options, the right option becomes obvious.
2. Hierarchy must be obvious
Emphasis should reflect action importance. Users should be able to identify the main action at a glance.
3. Layout is not a component’s job
Components should not carry external margins. Spacing between elements belongs to layout patterns, not to individual components.
4. Behavior and appearance are separate
Visual emphasis, structural form, and interactive behavior should be defined independently so they can change without affecting each other.
5. Consistency beats novelty
Do not introduce one-off styles for individual screens or campaigns into the core system. Tokens can express brand variation without creating new component variants.
Implementation Model
Tailwind-first: markup should lean on Tailwind utilities generated from @theme in tokens.css. Tokens define visual roles; utilities are the default hand-off for authors. Semantic component classes stay deliberately thin.
What this means in practice:
- Priority 1 — utilities in
class: layout, spacing, radius, and brand color from@theme(e.g.bg-light,text-dark,border-dark/12,bg-primary). Spacing and radius map to the token scale intokens.css, so classes stay aligned with tokens. - Priority 2 — CSS only where a utility cannot reach:
@keyframesand the selectors that drive them (KovalLogo), and the.prosebindings that point@tailwindcss/typographyat the type roles. That is the whole list. Everything else in this repo — page chrome, the sidebar, the code-block toolbar, the theme builder’s own controls — is Tailwind in the markup. - Repeat a class string, do not invent a class name. When several call sites
need the same stack, export the string from the component or a
.mjsmodule and interpolate it. Tailwind scans those files, so the utilities still compile, and there is no second cascade to keep in step. - State goes on an attribute, not a class.
aria-current,aria-selected,aria-pressed,data-open,data-copied— each is both the semantics and the styling hook, via Tailwind’saria-*anddata-*variants. Scripts set the attribute and touch no presentation. - Semantic aliases (
text-h1,text-primary) are allowed for stable, shared roles only. - No second utility engine, no custom class generator.
Tailwind-first does not mean Tailwind’s default color palette:
- Do not use
gray-*,slate-*,neutral-*, etc. for product UI color. Neutrals come fromdarkat one of the six ink-ladder steps.whiteandblackare the exceptions the system leans on deliberately — cards arebg-white, scrims arebg-black/50. - Repeated stable patterns — an exported class string, shared by every call site.
global.csscarries the six colors, the type roles, and the.prosebindings; nothing else belongs there.
Canonical rules for agents and contributors also live in AGENTS.md at the repository root (Tailwind v4 specifics and Astro conventions).
Semantic alias rules:
- Aliases must map directly to tokens
- Aliases must be few and predictable — name them like roles, not descriptions
- Aliases must not become a parallel framework
- When in doubt, use a utility directly instead of creating an alias
Client interactivity
Koval Core is framework-agnostic at the system level: tokens, CSS, and Astro components do not depend on React, Vue, or Solid. When you need behavior beyond static markup, pick the simplest option that works.
Level 1 — Native. Use semantic HTML, CSS, and ARIA. No client JavaScript. Prefer this for layout, typography, buttons and links, and purely visual states (:hover, :focus-visible).
Level 2 — Astro + small scripts. Use Astro markup with bundled <script> tags and native DOM APIs for common patterns—mobile nav toggles, simple tabs, accordions, dismissible banners, theme switching—without a client UI framework.
Level 3 — Custom elements. Use custom elements sparingly when a widget must stay reusable and framework-agnostic across projects, and repeating inline scripts would be harder to maintain than one well-defined element.
Level 4 — Solid islands. Use Solid as an Astro island only for heavy interaction, difficult accessibility, or when integrating headless primitives such as Ark UI or Zag. In practice those libraries are consumed from Solid islands—they are optional adapters for hard problems, not the default way to build the system.
One-line rule: Native HTML and Astro first, small client scripts second, custom elements only when justified, framework islands only when interaction truly needs them.
Token Strategy
Tokens are the layer between raw values and components. They carry intent, not just values.
Token layers
Primitive tokens — raw values in @theme:
--color-primary = oklch(...)
--type-h1-size = clamp(...)Color and typography are the whole layer. Spacing, radius, elevation and motion
are Tailwind’s own utilities, chosen once and recorded in theme-rules.md.
Color is one flat layer. Six brand values, and everything between them is one
of those at reduced opacity via Tailwind’s / modifier (text-dark/70,
border-dark/12). Which colors may sit together is documented and measured in
Color rather than frozen into names.
Spacing, radius, motion, and typography keep their scales; only color collapsed.
Component tokens — scoped to a specific component:
button-height-md = 3em
button-padding-inline-md = 1.286em
button-radius = radius-mdNaming rules
- Primitive tokens describe the value
- Component tokens describe the component and the role together
- Color has no role-named tokens — see above
- Avoid tokens that are too specific to one screen or too vague to mean anything
Preset Strategy
Presets are token packs that change visual style without changing component behavior.
A preset might represent an industry direction (finance, health, developer tools), a brand tier (default, premium, minimal), or a density mode (compact, standard, comfortable).
What presets can change
- The six brand colors (chromatics, neutrals, danger)
- Typography scale and weights
- Radius values
- Density (spacing, height tokens)
- Shadow depth and style (the three
--shadow-*values; never a fourth level)
What presets must not change
- Component hierarchy (which variant is primary, which is secondary)
- Variant meanings (danger is always danger)
- State model (hover, active, focus, disabled, loading always exist)
- Size names (small, medium, large stay consistent)
- Accessibility rules (focus visibility, contrast requirements)
Rule
Presets change how the system looks. They do not change what the system means or how it behaves.
System Structure
The system is organized in three layers:
Foundations
Design primitives and rules: color, spacing, typography, radius, motion, focus.
Components
Primitive UI elements: button, input, checkbox, badge, and similar.
Each component page covers: purpose, variants, sizes, states, structure, behavior, and the tokens it consumes.
Patterns
Multi-component compositions for recurring UI problems: form footers, modal footers, page headers, action groups, confirmations.
Pattern pages describe when to use the composition, which components it uses, and the hierarchy rules that apply.