Core
Primary is the anchor
Settings

How the five derived colors are placed around the anchor.

Theme builder

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 in tokens.css, so classes stay aligned with tokens.
  • Priority 2 — CSS only where a utility cannot reach: @keyframes and the selectors that drive them (KovalLogo), and the .prose bindings that point @tailwindcss/typography at 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 .mjs module 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’s aria-* and data-* 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 from dark at one of the six ink-ladder steps. white and black are the exceptions the system leans on deliberately — cards are bg-white, scrims are bg-black/50.
  • Repeated stable patterns — an exported class string, shared by every call site. global.css carries the six colors, the type roles, and the .prose bindings; 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:

Text
--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:

Text
button-height-md = 3em
button-padding-inline-md = 1.286em
button-radius = radius-md

Naming 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.