# xtyle: Full Documentation > xtyle is a themable-derivation engine and component contract. A named, swappable algorithm maps a small set of overridable anchors plus a few knobs into a full, internally-consistent design-token set, co-designed against a component library so any valid theme renders well out of the box. The algorithm is the durable asset; a theme is a materialized invocation of one. Source: https://xtyle.dev ## Concepts ---------------------------------------- # What xtyle is xtyle is a themable-derivation engine and component contract. A named, swappable **algorithm** maps a small set of overridable anchors plus a handful of knobs into a full, internally-consistent design-token set, co-designed against a component library so any valid theme renders well out of the box. The split that runs through everything: the algorithm is the durable, reusable asset; a **theme** is a materialized invocation of one. An algorithm is a named, composable engine you can share and reuse. A theme is the print it produces, anywhere from a quick three-color pick to a design dialed in over days. Both are first-class; the architecture just keeps the reusable machinery separate from any one materialized result. ## The three input tiers Every derivation takes input at one of three levels, in increasing power: 1. **Algorithm knobs.** Declared inputs an algorithm exposes (a split angle, a contrast posture, a warmth bias). This is the whole casual UX: pick an algorithm, turn its knobs, get a coherent theme. 2. **Anchors (constraints).** A small set of seed colors the algorithm derives from. `--bg-0`, `--fg-0`, and `--accent` are the common ones. They are constraints fed back into derivation, not a privileged tier of their own. 3. **Token overrides.** The universal escape hatch: pin any token in the register directly. A well-built algorithm re-threads the rest of the ramp around a pinned token rather than leaving an incoherent gap. ## What stays in the engine, what lives in an algorithm The engine (`@xtyle/core`) is mechanism, never policy. It owns the open register, the dependency graph and lineage, the coverage contract, and emit. It has no opinion about how a ramp should look or what "pleasant" means. The **algorithm** is the opinion: how this one works. There is no engine-level "can't look bad" gospel; invariants are per-algorithm policy, proven by a gauntlet parameterized by algorithm. ## The runtime is optional Once derived, a theme is just CSS custom properties plus the browser cascade. No engine needs to be running to use it. The engine can run live for the generator or for novel-at-runtime inputs, but nothing about consuming a finished theme depends on it. ---------------------------------------- # The open register and the coverage contract xtyle's token set is not a fixed schema. Authors declare new tokens and rewire any derivation; the register is open. That openness is held by exactly one hard contract: a **coverage check** between what components consume and what an algorithm produces. ## How coverage works Every component declares the theme tokens it reads in its manifest (`consumedTokens`). Every algorithm produces a register of tokens. Coverage is the check that the second covers the first: for a given component set and a given algorithm, every consumed token must be produced. A gap is a contract violation, caught before anything renders. The line between a consumed theme token and a component-internal custom property matters. A custom property a component's own CSS defines and reads is not a consumed theme token; it never enters the coverage check. The lint and the coverage check agree on that line, so the contract never lies about what a theme must supply. ## Why this is the only hard rule Because the register is open, there is no global schema to validate against. The coverage contract is what makes "any valid theme renders well out of the box" checkable rather than aspirational. It is the single seam every algorithm and every component meets at, and it is the only thing the engine enforces across the two. ---------------------------------------- # Algorithms are xript plugins A xtyle algorithm is literally an xript plugin: a manifest plus xript code, with its knobs as declared inputs, run in xript's zero-authority sandbox. Because of that, xript's toolchain (validate, typegen, docgen, init) and its capability model come free; xtyle builds none of it. An algorithm lives under the top-level `algorithms/` directory as a real mod: a `mod-manifest.json` and a bundled `mod.js`. It is authored against an ergonomic surface (`@xtyle/core/authoring`) and imports the core engine by name. The host exposes color primitives to the sandbox as a gated binding, so an algorithm does OKLCH color math without being handed broad authority. ## The blessed set xtyle ships a small built-in set, each its own xript plugin: - **xtyle-default**: the neutral default. - **xtyle-hc**: a high-contrast posture. - **xtyle-quiet**: muted, low-chroma. - **xtyle-loud**: saturated and high-energy. - **nxi-nite**: a multi-pass day/night algorithm whose register re-derives across the hours. Each algorithm carries its own invariants and proves them with a gauntlet parameterized by that algorithm. A blessed algorithm derives byte-identical whether run baked or hosted through the sandbox, so the sandboxed mod is the canonical engine and the baked path is a fast, verified fallback. ---------------------------------------- # Consuming a theme A derived theme is a flat set of CSS custom properties. You apply it once, at a scope, and the cascade does the rest. Every xtyle component reads those properties, so theming the tokens themes the whole component set at once. ## The bindings One component contract, three bindings, all honoring the same derived tokens: - **`@xtyle/core/elements`**: the raw custom elements. Framework-agnostic; works anywhere HTML does. - **`@xtyle/svelte`**: a thin Svelte wrapper over the same elements. - **`@xtyle/astro`**: Astro components, the binding this site is built from. A component renders one of two ways from a single fragment fill. Over Astro-rendered structure it adopts the existing markup as light DOM (zero-JS, no flash of unstyled content). Mounted bare, it attaches a shadow root and projects framework-owned children through native slots. The presence of server-rendered structure is the mode signal; it is auto-detected, never configured. ## The CLI `@xtyle/core` ships a Node bin, `xtyle`, for build-time derivation and proofs: - `xtyle derive --bg --accent --format css` derives a theme and emits it as CSS or JSON. - `xtyle gauntlet -a all --depth quick` spot-checks invariants across algorithms; `--mode hosted --depth full` runs the full battery against the sandboxed mods. - `xtyle coverage --consumed a,b,c` checks a component's consumed tokens against a produced register. The same engine runs in the browser through the QuickJS the xript runtime embeds, so live derivation in the generator and build-time derivation in the CLI are one codebase. ## The MCP server `xtyle mcp` starts a Model Context Protocol server over stdio, handing an agent the same engine the CLI hands a human. The tools cover `xtyle_derive`, `xtyle_coverage`, `xtyle_components` (list a component or describe its full manifest), `xtyle_gauntlet`, and `xtyle_list_algorithms`; resources serve the concept docs and every component manifest. Point a client at `xtyle mcp` (or `npx -y @xtyle/core xtyle mcp`) and an agent building against xtyle reads token names and prop shapes from the manifest instead of guessing. ---------------------------------------- # Effects A token is a value and a component is a thing; an **effect** is a *verb* — a visual behavior applied to any element under a condition. Neither of the other two can hold one: a token cannot say "on hover", and a component cannot decorate an element that already exists without wrapping it. So an effect is its own kind, addressed the way an icon name is: a name that is its own spec, written in a `data-fx` attribute. ## The spec string ``` {effect}[@{condition}][?{key}:{value},{key}:{value}…] ``` ```html ``` ---------------------------------------- # Component: App Shell Source: https://xtyle.dev/components/app-shell/ Category: shell. Bindings: html, svelte, astro. AppShell is the outermost layout frame for a full-screen application. It establishes a three-row grid (a top toolbar, a flexible body, and a bottom status bar) where the body is itself a three-column grid of a left rail, a scrollable main column, and a right rail. Every region is an optional named slot, so the same scaffold collapses cleanly from a full IDE-style layout down to a bare main column. The main region is a real `
` landmark that owns the only scroll, keeping the chrome pinned. An optional skip link, hidden until focused, lets keyboard users jump straight past the chrome to the content. It carries no chrome of its own: the Astro and HTML bindings emit the same light-DOM structure, and the custom element is a transparent `display: contents` host that contributes nothing to the layout. ## Props - `skipLink`: `string | boolean`. Renders a skip link targeting the main region. `true` uses the default label; a string overrides it. The Astro binding also accepts a `skip-link` slot for richer content. - `leftSize`: `number | string`. Width of the left rail column. A bare number is treated as px; a string passes through (`18rem`, `20%`). Omit to size the rail to its content. - `rightSize`: `number | string`. Width of the right rail column, same rules as `leftSize`. When `rightResizable` is set this is the rail's starting width and its double-click reset target. - `leftResizable`: `boolean`, default `false`. Makes the left rail user-resizable: a drag handle on the rail's inner edge, arrow-key nudges (`Shift` for a larger step, `Home`/`End` to jump to the bounds), and a double-click reset to `leftSize`. The main column reflows live as the rail changes. The element emits `resize` / `resize-end` events with the side and size. - `rightResizable`: `boolean`, default `false`. Makes the right rail user-resizable, mirroring `leftResizable` with a double-click reset to `rightSize`. - `leftMin`: `number`, default `160`. Lower clamp, in px, for the resizable left rail (`left-min`). - `leftMax`: `number`, default `720`. Upper clamp, in px, for the resizable left rail (`left-max`). - `rightMin`: `number`, default `160`. Lower clamp, in px, for the resizable right rail (`right-min`). - `rightMax`: `number`, default `720`. Upper clamp, in px, for the resizable right rail (`right-max`). ## States - **main-focus**: The main region after the skip link moves focus to it; an inset ring marks the landing. - **skip-link-focus**: The skip link revealed on keyboard focus, sliding into view with the standard ring. ## Slots - **default**: The main content, rendered inside the scrollable `
` landmark. - **toolbar**: The top row; typically a Toolbar with the app title and global actions. - **left**: The left rail; typically navigation. - **right**: The right rail; typically contextual detail. - **statusbar**: The bottom row; typically a Statusbar. - **skip-link**: Custom content for the skip link (Astro only); falls back to a default label otherwise. ## Consumed tokens `--layer-chrome`, `--layer-skip`, `--body-bg`, `--fg-0`, `--font-sans`, `--text-body`, `--text-sm`, `--leading-normal`, `--leading-tight`, `--weight-medium`, `--accent`, `--accent-fg`, `--line`, `--border-thin`, `--border-normal`, `--border-thick`, `--radius-md`, `--ring`, `--space-2`, `--space-3`, `--space-4`, `--space-5`, `--duration-fast`, `--ease-standard` ## Accessibility - The main content is a native `
` landmark, so assistive tech can jump to it directly. - The optional skip link is the first focusable element and targets `#main` (which carries `tabindex="-1"`), letting keyboard users bypass the chrome. - The skip link is positioned off-screen and only slides into view on `:focus-visible`, so it stays out of the visual layout until needed. - `
` receives a focus ring when the skip link moves focus to it, confirming the landing point. - The custom element host is `display: contents`, so it adds no box and never disturbs the landmark or grid structure. - A resizable rail's handle is a `role="separator"` with `aria-orientation="vertical"` and `aria-valuenow`/`min`/`max`, is keyboard-focusable, and drives the width with the arrow keys, so the rail resizes without a pointer. ## Examples ### Full application scaffold Toolbar, left and right rails, scrollable main, a status bar, and a skip link. ``` --- import { AppShell } from "@xtyle/astro"; ---
App title and global actions

