Konde Design Framework

i18n is your translation.
KDF is your design.
Both live in JSON.

A JSON-based design coordination layer for server-side web apps and agent-assisted UI. Repeatable decisions — layout, spacing, typography, component styling — live in one source of truth your agents read instead of rediscovering across files, pages, and past sessions.

See how it works
kdf / homepage.json → rendered
kdf/homepage.json
{
  "$layout": ["hero", "footer"],
  "hero": {
    "wrapper": "mx-auto max-w-6xl px-6 py-20",
    "title":   "@typography.h1",
    "cta-primary": "@button.cta"
  }
}
data-kdf="hero.title"
Build and ship with AI.
data-kdf="hero.body"

One source of truth keeps every page, component, and agent session consistent.

data-kdf="hero.cta-primary"
Get started →

JSON defines · code renders · data-kdf maps back

Licence
MIT
Runtime
Node Filesystem
Dependencies
Zero Runtime
Tested with
Next · Astro · Hono
01 — The problem

Design drifts in
your components.

When class names live only inside .tsx files, every page slowly diverges. It works for a single human editor — but it is fragile the moment agents start touching the UI.

without kdf · scattered across files
<section className="mx-auto max-w-6xl px-6 py-20">
  <h1 className="text-5xl font-semibold tracking-tight">
  // …and again, slightly different, on the next page

The same button quietly grows five variants. Spacing changes page by page. Every new agent session has to rediscover the rules from scratch.

THE RESULT
  • Agents improvise colours, spacing, typography, and layout.
  • Users correct the result through chat, every time.
  • Changes mean searching through component files.
  • Repeated sessions lose design intent.

→ endless "bigger" · "more blue" · "move left" iteration

02 — The solution

Make design explicit.

KDF does for design what i18n does for text: it moves repeatable decisions out of component files and into JSON. The rendered UI still uses normal CSS — what changes is ownership.

1 — define it in json
{
  "hero": {
    "title": "text-5xl font-semibold tracking-tight"
  }
}
2 — render it in code
const d = getDesign("homepage");

<h1 data-kdf="hero.title" className={d("hero.title")}>
JSON defines
One file owns the design direction. Users edit it directly when they want precise control.
Code renders
Your components read tokens and render approved design with normal CSS — nothing magic underneath.
Agents implement
Agents build UI from JSON instead of guessing. No invented spacing, colours, typography, or section order.
Users adjust JSON
Change the source once instead of describing the same visual correction in chat, session after session.
03 — Not a component library

A library says what.
KDF says which.

A design library gives you a Button. KDF tells you this is homepage.hero.cta-primary, rendered as a Button, styled with this token. It adds the one mapping libraries leave out — element to design decision.

 Design libraryKDF
What it says"This is a Button.""This is hero.cta-primary on homepage."
MappingGeneric component, many possible overrides.One element maps to one design key.
TraceabilityNo exact map from element to decision.data-kdf points every DOM node to its JSON path.
Sits above shadcn · Bootstrap · Chakra · Tailwind · plain CSS · your own system — KDF is not a replacement for any of them.
04 — The vocabulary

Six symbols.
One grammar.

KDF stores class names, shared references, section order, and CSS custom properties. It never stores business logic, event handlers, data fetching, permissions, or accessibility behaviour.

data-kdf
The map

Every element using a token carries a matching path. DOM nodes trace straight back to JSON — for scanners, tests, and agent review.

data-kdf="hero.title"
d(path)
The accessor

Resolves a path to its className string. d.css() returns the CSS custom properties object.

className={d("hero.title")}
@
Shared reference

Point at a reusable token in shared/. Refs can chain and extend with extra classes.

"@button.cta" shadow-xl
$layout
Order & visibility

An array of section keys. Listed sections render in order; missing ones are hidden by the host app.

["hero", "features", "footer"]
$
Component identity

Metadata for agents and host tooling — which component renders this token, plus variant and size hints.

