Install Setup
Install Setup
Just add this single script tag to your header which auto-injects the CSS and includes all the JS needed.
<script src="dist/volty-embed.js"></script>
To install via npm, run npm i volty
Components
Buttons
All button variants use --vt-color-brand tokens for colors,
so they automatically update when the brand or theme changes. The focus ring uses
color-mix() to create a translucent ring that always
matches the current brand color.
Style variants
Size variants
Outline + size
Disabled states
Icon buttons
Buttons with icons
html ▶
<!-- Style variants -->
<button class="vt-btn">Primary</button>
<button class="vt-btn vt-btn--outline">Outline</button>
<button class="vt-btn vt-btn--ghost">Ghost</button>
<button class="vt-btn vt-btn--surface">Surface</button>
<button class="vt-btn vt-btn--danger">Danger</button>
<!-- Size variants -->
<button class="vt-btn vt-btn--sm">Small</button>
<button class="vt-btn">Default</button>
<button class="vt-btn vt-btn--lg">Large</button>
<!-- Icon-only button -->
<button class="vt-btn vt-btn--icon" title="Add">
<svg ...></svg>
</button>
<button class="vt-btn vt-btn--icon vt-btn--outline"><svg ...></svg></button>
<!-- Button with leading/trailing icon -->
<button class="vt-btn">
<svg ...></svg>
New Project
</button>
<!-- Disabled state -->
<button class="vt-btn" disabled>Primary</button>
<button class="vt-btn vt-btn--outline" disabled>Outline</button>
Components
Cards
Cards use container-type: inline-size, making them
self-aware of their own width. The footer layout switches from left-aligned to
right-aligned automatically when the card is wider than 420px — no media queries needed.
Basic Card
The default card style — clean border, overlay surface background. Use this for most content.
Card body content sits here. It inherits --vt-color-text automatically and reacts to theme changes.
Raised Card
Shadow is computed from --vt-color-text via color-mix() so it works in both light and dark.
The box-shadow uses color-mix(in oklch, var(--vt-color-text) 8%, transparent) — always the right shade.
Interactive Card
Hover to see the brand-colored glow effect — driven entirely by CSS tokens.
The hover glow is color-mix(in oklch, var(--vt-color-brand) 15%, transparent) — automatically updates when you change brand color.
Card with Footer
Footer buttons move to the right when this card exceeds 420px width.
This uses @container card (min-width: 420px) — the card responds to its own width, not the page width.
Wide Card — Container Query Active
At this width (>420px) the footer aligns right. Resize the browser to see it shift.
The layout decision is made by the card itself, not by the viewport. Narrow this card's container and the footer reverts to left-aligned — without touching any CSS or JavaScript.
html ▶
<!-- Basic card -->
<div class="vt-card">
<div class="vt-card__header">
<h3 class="vt-card__title">Title</h3>
<p class="vt-card__description">Description</p>
</div>
<div class="vt-card__body">Content here.</div>
</div>
<!-- Raised (box shadow) -->
<div class="vt-card vt-card--raised">...</div>
<!-- Interactive (hover glow from brand token) -->
<div class="vt-card vt-card--interactive">...</div>
<!-- Card with footer — footer aligns right above 420px container (container query) -->
<div class="vt-card">
<div class="vt-card__header">
<h3 class="vt-card__title">Title</h3>
</div>
<div class="vt-card__body">...</div>
<div class="vt-card__footer">
<button class="vt-btn vt-btn--surface vt-btn--sm">Cancel</button>
<button class="vt-btn vt-btn--sm">Confirm</button>
</div>
</div>
Components
Badges
Badges come in subtle (default), solid, and semantic variants. Subtle colors
use color-mix() at 15% opacity so they always feel
on-brand without overpowering surrounding content.
Default (brand subtle)
Solid
Surface (neutral)
Semantic status
In context
API Gateway
HealthyProduction endpoint serving 2.4M requests/day
Image Processor
DegradedAsync image resizing and optimization pipeline
html ▶
<!-- Default (brand subtle) -->
<span class="vt-badge">Beta</span>
<!-- Solid -->
<span class="vt-badge vt-badge--solid">Active</span>
<!-- Surface (neutral) -->
<span class="vt-badge vt-badge--surface">Draft</span>
<span class="vt-badge vt-badge--surface">v0.1.3</span>
<!-- Semantic status -->
<span class="vt-badge vt-badge--success">Deployed</span>
<span class="vt-badge vt-badge--warning">Degraded</span>
<span class="vt-badge vt-badge--danger">Incident</span>
<!-- With dot indicator -->
<span class="vt-badge vt-badge--success">
<svg width="8" height="8" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="4" fill="currentColor"/>
</svg>
Healthy
</span>
Components
Shorthand Commands
Drop any Volty component into a page without writing the markup by hand. Three equivalent syntaxes — pick whichever fits your workflow.
html — custom element ▶
<!-- Navigation bar -->
<vt-nav logo="My App" version="v1.0"
links='[{"href":"/","label":"Home"},{"href":"/docs","label":"Docs"},{"href":"/about","label":"About"}]'>
</vt-nav>
<!-- Card -->
<vt-card title="Card title" description="Subtitle">Body content here.</vt-card>
<vt-card title="Raised" variant="raised" footer='<button class="vt-btn vt-btn--sm">Action</button>'></vt-card>
<!-- Alert -->
<vt-alert variant="success" title="Deployed">Version 2.0 is live.</vt-alert>
<vt-alert variant="danger" title="Error" dismissible="false">Payment failed.</vt-alert>
<!-- Badge & Button -->
<vt-badge variant="success">Active</vt-badge>
<vt-button variant="outline" size="sm">Outline</vt-button>
<!-- Modal -->
<vt-modal id="confirm" title="Confirm" confirm="Yes, delete" cancel="Cancel" variant="danger">
This cannot be undone.
</vt-modal>
<!-- Toast trigger -->
<vt-toast-trigger variant="success" message="Saved!" title="Done">Save changes</vt-toast-trigger>
html — data attribute ▶
<!-- Works on any existing element — useful for CMS output, templates -->
<div data-vt-insert="nav"
data-logo="My App"
data-links='[{"href":"/","label":"Home"}]'></div>
<div data-vt-insert="alert"
data-variant="warning"
data-title="Heads up">Rate limit at 85%.</div>
<div data-vt-insert="card"
data-title="Hello"
data-description="Subtitle">Body content</div>
js — api ▶
// Render into a target element (replaces innerHTML)
Volty.insert('nav', document.querySelector('header'), {
logo: 'My App',
links: [
{ href: '/', label: 'Home' },
{ href: '/docs', label: 'Docs', active: true },
{ href: 'https://github.com/org/repo', label: 'GitHub', external: true },
],
});
// Get raw HTML string from any template
const html = Volty.templates.card({
title: 'Hello',
description: 'World',
content: '<p>Body text</p>',
variant: 'raised',
});
// Re-scan for data-vt-insert after dynamic content is added
Volty.processInserts(document.getElementById('container'));
Components
Form Fields
Input fields use color-mix() for the hover border
(a blend of the current border and brand color) and a brand-colored focus ring
via --vt-color-brand-ring. Error states swap
the ring color to --vt-color-danger.
html ▶
<!-- Default field -->
<div class="vt-field">
<label class="vt-label" for="email">Email address</label>
<input class="vt-input" id="email" type="email" placeholder="you@example.com">
<span class="vt-hint">We'll never share your email.</span>
</div>
<!-- Required field -->
<div class="vt-field">
<label class="vt-label vt-label--required" for="name">Full name</label>
<input class="vt-input" id="name" type="text" placeholder="Jane Smith">
</div>
<!-- Error state -->
<div class="vt-field">
<label class="vt-label" for="username">Username</label>
<input class="vt-input vt-input--error" id="username" type="text" value="jane!smith">
<span class="vt-hint vt-hint--error">Only letters, numbers, and underscores.</span>
</div>
<!-- Disabled -->
<div class="vt-field">
<label class="vt-label" for="api-key">API Key</label>
<input class="vt-input" id="api-key" type="text" value="sk-••••••••••••••••" disabled>
<span class="vt-hint">Contact support to rotate your key.</span>
</div>
Components
Switches
Switches use :has(input:checked) to react to checkbox
state entirely in CSS — no JavaScript event listeners required. The thumb animation
uses a spring cubic-bezier for a satisfying bounce.
Functional examples
Notification Preferences
Choose how you want to be notified about activity on your account.
html ▶
<label class="vt-switch">
<input type="checkbox" checked>
<span class="vt-switch__track">
<span class="vt-switch__thumb"></span>
</span>
<span class="vt-switch__label">Email notifications</span>
</label>
<!-- Unchecked -->
<label class="vt-switch">
<input type="checkbox">
<span class="vt-switch__track"><span class="vt-switch__thumb"></span></span>
<span class="vt-switch__label">SMS alerts</span>
</label>
<!-- Disabled — :has(input:checked) drives state in CSS, no JS needed -->
<label class="vt-switch">
<input type="checkbox" disabled>
<span class="vt-switch__track"><span class="vt-switch__thumb"></span></span>
<span class="vt-switch__label">Account activity (required)</span>
</label>
Components
Alerts
Inline banners for feedback and status. Uses CSS :has() to
collapse the grid columns when an icon or dismiss button is absent — no modifier class needed.
Semantic variants
Info
Your session will expire in 15 minutes. Save your work to avoid losing progress.
Deployment successful
Version 2.4.1 is live on production. All health checks passed.
Rate limit approaching
You've used 85% of your monthly API quota. Upgrade to avoid interruption.
Payment failed
We couldn't charge your card ending in 4242. Update your billing info.
Solid variant
Solid success
Higher contrast for critical confirmations and prominent notices.
Account suspended
Contact support to restore access to your account.
html ▶
<!-- Default (info) -->
<div class="vt-alert" role="alert">
<div class="vt-alert__icon"><svg ...></svg></div>
<div class="vt-alert__body">
<p class="vt-alert__title">Info</p>
<p class="vt-alert__desc">Your session will expire in 15 minutes.</p>
</div>
<button class="vt-alert__dismiss" aria-label="Dismiss"
onclick="this.closest('.vt-alert').remove()"><svg ...></svg></button>
</div>
<!-- Semantic variants -->
<div class="vt-alert vt-alert--success" role="alert">...</div>
<div class="vt-alert vt-alert--warning" role="alert">...</div>
<div class="vt-alert vt-alert--danger" role="alert">...</div>
<!-- Solid variant (higher contrast) -->
<div class="vt-alert vt-alert--solid vt-alert--success" role="alert">...</div>
<div class="vt-alert vt-alert--solid vt-alert--danger" role="alert">...</div>
Components
Tooltips
Two paths: data-tooltip for zero-markup single-line tips,
and .vt-tooltip with CSS Anchor Positioning for rich
multi-line content with automatic placement fallbacks.
data-tooltip — zero markup
Placement variants
html ▶
<!-- data-tooltip — zero markup, pure CSS, no JS -->
<button class="vt-btn" data-tooltip="Saves your current progress">Save</button>
<button class="vt-btn" data-tooltip="Permanently deletes this item"
data-tooltip-placement="bottom">Delete</button>
<!-- Placement variants: top (default) | bottom | left | right -->
<button data-tooltip="Appears above" data-tooltip-placement="top">Top</button>
<button data-tooltip="Appears below" data-tooltip-placement="bottom">Bottom</button>
<button data-tooltip="Appears left" data-tooltip-placement="left">Left</button>
<button data-tooltip="Appears right" data-tooltip-placement="right">Right</button>
Components
Select
Styled native <select> — accessible by default, no JS.
The chevron is pure CSS via clip-path.
Wrap in .vt-field for label and hint layout.
html ▶
<!-- Wrap in .vt-field for label + hint layout -->
<div class="vt-field">
<label class="vt-label" for="region">Region</label>
<div class="vt-select">
<select id="region">
<option>US East (N. Virginia)</option>
<option>EU (Ireland)</option>
</select>
</div>
<span class="vt-field-hint">Closest to your users.</span>
</div>
<!-- Small size -->
<div class="vt-select vt-select--sm"><select>...</select></div>
<!-- Error state -->
<div class="vt-select vt-select--error">
<select><option value="">Select a role…</option></select>
</div>
<span class="vt-field-error">A role is required.</span>
<!-- Disabled -->
<div class="vt-select">
<select disabled><option>Production</option></select>
</div>
Components
Modal
Built on the native <dialog> element with
showModal(). Entry and exit animations use
@starting-style — no JavaScript animation required.
Click the backdrop to close.
Default modal
html ▶
<!-- Trigger button -->
<button class="vt-btn" onclick="document.getElementById('my-modal').showModal()">
Open modal
</button>
<!-- Default modal — entry/exit animation via @starting-style, no JS -->
<dialog class="vt-modal" id="my-modal">
<div class="vt-modal__header">
<h2 class="vt-modal__title">Publish changes</h2>
<button class="vt-modal__close" aria-label="Close"
onclick="this.closest('dialog').close()"><svg ...></svg></button>
</div>
<div class="vt-modal__body">
<p>Your changes will go live immediately.</p>
</div>
<div class="vt-modal__footer">
<button class="vt-btn vt-btn--surface" onclick="this.closest('dialog').close()">Cancel</button>
<button class="vt-btn" onclick="this.closest('dialog').close()">Publish</button>
</div>
</dialog>
<!-- Size variants -->
<dialog class="vt-modal vt-modal--sm" id="rename">...</dialog>
<dialog class="vt-modal vt-modal--lg" id="preview">...</dialog>
<!-- Danger variant -->
<dialog class="vt-modal vt-modal--danger" id="delete">...</dialog>
js — close on backdrop click ▶
document.querySelectorAll('.vt-modal').forEach(modal => {
modal.addEventListener('click', e => {
if (e.target === modal) modal.close();
});
});
Components
Toasts
Notification toasts created programmatically via Volty.toast().
Entry animation via @starting-style, countdown bar via
CSS animation, auto-dismiss after 4 seconds by default.
Try them
js ▶
// Simple message
Volty.toast('Copied to clipboard.')
// With options
Volty.toast({ message: 'Changes saved.', variant: 'success', title: 'Saved' })
Volty.toast({ message: 'Rate limit at 85%.', variant: 'warning', title: 'Warning' })
Volty.toast({ message: 'Could not connect.', variant: 'danger', title: 'Error' })
// Persistent — stays until user dismisses (duration: 0)
Volty.toast({ message: 'Action required.', title: 'Notice', duration: 0 })
// Custom duration in ms (default: 4000)
Volty.toast({ message: 'Deploying…', duration: 8000 })
Container Theming
Dark Island
Apply data-theme="dark" to any element to make it
render in dark mode — regardless of the page's current theme. Components inside
inherit the scoped tokens automatically. No JavaScript, no class toggling.
data-theme="dark" — always dark regardless of page theme
Dark Card
ActiveThis card is always dark. The parent div has data-theme="dark" so all components inside inherit dark tokens.
Nested Card
Nested inside the dark island — still dark.
html ▶
<!-- Any element with data-theme="dark" always renders dark -->
<div data-theme="dark">
<div class="vt-card">...</div>
<button class="vt-btn">Always dark</button>
<span class="vt-badge vt-badge--success">Active</span>
</div>
<!-- Pin a section to always-light -->
<div data-theme="light">...</div>
js ▶
// Override theme globally
Volty.setTheme('dark') // force dark
Volty.setTheme('light') // force light
Volty.setTheme('system') // follow OS preference (default)
Volty.getTheme() // → 'light' | 'dark' | 'system'
Volty.getSystemTheme() // → 'light' | 'dark' (actual resolved value)
Token Architecture
Brand Theming
Apply data-brand to any container to scope a different
brand color. The entire color scale — hover states, focus rings, badge tints — all
regenerate automatically via color-mix(). One token
powers everything.
Blue (default)
BadgeViolet
BadgeEmerald
BadgeRose
BadgeAmber
BadgeCyan
Badgehtml ▶
<!-- Scoped brand on any container — entire token scale regenerates -->
<section data-brand="violet">...</section>
<section data-brand="emerald">...</section>
<section data-brand="rose">...</section>
<section data-brand="amber">...</section>
<section data-brand="cyan">...</section>
js ▶
// Apply brand globally
Volty.setBrand('violet')
Volty.setBrand('emerald')
Volty.setBrand(null) // reset to default
Volty.getBrand() // → 'violet' | null
css — custom brand color ▶
/* Override --vt-color-brand — entire scale (50–950) regenerates via color-mix() */
:root {
--vt-color-brand: oklch(58% 0.21 310);
}
Design Tokens
Color Scale
The brand scale from 50 to 950 is generated entirely via color-mix(in oklch, ...)
from a single --vt-color-brand token. Change the brand
color above — every swatch updates instantly.
Brand scale (auto-generated)
Semantic surface tokens
Semantic status tokens
css ▶
/* Surface tokens */
--vt-color-surface /* page background */
--vt-color-surface-raised /* elevated (sidebars, inputs) */
--vt-color-surface-overlay /* highest (cards, modals) */
/* Text */
--vt-color-text
--vt-color-text-muted
/* Brand — entire scale (50–950) auto-generated via color-mix() */
--vt-color-brand
--vt-color-brand-subtle
--vt-color-brand-hover
--vt-color-brand-text
/* Semantic */
--vt-color-success / --vt-color-success-subtle
--vt-color-warning / --vt-color-warning-subtle
--vt-color-danger / --vt-color-danger-subtle
--vt-color-info / --vt-color-info-subtle
/* Override brand — entire scale regenerates automatically */
:root {
--vt-color-brand: oklch(58% 0.21 310);
}
Design Tokens
Type Scale
Each step uses clamp() with viewport-relative sizing.
The type scale flows smoothly between breakpoints — resize the browser to see it adapt.
All registered as @property tokens of type
<length>.
css ▶
/* Type scale — all registered as @property <length> tokens */
--vt-text-xs /* 12px */
--vt-text-sm /* 14px */
--vt-text-base /* 16px */
--vt-text-lg /* 18px */
--vt-text-xl /* 20px */
--vt-text-2xl /* 24px */
--vt-text-3xl /* 30px */
--vt-text-4xl /* 36px */
--vt-text-5xl /* 48px */
/* Font weight tokens */
--vt-font-weight-normal /* 400 */
--vt-font-weight-medium /* 500 */
--vt-font-weight-semibold /* 600 */
--vt-font-weight-bold /* 700 */
/* Usage example */
.my-heading {
font-size: var(--vt-text-3xl);
font-weight: var(--vt-font-weight-bold);
letter-spacing: -0.02em;
color: var(--vt-color-text);
}
Architecture
Layer Cascade
Volty uses @layer to establish a deterministic cascade
order. Later layers win. This means you can override any component style without
needing higher specificity — just target the right layer.
Layer Order
Declared once at the top of the stylesheet.
volty.reset
— box-sizing, margins
volty.tokens
— CSS custom properties
volty.base
— html, body defaults
volty.components
— button, card, etc.
volty.utilities
— your overrides go here
Why @property matters
Typed tokens unlock animation and proper fallbacks.
Color tokens can animate
Because --vt-color-brand is typed as <color>, CSS can interpolate between values — enabling smooth theme transitions.
Proper initial values
Unset tokens fall back to their initial-value rather than an empty string, preventing broken layouts.
Type validation
Setting a <length> token to a color value is silently ignored — no cascading breakage.
css ▶
/* @layer order — declared once, deterministic cascade */
@layer
volty.reset, /* box-sizing, margin resets */
volty.tokens, /* @property custom properties */
volty.base, /* html, body defaults */
volty.components, /* .vt-btn, .vt-card, etc. */
volty.utilities; /* your overrides — always win */
/* Override any component without specificity battles */
@layer volty.utilities {
.vt-btn { border-radius: 2px; }
}
/* @property registration (44 tokens total) */
@property --vt-color-brand {
syntax: '<color>';
inherits: true;
initial-value: oklch(55% 0.2 250);
}
The Differentiator
@property Token Transitions
Because every color token is registered as a typed <color>
via @property, CSS can interpolate between values.
Volty puts transition: --vt-color-* 350ms ease on
:root — so every element on the page inherits
the animation with zero component-level transition rules.
No other library ships this. Try it — click a brand or theme above and watch the swatches below fade.
background: var(--vt-color-brand) etc. — with no
transition rule of their own. They animate because
:root transitions the token itself. Untyped custom
properties cannot do this — the browser would snap instantly since it can't interpolate
an untyped string. @property is what makes this possible.
Full UI — changes animate because tokens do
Live Token Demo
ActiveSwitch the brand or theme — every color here fades in 350ms. No JS animation, no requestAnimationFrame.
brand-50 → brand-950 — all from one token, all animated
css ▶
/* Volty registers all color tokens as typed <color> via @property */
@property --vt-color-brand {
syntax: '<color>';
inherits: true;
initial-value: oklch(55% 0.2 250);
}
/* :root transitions the token itself — every element inherits the animation */
:root {
transition:
--vt-color-brand 350ms ease,
--vt-color-surface 350ms ease,
--vt-color-surface-raised 350ms ease,
--vt-color-text 350ms ease;
}
/* No transition rule needed on components — they animate for free */
.vt-btn {
background: var(--vt-color-brand); /* animates when brand changes */
color: var(--vt-color-brand-text);
}
js ▶
// Each call triggers a smooth 350ms color fade across the entire page
Volty.setBrand('violet') // → all brand tokens animate to violet
Volty.setTheme('dark') // → all surface + text tokens animate to dark values
The Differentiator
Real Container Queries
Components adapt to their container's width, not the viewport. Drag the handles below to resize — the components respond to their own available space. No viewport media queries, no JavaScript resize observers.
Button — expands to full width below 280px container
container: ~260px → buttons go full width
container: ~400px → buttons are inline
Card — footer aligns right above 420px container
container: ~320px → footer stacks
Narrow card
Footer buttons stack to the start.
container: ~500px → footer aligns right
Wide card
Footer buttons align to the end.
Field — inline layout above 400px container
container: ~280px → label stacks above
container: ~500px → label inline with input
css — how it works ▶
/* Components use container-type — they respond to their own container, not viewport */
.vt-card {
container-type: inline-size;
container-name: card;
}
/* Footer aligns right above 420px — no media query needed */
@container card (min-width: 420px) {
.vt-card__footer { justify-content: flex-end; }
}
/* Buttons go full-width below 280px container */
@container (max-width: 280px) {
.vt-btn { width: 100%; justify-content: center; }
}
/* Inline field layout above 400px container */
@container (min-width: 400px) {
.vt-field--inline { flex-direction: row; align-items: center; }
}
The Differentiator
Dark Mode Color Correctness
Most CSS libraries hardcode color-mix(... white) for
subtle tints — which produces pale muddy grays in dark mode instead of dark tints.
Volty mixes into --vt-color-surface so subtle colors
are always correct: light tints in light mode, dark tints in dark mode.
data-theme="light" — always light
subtle = color-mix(brand, surface-light) ✓
data-theme="dark" — always dark
subtle = color-mix(brand, surface-dark) ✓
What mixing with white looks like in dark mode (the broken approach)
dark mode — wrong: color-mix(..., white)
dark mode — correct: color-mix(..., surface)
css ▶
/* ❌ Broken approach — mixing into white produces pale muddy tints in dark mode */
background: color-mix(in oklch, var(--vt-color-success) 15%, white);
/* ✅ Volty's approach — mix into surface so dark mode gets rich dark tints */
background: color-mix(in oklch, var(--vt-color-success) 15%, var(--vt-color-surface));
/* This is how all subtle tokens are defined in Volty */
--vt-color-success-subtle: color-mix(
in oklch,
var(--vt-color-success) 15%,
var(--vt-color-surface) /* dark surface in dark mode → rich dark tint ✓ */
);
/* Usage — tokens adapt automatically in both modes */
.vt-badge--success {
background: var(--vt-color-success-subtle);
color: var(--vt-color-success);
}
Differentiator
Shadow DOM — tokens for free
CSS custom properties don't normally cross shadow boundaries. But
@property with inherits: true
makes the browser treat Volty tokens exactly like built-in inherited properties
(color, font-size) —
they pass through automatically. No CSS injection. No ::part(). Nothing.
Live Shadow DOM component
The card below lives inside a real Shadow Root. Change the brand color or theme above — it updates instantly because the tokens inherit across the boundary.
Why it works
All 44 Volty tokens are registered like this:
syntax: "<color>";
inherits: true; /* ← this is the key */
initial-value: oklch(55% 0.2 250);
}
inherits: true is what no other library does. It registers the property as an inherited CSS property — which the browser automatically propagates into shadow trees from the host element.
Tokens that cross the boundary
JS API
shadow.innerHTML = `<div class="vt-card">...</div>`;
'--vt-color-brand': 'oklch(55% 0.22 10)'
});
js ▶
// Attach shadow root — tokens flow in automatically via @property inherits: true
const shadow = Volty.createShadow(hostElement);
shadow.innerHTML = `<div class="vt-card">
<div class="vt-card__header">
<h3 class="vt-card__title">Shadow card</h3>
</div>
</div>`;
// Adopt component styles into an existing shadow root
Volty.adoptStyles(existingShadowRoot);
// Scoped token override — cascades into all shadow children automatically
Volty.setTokens(hostElement, {
'--vt-color-brand': 'oklch(55% 0.22 10)',
});
js — web component example ▶
class MyCard extends HTMLElement {
connectedCallback() {
const shadow = Volty.createShadow(this);
shadow.innerHTML = `
<div class="vt-card vt-card--raised">
<div class="vt-card__header">
<h3 class="vt-card__title"><slot name="title"></slot></h3>
</div>
<div class="vt-card__body"><slot></slot></div>
</div>`;
}
}
customElements.define('my-card', MyCard);
// Usage — all Volty tokens flow in automatically
// <my-card>
// <span slot="title">Hello from Shadow DOM</span>
// Body content — tokens cross the boundary for free.
// </my-card>
AI / Schema
Machine-readable by design
@property gives every token a declared type, a fallback, and an inheritance contract.
That's a schema. Volty generates volty.schema.json and
llms.txt at build time — so AI tools can reason about your
design system with the same precision the browser uses.
volty.schema.json
All 44 tokens with their syntax, initial-value, and group. All 11 components with modifiers, elements, and usage notes. Auto-generated from @property declarations on every build — never out of sync.
llms.txt
7.7 kB compact reference — the entire Volty API in a format that fits in any context window. Paste it into Cursor, Claude, or Copilot to get accurate component output with no hallucinated class names.
View llms.txtTyped tokens = browser validation
If an AI generates --vt-color-brand: 16px, the browser rejects it and falls back to initial-value. The type contract is enforced at parse time — not just in documentation.
8 code-generation rules baked into llms.txt
.vt- prefix.<color>, <length>, or <time>.--vt-color-danger, not hardcoded oklch values).<dialog> + el.showModal(). Never div-based modals.Volty.toast() — never hand-write .vt-toast HTML.container-type: inline-size to parents that should trigger responsive behavior..vt-field with a .vt-label sibling.@property tokens have initial-value fallbacks — missing token references render correctly.