Page content

Everything in the default slot lands in the scrollable main column.

Ready
``` ### Resizable right rail An editor with a user-resizable inspector: drag the rail's inner edge (or arrow-key the handle) and the main column reflows; double-click to reset. `leftResizable` mirrors it. ``` --- import { AppShell } from "@xtyle/astro"; ---
Editor

Canvas

``` ---------------------------------------- # Component: Avatar Source: https://xtyle.dev/components/avatar/ Category: media. Bindings: html, svelte, astro. Avatar presents a person or entity as a compact square or circle. Given a `src`, it shows the image, cover-cropped to fill; if the image is absent or fails to load, it falls back to initials derived from `userName` (`"Ada Lovelace"` → `AL`) on a soft, hue-tinted background. Slot your own content to override those initials, or use the `icon` slot for a glyph instead. The prop is `userName`, not `name`, because `name` carries form-participation meaning on an element and an avatar is not a form control. The fallback tint follows `tone`, which accepts any of the six semantic roles or the twelve named hues, so a deterministic per-user color is a one-attribute choice. Four sizes (sm, md, lg, xl), two shapes (circle, square), and an optional corner status dot in any semantic tone round out the surface. `alt` names the image; `userName` names the avatar when there is no image, so it is announced either way. ## Props - `src`: `string`. The image URL. When absent or failed, the fallback content shows instead. - `alt`: `string`. Accessible name for the image. Required when `src` is set; also labels the fallback. - `userName`: `string`. The person the avatar stands for. Derives the fallback initials shown when there is no image and nothing slotted (`"Ada Lovelace"` → `AL`, `"Prince"` → `P`), and names the avatar when no `alt` is given. Named `userName` rather than `name` because `name` is a form-participation attribute on an element. Attribute: `user-name`. - `tone`: `FullTone`, default `neutral` (one of: accent, neutral, danger, success, warn, info, accent-2, accent-3, accent-4, red, orange, yellow, green, blue, purple, brown, pink, cyan, gray, white, black). Soft-tint color for the fallback background: any semantic role, accent variant, or named hue. - `size`: `AvatarSize`, default `md` (one of: sm, md, lg, xl). Chip size. - `shape`: `AvatarShape`, default `circle` (one of: circle, square). Outline shape: a circle or a rounded square. - `status`: `Tone` (one of: accent, neutral, danger, success, warn, info). When set, shows a corner status dot in this semantic tone. - `statusLabel`: `string`. Accessible name for the status dot (e.g. "Online"); announced as additional avatar text. - `pulse`: `boolean | "slow" | "fast"`. Breathe the status dot for a live / online presence: a bare `true` pulses slow, `"fast"` quick. A no-op without `status`, and held still under `prefers-reduced-motion`. ## Variants - **circle**: Fully rounded, the default identity shape. - **square**: A rounded square, for entities and projects rather than people. ## States - **fallback**: No image: the tinted initials-or-icon stand-in fills the chip. - **status**: A corner dot conveys presence; its color is the chosen status tone, ringed in the surface. - **pulse**: With `pulse`, the status dot breathes on a soft opacity loop so the avatar reads as live, at a slow or fast cadence; held still under reduced-motion. ## Slots - **default**: Custom fallback content shown when no image loads. Overrides the initials `userName` derives — leave it empty and those initials are what you get. - **icon**: An icon fallback, used in place of initials when no image loads. ## Consumed tokens `--space-2`, `--space-4`, `--space-6`, `--space-7`, `--space-8`, `--font-sans`, `--text-xs`, `--text-sm`, `--text-body`, `--text-lg`, `--weight-semibold`, `--leading-tight`, `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-full`, `--border-thick`, `--bg-1`, `--accent-bg`, `--accent-text`, `--neutral-bg`, `--neutral-text`, `--danger-bg`, `--danger-text`, `--success-bg`, `--success-text`, `--warn-bg`, `--warn-text`, `--info-bg`, `--info-text`, `--accent-2-bg`, `--accent-2-text`, `--accent-3-bg`, `--accent-3-text`, `--accent-4-bg`, `--accent-4-text`, `--red-bg`, `--red-text`, `--orange-bg`, `--orange-text`, `--yellow-bg`, `--yellow-text`, `--green-bg`, `--green-text`, `--blue-bg`, `--blue-text`, `--purple-bg`, `--purple-text`, `--brown-bg`, `--brown-text`, `--pink-bg`, `--pink-text`, `--cyan-bg`, `--cyan-text`, `--gray-bg`, `--gray-text`, `--white-bg`, `--white-text`, `--black-bg`, `--black-text`, `--accent`, `--neutral`, `--danger`, `--success`, `--warn`, `--info` ## Accessibility - The image carries `alt`; the binding warns at runtime when `src` is set without it. - `userName` names the avatar when there is no `alt`, so an image-less avatar is announced by the person it stands for rather than by two bare initials. - When the image is absent or errors, the fallback initials or `aria-label`'d icon remain in the accessibility tree so the avatar is still announced. - The decorative status dot is `aria-hidden`; its meaning rides on `statusLabel` as visually-hidden text in the accessibility tree. - `pulse` is decorative motion only, never the carrier of meaning: put the live state in `statusLabel` ("Online") so it reads the same to assistive tech and under `prefers-reduced-motion`, where the pulse holds still. - The fallback icon slot is decorative. Provide `alt` (or fallback initials) so the avatar still has a name. ## Examples ### Image, fallback, and status A photo when one loads, tinted initials or an icon when it doesn't, with an optional presence dot. ``` --- import { Avatar } from "@xtyle/astro"; --- 🚀 ``` ### Live presence A pulsing status dot reads as online in real time; `statusLabel` carries the meaning for assistive tech. ``` ``` ---------------------------------------- # Component: Avatar Group Source: https://xtyle.dev/components/avatar-group/ Category: media. Bindings: html, svelte, astro. Avatar Group overlaps a set of `Avatar` children into a compact stack, the shape a contributor list, an attendee row, or a "who's viewing" strip takes. Each avatar carries a ring in the page background so the overlap reads as distinct faces rather than a blur, and later avatars sit over earlier ones. Slot the avatars you want shown and set `overflow` to the number of people beyond them, and the group renders a trailing `+N` chip in the neutral tone (its accessible name reads "N more"). `size` matches the chip to the avatars you used, and `spacing` tightens or loosens the overlap. It's a `role="group"`; give it a `label` to name the set. The overflow count is explicit rather than auto-counted so the stack renders identically with no JavaScript, on the server, and in the browser. ## Props - `overflow`: `number`. How many people beyond the slotted avatars to summarize. A positive value renders a trailing `+N` chip (named "N more" for assistive tech); zero or omitted renders no chip. Explicit rather than auto-counted, so the stack is identical with no JavaScript. - `size`: `"sm" | "md" | "lg" | "xl"`, default `md` (one of: sm, md, lg, xl). Sizes the `+N` chip to match the avatars you slotted (each `Avatar` still sets its own size). - `spacing`: `"snug" | "normal" | "loose"`, default `normal` (one of: snug, normal, loose). How far the avatars overlap: `snug` stacks them tighter, `loose` spreads them out. - `label`: `string`. An accessible name for the group, since it is a set of people (e.g. "Contributors"). ## States - **avatar-hover**: Hovering or focusing an avatar raises it above its neighbours so a covered face reads in full. ## Slots - **default**: The `Avatar` children to overlap, in stacking order (the first sits at the bottom of the stack). ## Consumed tokens `--bg-0`, `--neutral-bg`, `--neutral-text`, `--border-thick`, `--radius-full`, `--space-2`, `--space-3`, `--space-4`, `--space-6`, `--space-7`, `--space-8`, `--font-sans`, `--text-xs`, `--text-sm`, `--text-body`, `--text-lg`, `--weight-semibold`, `--leading-tight` ## Accessibility - The row is a `role="group"`; a `label` names it so assistive tech announces the set ("Contributors"). - The `+N` chip is a `role="img"` with an accessible name of "N more", so the hidden count is spoken rather than read as a bare `+16` glyph. - Each slotted `Avatar` keeps its own accessible name (its `alt` or status), so the people in the stack are individually announced. ## Examples ### A contributor stack Overlapping avatars with a `+N` chip for the rest; each keeps its own tone. ``` --- import { AvatarGroup, Avatar } from "@xtyle/astro"; const people = [ { name: "Ada Lovelace", initials: "AL", tone: "accent" }, { name: "Alan Turing", initials: "AT", tone: "success" }, { name: "Grace Hopper", initials: "GH", tone: "warn" }, { name: "Katherine Johnson", initials: "KJ", tone: "info" }, ]; --- {people.map((p) => {p.initials})} ``` ### Sizes and spacing `size` matches the chip to the avatars and `spacing="snug"` tightens the overlap for a dense viewer strip. ``` --- import { AvatarGroup, Avatar } from "@xtyle/astro"; --- AD AL GR ``` ---------------------------------------- # Component: Badge Source: https://xtyle.dev/components/badge/ Category: feedback. Bindings: html, svelte, astro. Badge labels, tags, counts, and statuses inline. Fill treatment (`variant`) and color (`tone`) are independent axes: each of the three fills (solid, soft, outline) can carry any of the six semantic tones (accent, neutral, danger, success, warn, info) or any of the twelve named hues (red … black). It adds a leading status dot (which can `pulse` to read as live), a tabular count affordance, and a `removable` form whose `×` is a real focusable `