New
Apps 1,895Makers 1,890This week 1,690
Promote your app−30%
0views in
the last 30 days

defuss-shadcn

A UI component system that scales with AI: the shadcn/ui look as plain HTML, CSS and vanilla...

Built with Claude Code

by kyr0 · Unclaimed · from DEGermany · added Oct 29, 2026
no ratings0 comments

Socket Badge

A UI component system that scales with AI: the shadcn/ui look as plain HTML, CSS and vanilla JavaScript, with a skill for every component.

Themeable, stateful components, built on semantic HTML, modern CSS, and vanilla JavaScript. No framework. No build step for consumers - dist/ is committed and ready to use as-is. A foundation for agentic-first frontend engineering.

Built using Verified Agentic Engineering (VAE).

249 components - 78 with JavaScript, 171 CSS-only - 510.5 KiB minified + compressed - 338.1 KiB as the all.css/all.js bundle 171 of 249 components need no JavaScript - native HTML and modern CSS cover them entirely.

The footprint is measured from the shipped dist/ files on every build and published as dist/stats.json; verify fails the build if this sentence and that file disagree.

Documentation & Live Demos → · Theme Designer · Architecture (ARCH.md) · Agent skill (SKILL.md) · Paper (PDF)

TL;DR

A framework component library ties every button to a framework version, a bundler and a build. An AI agent that writes a page then needs all three to see what it wrote, and a server-rendered template, a static site or a plain .html file cannot use the components at all.

defuss-shadcn ships the components as files. Tokens are CSS custom properties in the tweakcn shape, every component is one stylesheet plus, where the browser cannot do it alone, one ES module, and every component carries a skill that tells an agent how to write its markup. A page links two files, writes HTML, and is done - in a template engine, a static site, a CDN include or a file opened from disk.

  • 🪟 Application Scaffolds - demo apps built with defuss-shadcn: Admin Dashboard, Messenger, Issue Tracker, Notes & Docs, Status Page, Desktop and Document Editor. Each opens full screen and loads only its own bundle from dist/apps/<app>/: the core plus the components its markup uses. Each routes with defuss-dom-router (views are linkable, Back works) and switches between English and German with defuss-i18n - see the Client-side Routing and Translation guides
  • 🎨 Themeable - full shadcn semantic token model. 43 tweakcn presets ship as drop-in theme files - swap one and every component updates instantly; light and dark are two palettes of one identity (the same radius in both, gated)
  • 📘 Component Skills - every component includes a structured skill - markup, variants, sizes, density, named states, ARIA, and wiring conventions - grounded in web standards; every interactive component also ships a machine-readable *.schema.json contract (states, types, defaults, actions) that drives its docs controls and is verifier-enforced; three task skills - shadcn-plan, shadcn-theme, shadcn-review - plan UI from the existing components, write themes and review markup, each with a bundled checker where a program can decide
  • 🔁 Observable state - interactive components expose a State API (el.api.setState('open'), el.api.getState()), so agents and tests can drive every documented state by name without knowing the implementation
  • ♿ Accessible - built on native HTML elements and WAI-ARIA patterns (menus and menubars with nested submenus, tabs, dialogs, trees, comboboxes, one-time-code fields, virtualized lists). Keyboard navigation, focus management, and screen reader support by default
  • 🧩 Standalone HTML - plain HTML, CSS and ES modules, no framework, no build pipeline; the interactive components run on one tiny core runtime, df$ - defuss-query (DOM selection and events) and defuss-morph (HTML reconciling) plus the shared State API helpers, shipped once as core.js inside all.js

The documentation site dogfoods the CDN install: every demo loads all.css / all.js and its theme from the jsDelivr CDN, pinned to the release it documents (the index page names that release) - if it renders, the CDN install works.

How it works

One component is five layers in one folder. The tokens feed the stylesheet, the stylesheet styles semantic HTML, a script enhances that HTML only where the browser has no native behavior, and the skill describes the whole to an agent:

flowchart LR
    T(["🎨 tokens<br/>default-semantic-tokens.css"]) -->|"var(--*)"| C["component CSS<br/>dialog.css"]
    C -->|"styles"| H["semantic HTML<br/>dialog · data-variant · data-size"]
    J["vanilla JS, when needed<br/>dialog.js · el.api · el.store"] -->|"enhances"| H
    R["core.js<br/>df$ runtime"] --> J
    S["📘 component skill<br/>component-skill.md + dialog.schema.json"] -. "describes" .-> H

For an agent the same files close a loop: it reads the skill, writes markup against the shipped bundle, drives the result by state name and checks what it sees.

