defuss-shadcn / Introduction / anatomy
Anatomy of a Component
Installation put the files on your page. This is what one component is made of, shown on the Toggle: the CSS custom properties it reads, the HTML you write, the stylesheet that turns the tokens into a look, the script that adds the behavior HTML cannot express, and the two files that describe it to an agent. No part knows the others by name - they meet in the browser through a class, a few attributes and the tokens.
On this page (8)
§1 - Tokens: the values every part reads
The theme is a set of CSS custom properties on :root (and the same keys with dark values on .dark), in the shape tweakcn exports. A component never names a colour, a radius or a shadow - it reads a token, so a theme swap or dark mode reaches every component at once. These are the tokens the Toggle reads:
:root { --accent: oklch(0.9700 0 0); /* pressed surface */ --accent-foreground: oklch(0.2050 0 0); /* pressed text */ --muted: oklch(0.9700 0 0); /* hover surface */ --muted-foreground: oklch(0.5560 0 0); /* resting text */ --ring: oklch(0.7080 0 0); /* focus ring */ --radius: 0.625rem; --radius-md: calc(var(--radius) - 2px); --font-sans: ui-sans-serif, system-ui, sans-serif;}.dark { /* the same keys, dark values */ }§2 - Markup: one class, data attributes, native semantics
The HTML is a native element with one base class that says what it is. Variants and sizes are data-* attributes (the Data Attribute API), never modifier classes, and the state is a native or ARIA attribute - here aria-pressed, which is also what assistive technology reads.
<button class="toggle" aria-pressed="false" aria-label="Bold"> <i data-lucide="bold"></i></button><button class="toggle" data-variant="outline" data-size="sm" aria-pressed="true"> Italic</button>§3 - Stylesheet: the class reads the tokens
Every component stylesheet lives in @layer components and nests its variants, sizes and states under the base class. It reads tokens through var(--*) and derives hover colours with color-mix() - no hardcoded palette, so the same file renders every theme. The pressed look is the aria-pressed="true" attribute the markup already carries.
@layer components { .toggle { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; font-size: 0.875rem; font-weight: 500; font-family: var(--font-sans); border-radius: var(--radius-md); border: 1px solid transparent; background: transparent; color: var(--muted-foreground); height: 2.25rem; padding: 0 0.5rem; &:hover:not(:disabled) { background-color: var(--muted); } &[aria-pressed="true"] { background-color: var(--accent); color: var(--accent-foreground); &:hover:not(:disabled) { background-color: color-mix(in oklch, var(--accent) 85%, var(--foreground)); } } &:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; } &:disabled { opacity: 0.5; cursor: not-allowed; } &[data-variant="outline"] { /* ... */ } &[data-size="sm"] { /* ... */ } }}§4 - Script: only what HTML and CSS cannot do
Most components stop at the stylesheet. The Toggle needs one thing the platform does not give a button: flipping aria-pressed on click. Its module finds every .toggle through df$ (the core runtime: defuss-query for selection and events, defuss-morph for markup), guards against initializing twice, binds the State API so el.api.setState('pressed') and el.store work on every instance, and watches the document so elements added later initialize too. The attribute the script writes is the one the stylesheet reads - the script never styles anything.
function init() { dfDollar('.toggle:not([data-init]):not(.toggle-group .toggle)').each((_i, toggle) => { dfDollar(toggle).data('init', ''); // the double-init guard toggle._defaultPressed = dfDollar(toggle).attr('aria-pressed') || 'false'; // el.store + el.api: $('#my-toggle').api.setState('pressed') bindComponent(toggle, toggleApi, { name: toggle._defaultPressed === 'true' ? 'pressed' : 'default', config: {} }); dfDollar(toggle).on('click', () => { dfDollar(toggle).attr('aria-pressed', String(dfDollar(toggle).attr('aria-pressed') !== 'true')); }); });}init();new MutationObserver(init).observe(document, { childList: true, subtree: true });§5 - Skill and schema: the component described
Two more files ship beside the stylesheet and the script. The component skill (component-skill.md) is the markup reference an agent reads before writing the HTML: the native basis, the structure, the variants, the ARIA and the states. The schema (toggle.schema.json) is the machine contract of the states: their names, types and defaults, how each is set and observed on the DOM - the docs' State tab is generated from it, and the verifier compares every page's States table against it.
§All five, live
The same markup three times under three token sets: the page's theme, a warm accent and the dark palette. Press any of them - the script flips the attribute, the stylesheet paints the state from whichever tokens are in scope.
§One component, three token scopes
The wrappers set --accent and --accent-foreground inline (the right one carries the .dark palette). The toggles are identical: one class, aria-pressed, the shipped toggle.css and toggle.js. Click to press; the colour comes from the tokens in scope.
§Where to go next
Configure variants and sizes in markup with the Data Attribute API, drive and observe the interactive components' named states through the State API, see how the stylesheets stack in Cascade Layers, and read a component's component skill before writing its HTML.
Comments, ideas or improvements? Edit this page's source on GitHub