"$": "Button"
css
Custom properties

Values not expressible as reusable classes, applied as inline style variables via d.css().

{ "--kdf-accent": "#4F46E5" }
05 — Architecture

Shared defaults.
Page overrides.

Design tokens live in two places: reusable defaults in shared/, and per-page composition that overrides only what it needs. Two user-owned CSS files cover first paint and escape hatches.

project structure
kdf/
  shared/
    button.json      ← reusable defaults
    card.json
    color.json
    typography.json
  homepage.json      ← page composition
  konde-server.css   ← critical, first paint
  konde.css          ← non-critical tweaks
Multi-level cascade

A reference like @button.cta resolves from the page's shared/, then a parent shared/, then page tokens. Templates override only the parts they need and inherit the rest.

Light by default

Design resolves on the server; the browser receives finished class strings. Critical CSS lands in the first paint, the rest loads after — no render cost dumped on low-spec clients.

TWO USER-OWNED CSS FILES · TWO LOADING MOMENTS
konde-server.css
critical · first paint

Imported by the app so design variables and no-FOUC overrides arrive in the very first paint — before anything flashes.

/* konde-server.css */
:root { --kdf-primary: #1F8F47; }
[data-kdf="hero.slider"] { display: none; }
konde.css
non-critical · after app css

Loaded after framework and app CSS for tweaks, experiments, and escape hatches — fine-tuning that never needs to block paint.

/* konde.css */
[data-kdf="hero.title"] { letter-spacing: -0.02em; }
[data-kdf="hero.wrapper"] { gap: 3rem; }

→ KDF creates both once and never overwrites them. The plugin exposes their paths via env - your app wires the imports; nothing is injected for you.

06 — Multi-template

Switch design
like switching language.

The way i18n switches languages, KDF switches design templates. Same app, same components, same code — point KDF_DIR at another design folder and the whole look changes.

designs/ — two templates, one app
designs/
  lander/
    shared/
    homepage.json
  newlander/
    shared/
    homepage.json
// next.config.ts
withKDF({ dir: "./designs/lander" })(nextConfig);
$layout Reorder or hide sections without touching a component.
@button Change every CTA at once through a shared token.
KDF_DIR Swap the entire design template as a deliberate host decision.
07 — Runtime API

Resolve on the server.
Pass strings down.

KDF core reads JSON from disk, so it runs server-side: Next.js Server Components, Astro renders, Hono handlers. Resolve classes there, then hand plain strings to client components.

server component
import { getDesign, cn } from "@kondeio/kdf";

const d = getDesign("homepage");

<button data-kdf="hero.cta" className={cn(d("hero.cta"), isActive)}>Start</button>
d("hero.title")

→ resolved className string

d.css("hero.title")

→ CSS custom-properties object

cn · cx · dedupeClasses

Join conditional classes, drop falsy values, dedupe — semantic-free by default.

cache: auto · always · none

Production caches; dev revalidates by mtime & size.

08 — Compatible by design

It sits above
your stack.

STYLING — STORES CLASS NAMES, NOT CSS

TailwindshadcnBootstrapChakraCSS ModulesPlain CSSYour own system

KDF is not a CSS engine. Tokens hold whatever class strings your styling system understands. If your framework scans source files, point it at the JSON too: @source "../kdf/**/*.json".

RUNTIME — SERVER-SIDE JAVASCRIPT

Next.js tested · plugin
Astro tested
Hono tested
Get started

Install once.
Init scaffolds the rest.

Installing also scaffolds a starter kdf/ folder when one doesn't exist. Existing files are never overwritten.

$npm install @kondeio/kdf
$pnpm add @kondeio/kdf
$bun add @kondeio/kdf
$npm exec -- kdf init # manual scaffold
next.config.ts
import withKDF from "@kondeio/kdf/plugin";

export default withKDF({ dir: "./designs" })(nextConfig);