flowchart LR
    A["🤖 agent"] -->|"reads"| S["SKILL.md<br/>+ every component skill"]
    S -->|"writes markup"| P["your page<br/>all.css + all.js"]
    P -->|"el.api.setState('open')<br/>el.api.getState()"| V["observed state"]
    V -->|"screenshot · e2e · verify"| A
Surface When you reach for it What it does
all.css + all.js one page, every component The generated bundle: the core runtime plus every component's styles and behavior (.min twins and source maps ship beside them). The documentation site loads exactly this pair.
core.css + core.js, then <name>/<name>.css + .js ship only what you use core.css is tokens + sizing + layout + accessibility utilities, core.js the df$ runtime; every component's own files load after them and are independent of each other.
dist/theme/<preset>.css a different look One stylesheet with :root + .dark token blocks, linked after the tokens. 43 tweakcn presets ship generated; your own tweakcn export works the same way.
data-variant / data-size / data-density which version of a component The one attribute API for variants and sizes - <button class="btn" data-variant="outline">, never a modifier class.
el.api / el.store drive or observe a component setState(name, config), getState() and render(state) by state name on every interactive element, plus a defuss-store of { name, config } to subscribe to; the registry twins live at df$.shadcn.<name>Api.
dist/schemas/<name>.schema.json tooling The machine contract of a component's states and actions - what drives the State tab of every live example.
skills/defuss-shadcn/SKILL.md, skills/shadcn-* an AI agent The whole project as one Agent Skill with every component skill beside it, and three task skills: plan, theme, review.
dist/sections/, dist/apps/<app>/ a slice of the system One bundle per sidebar section (also attached to each release as a ZIP) and one lean bundle per application scaffold.

Install

Via CDN

<!-- 1. Add the base: tokens + sizing/layout/accessibility utilities (a theme preset from dist/theme/ may follow) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/core.css">

<!-- 2. Add the icons -->
<script src="https://unpkg.com/lucide@1.8.0"></script>
<script>lucide.createIcons();</script>

<!-- 3a. Everything at once: the bundle (embeds the core runtime) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/all.css">
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/all.js"></script>

<!-- 3b. …or only the components you use: their CSS, then core.js + their JS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/button/button.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/dialog/dialog.css">
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/core.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/dialog/dialog.js"></script>

<!-- 4. Only on a page that uses them: the extra bundles, after 3a or 3b -
     wysiwyg (HTML Preview Editor, Editor.js), mermaid, math (formulas, editor, compute), visual-editor -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/wysiwyg.css">
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/wysiwyg.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/mermaid.css">
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/mermaid.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/math.css">
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/math.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/visual-editor.css">
<script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/visual-editor.js"></script>

Two delivery modes, one implementation: load core plus the components you use, or load all alone. core.js installs the callable df$ runtime (query + morph + the shared component layer at df$.shadcn.shared) - every component .js requires it, loaded first; a missing or mismatched core fails with one actionable load-order error before anything renders. Every runtime bundle carries a per-release provenance pointer (bundled upstream versions + license hashes; full notice in components/NOTICE.txt). Four groups are kept out of all.* and ship as extra bundles, each loaded after all.* or core on a page that uses them: wysiwyg.css / wysiwyg.js (the HTML Preview Editor code-example - editable source + sandboxed live preview, the card every docs example renders in - and Editor.js), mermaid.css / mermaid.js (Mermaid), math.css / math.js (Formula Rendering, Formula Editing, Compute & Explain) and visual-editor.css / visual-editor.js (the Visual Editor - GrapesJS over the components' markup, the canvas of the docs' Studio). Pin a release instead of @latest for production: Installation → Pinning a version.

Via npm

In a project with a package manager and a bundler, install the package and import the same files in the same order from the app entry:

bun add defuss-shadcn
# or: npm install defuss-shadcn
import 'defuss-shadcn/dist/components/core.css'; // tokens + utilities
import 'defuss-shadcn/dist/components/all.css';  // every component's styles
import 'defuss-shadcn/dist/components/all.js';   // df$ runtime + every component's behavior

Self-hosting

Download the full system and drop it into any project. All the files are static - no build step, no dependencies. Point an AI at dist/SKILL.md and it has everything it needs: the library's integration guide and philosophy, an index of every component skill (type/why/when/where + supported states), CSS to include, JS to wire up, and the entire documentation site with working examples of every component.

Download latest (.zip)

Need less than everything? Every sidebar section also ships as its own bundle in dist/sections/, and each release attaches one ZIP per section - see Bundles & Downloads.

Quick start

One page, one dialog, driven by name - the markup is the Dialog skill's structure:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/core.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/all.css">
</head>
<body>
  <!-- 1. Trigger: a Button, variant and size as data attributes -->
  <button class="btn" data-variant="default" data-dialog-trigger="my-dialog" aria-haspopup="dialog">
    Open
  </button>

  <!-- 2. A native <dialog>: showModal(), Escape, ::backdrop and the focus trap come from the browser -->
  <dialog id="my-dialog" class="dialog" role="dialog" aria-modal="true" aria-labelledby="my-dialog-title">
    <div class="dialog-content">
      <div class="dialog-header">
        <h2 class="dialog-title" id="my-dialog-title">Dialog Title</h2>
        <p class="dialog-description">Supporting description.</p>
      </div>
      <div class="dialog-body">
        <!-- Content goes here -->
      </div>
      <div class="dialog-footer">
        <button class="btn" data-variant="outline" data-dialog-close>Cancel</button>
        <button class="btn" data-variant="default">Confirm</button>
      </div>
    </div>
  </dialog>

  <!-- 3. The runtime + every component; dialog.js wires the trigger and binds el.api -->
  <script type="module" src="https://cdn.jsdelivr.net/gh/kyr0/defuss-shadcn@latest/dist/components/all.js"></script>

  <!-- 4. Drive it by state name - the same calls an agent or a test makes -->
  <script type="module">
    const dialog = document.querySelector('#my-dialog');
    dialog.api.setState('open');
    console.log(dialog.api.getState()); // → { name: 'open', config: { ... }, model: { ... } }
  </script>
</body>
</html>

Every component page shows this pattern live: Dialog, and every other component in the sidebar.

Using components

Data attributes

Components use data-* attributes for variants and sizes - one base class says what it is, the attributes say which version. Flat specificity, one API for every component, and a class attribute an agent can read. Guide: Data Attribute API.

Attribute Values (example) Meaning
data-variant default, outline, ghost, destructive the visual variant
data-size sm, md, lg, icon the size
data-density compact, comfortable, spacious the whitespace policy - gaps and padding scale, type and fixed dimensions stay
data-side, data-position, ... per component structural variations where a component has them

State API

Every interactive component declares its states by name (const dialogStates = ['default', 'open']) and binds them to each element it initializes. Guide: State API.

Member Type Description
el.api.setState(name, config?) (string, object?) => void apply a declared state; an unknown name throws
el.api.getState() () => { name, config, model } the current state, its config and the element's authored markup model
el.api.render(state?) (state?) => string the markup of a state, a function of state - what setState would produce
el.store defuss-store { name, config } the state as a store: writing it applies the state, subscribing follows every change, including the user's clicks and native closes
df$.shadcn.<name>Api, df$.shadcn.<name>States registry the same API with the element passed explicitly, and the declared state list

Every declared state is covered by four artifacts - a screenshot in both color schemes, the doc page, the component skill and an e2e assertion - and verify fails the build when one is missing.

Theming

Tokens are compatible with tweakcn.com theme exports. A theme is one plain stylesheet with :root + .dark token blocks - switching themes means switching that file. Guide: Theming.

  1. Ship it at build time - link a theme file instead of the token file: every tweakcn preset ships generated in dist/theme/ (claude.css, vercel.css, ..., 43 presets; regenerated by bun run build, gated by verify). Or export your own from tweakcn and replace dist/theme/utils/default-semantic-tokens.css - components see the same tokens either way.
  2. Switch it at runtime - the Theme Switcher component loads/unloads one <link id="theme-css"> (the theme rides on top of the token file; no JS token objects). That is exactly what the theme picker on the documentation site does.
  3. Design your own - the Theme Designer edits every token live on the whole site, saves custom themes in your browser as you go, and imports / exports them in the tweakcn shape.
  4. Everything updates automatically - all components, the doc site, dark mode (each theme file carries both palettes).

Optional modules

Four standalone stylesheets live beside the token file and are opt-in - no component depends on them, and the token export stays tweakcn-pure:

  • dist/theme/utils/sizing.css - one numeric scale (w-4 = four base units, --size-* aliases, density-aware spacing)
  • dist/theme/utils/layout.css - small layout surface (flex, grid, stack, container, named query boundaries, overflow + text-flow helpers)
  • dist/theme/utils/accessibility.css - screen-reader-only content (.sr-only / .not-sr-only, the clip pattern)
  • dist/theme/utils/shapes.css - reusable silhouettes to mix a design up (radius scale, organic / cut / notch / scoop corners, clip-path shapes, section edges, shadow scale + stylized shadows, frames, background patterns)

Load them after the tokens, before component CSS. Docs: Sizing · Layout · Shapes · Accessibility

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#design · #components · #shadcn