The first UI framework built for AI agents.
14 core components + dashboard add-on. Pure CSS interactions. Zero runtime JavaScript.
MCP server lets Claude Desktop, Cursor, and any AI agent control your UI with typed tools.
A2UI catalog makes your components discoverable across Google's agent ecosystem.
Agent API gives LLMs structured access to every component on the page.
- Component Showcase — Interactive demo of all 14 components and the Agent API
- Helio FAQ Demo — A fictional SaaS product page with a live AI support assistant powered by
<z-agent>— shows what a real customer would experience (Source)
Save this as an HTML file and open it in your browser. No install, no build step, no dependencies:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/zephyr-framework@1/zephyr-framework.min.css">
</head>
<body>
<z-accordion>
<z-accordion-item>
<button slot="trigger">What is Zephyr?</button>
<div slot="content">A UI framework built for AI agents. Zero JS. Pure CSS interactions.</div>
</z-accordion-item>
</z-accordion>
<z-modal id="demo">
<h2>Hello from Zephyr</h2>
<p>An AI agent can open this with: <code>Zephyr.agent.act('#demo', 'open')</code></p>
</z-modal>
<button data-trigger="demo">Open Modal</button>
<z-tabs>
<button slot="tab" data-tab="one" data-active>First</button>
<button slot="tab" data-tab="two">Second</button>
<div slot="panel" data-tab="one">Tab content here.</div>
<div slot="panel" data-tab="two">More content here.</div>
</z-tabs>
<script src="https://cdn.jsdelivr.net/npm/zephyr-framework@1/zephyr-framework.min.js"></script>
</body>
</html>That's it. Three components, zero JavaScript, working in your browser.
Want a live, deployed site with a real <z-agent> chat widget? Fork the reference demo and deploy it to Vercel. Bring your own Anthropic API key:
The demo includes a serverless proxy (/api/chat.js) that keeps your key server-side, never exposed in the browser. Source: zephyr-agent-demo.
Two paths to production:
| DIY (free) | Hosted (zephyr-agent.sh, $9 once) | |
|---|---|---|
| Fork the demo, deploy your own proxy | ✓ | ✗ |
| No backend to maintain | ✗ | ✓ |
| Works on Webflow, Squarespace, GitHub Pages | ✗ | ✓ |
| BYOK encrypted server-side | ✓ | ✓ |
npm (recommended for projects):
npm install zephyr-frameworkCDN (no install):
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/zephyr-framework@1/zephyr-framework.min.css">
<script src="https://cdn.jsdelivr.net/npm/zephyr-framework@1/zephyr-framework.min.js"></script>Scaffold a new project (includes MCP server):
npx create-zephyr-framework my-app
cd my-app
npm startZephyr is designed for AI agents. Pick the integration that fits your stack:
| Integration | What it does | Setup |
|---|---|---|
| MCP Server | Claude Desktop, Cursor, VS Code Copilot, Windsurf, or any MCP client controls your UI | npx zephyr-mcp |
| AI SDK Tools | Vercel AI SDK tool() definitions for generateText/streamText |
Example project |
| Chat Widget | Drop-in <z-agent> element — visitors chat with your UI on any deployed page |
<z-agent data-provider="anthropic"> |
| A2UI Catalog | Google's Agent-to-UI spec — makes components discoverable across agent ecosystems | Automatic |
| Agent API | Zephyr.agent.getState(), .act(), .describe(), .render(), .compose() |
Built into the framework |
The MCP server works with any client that supports the Model Context Protocol:
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"zephyr": {
"command": "npx",
"args": ["zephyr-mcp"],
"env": { "ZEPHYR_ROOT": "/path/to/my-app" }
}
}
}Cursor
Add to .cursor/mcp.json in your project root:
{
"mcpServers": {
"zephyr": {
"command": "npx",
"args": ["zephyr-mcp"],
"env": { "ZEPHYR_ROOT": "/path/to/my-app" }
}
}
}Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"zephyr": {
"command": "npx",
"args": ["zephyr-mcp"],
"env": { "ZEPHYR_ROOT": "/path/to/my-app" }
}
}
}VS Code (with Copilot)
Add to your VS Code settings.json:
{
"mcp": {
"servers": {
"zephyr": {
"command": "npx",
"args": ["zephyr-mcp"],
"env": { "ZEPHYR_ROOT": "/path/to/my-app" }
}
}
}
}Any other MCP client
The Zephyr MCP server uses stdio transport (JSON-RPC 2.0 over stdin/stdout). Any MCP-compatible host can spawn it as a child process. Set ZEPHYR_ROOT to your project directory and ZEPHYR_MCP_PORT to change the bridge port (default 3456).
After connecting, the agent gets 6 tools: zephyr_act, zephyr_get_state, zephyr_describe, zephyr_set_state, zephyr_get_schema, zephyr_get_prompt. Ask it to open a modal, switch a tab, or inspect the page.
- Component Showcase — Interactive demo of all 14 components and the Agent API
- Helio FAQ Demo — A fictional SaaS product page with a live AI support assistant powered by
<z-agent>— shows what a real customer would experience (Source)
Every UI framework today was built for humans writing code. Zephyr was built for AI agents driving interfaces.
When an agent needs to interact with a web UI, it typically has two options — both terrible:
- Screenshot parsing (computer use) — Slow, brittle, expensive. A CSS change breaks everything. Costs thousands of tokens per interaction.
- Raw HTML analysis — The agent parses DOM structure, guesses what elements do, and writes JavaScript to manipulate them. Error-prone, high token cost, fragile.
Zephyr eliminates both problems. Agents get typed, structured tools that work every time:
Agent thinks: "I need to open the settings modal"
Agent calls: zephyr_act({ selector: '#settings', action: 'open' })
Agent gets: { success: true }
No screenshots. No DOM parsing. No guessing. One function call.
Why this works so well:
- State lives in
data-*attributes — not hidden in JS closures or framework internals. An agent can readdata-openon any component and instantly know its state. - Semantic custom elements — LLMs understand
<z-modal>,<z-select>,<z-accordion>without reading docs. The tag name is the documentation. - Zero runtime JS — No framework code runs during interactions, which means no race conditions, no async state to track, no timing issues for agents.
- Minimal API surface — The entire agent interface fits in a few hundred tokens. Low context window cost means agents can hold the full Zephyr API in memory while doing other work.
The Model Context Protocol server is the killer feature. It turns every Zephyr component on a live page into a tool that Claude Desktop, Cursor, or any MCP-compatible host can call directly.
Claude Desktop / Cursor / Any MCP Host
↕ stdio (JSON-RPC 2.0)
zephyr-mcp/server.js
↕ WebSocket (localhost:3456)
Your browser tab
↕ direct JavaScript calls
Zephyr.agent API → Your components
The agent doesn't see HTML. It sees typed tools with structured inputs and outputs. It calls zephyr_act('#modal', 'open') and gets { success: true }. It calls zephyr_get_state() and gets a JSON array of every component's current state, actions, and metadata.
See the Quick Start above for config examples for Claude Desktop, Cursor, Windsurf, VS Code, and any other MCP client.
If running from the framework repo (not via npm):
cd zephyr-mcp && npm install
node server.js # starts bridge on http://localhost:3456Open http://localhost:3456 in your browser. The agent can now control the page.
| Tool | What it does | Example |
|---|---|---|
zephyr_act |
Perform actions on components | act('#modal', 'open'), act('#select', 'select', { value: 'red' }) |
zephyr_get_state |
Snapshot all component states | Returns [{ tag, id, state, actions }] for every component |
zephyr_describe |
Deep-inspect a single component | Returns state, actions, slots, events, ARIA info |
zephyr_set_state |
Set/remove data attributes | setState('#dropdown', { 'data-open': true }) |
zephyr_get_schema |
Get the full component reference | All 14 components with actions, events, methods |
zephyr_get_prompt |
Generate an LLM context summary | Markdown of current page state for agent context windows |
- Agent-controlled dashboards — An agent calls
getSchema()to learn available components, generates a page with tabs/selects/modals, then controls everything viaact()— switching views, opening detail panels, selecting filters. No Playwright. No Puppeteer. Just tool calls. - AI-powered testing — Instead of writing brittle E2E scripts, tell Claude: "Test that the accordion opens, closes, and fires the toggle event." The agent uses
getState()to check component states andact()to interact. Tests that describe themselves in natural language. - Agent-assisted form filling — An agent fills complex forms by calling
act('#country', 'select', { value: 'US' })andact('#date', 'set', { value: '2026-04-01' }). These are form-associated components that participate in native FormData, so the form just works — no custom serialization. - Live UI debugging — Working in Claude Code or Cursor and something looks wrong? Ask the agent to inspect the page — it calls
getState()and instantly sees every component's state as structured JSON. No DevTools, no console logging, no manual inspection. - Custom agent skills — Build skills that leverage Zephyr as their rendering layer. Your skill calls
getSchema()to know what's available,act()to control components, andgetPrompt()to inject UI context into the agent's working memory.
See zephyr-mcp/README.md for the full tool reference, architecture diagram, and troubleshooting.
A2UI (Agent-to-UI) is Google's protocol for how AI agents describe, discover, and render UIs. Zephyr publishes its components as an A2UI catalog in zephyr-a2ui-catalog.json, making them instantly available to any agent in the ecosystem.
Think of it this way: MCP is the waiter (it executes actions on a live page) and A2UI is the menu (it tells agents what components exist and what they can do). Together, they give agents the complete picture.
- Agent discovery — When an A2UI-compatible agent (Gemini, CopilotKit, or any agent using the protocol) needs to build a UI, it browses component catalogs. Zephyr's catalog says: "I have 14 components — here's a modal with open/close, a select with typed options, a carousel with next/prev." The agent picks the right components without integration code.
- Cross-platform rendering — A2UI is renderer-agnostic. An agent using Zephyr's catalog could render components as Web Components, Flutter widgets, React components, or any future renderer. Define the component once; any renderer knows how to display it.
- Structured contracts — The catalog formally documents what each component accepts (properties), does (actions), reports (events), and provides (ARIA). This powers auto-generated docs, visual component browsers, and validation of agent-generated UIs.
- Ecosystem positioning — Google backs A2UI as the standard for agent-driven interfaces (deployed in Gemini Enterprise, Opal). Having a catalog means Zephyr shows up alongside Google's own components when agents search for UI toolkits.
The <z-agent> widget brings agent control to deployed production sites. Drop one tag into your HTML and visitors get a chat interface that can control every Zephyr component on the page through natural language.
<script src="zephyr-framework.js"></script>
<script src="zephyr-agent-widget.js"></script>
<!-- Demo mode (API key in source — not for production) -->
<z-agent data-api-key="sk-ant-..." data-provider="anthropic"></z-agent>
<!-- Production mode (proxy keeps your API key server-side) -->
<z-agent data-endpoint="https://api.mysite.com/agent/chat"></z-agent>A visitor types "open the settings modal" — the agent calls Zephyr.agent.act('#settings', 'open') in the browser and the modal opens. The component briefly pulses blue to show what was touched.
Two API modes:
| Mode | Attribute | Use case |
|---|---|---|
| Direct | data-api-key |
Quick demos. Calls LLM API from browser. Key visible in source. |
| Proxy | data-endpoint |
Production. POST to your backend, which forwards to any LLM. |
Supports Anthropic and OpenAI — set data-provider="anthropic" (default) or data-provider="openai".
Attributes:
| Attribute | Default | Description |
|---|---|---|
data-endpoint |
— | Proxy URL for production |
data-api-key |
— | Direct API key for demos |
data-provider |
anthropic |
anthropic or openai |
data-model |
per provider | Model identifier |
data-position |
bottom-right |
bottom-right, bottom-left, top-right, top-left |
data-placeholder |
Ask about this page... |
Input placeholder text |
data-greeting |
Hi! I can help you... |
First message shown |
Methods: open(), close(), send(message), clear()
Events: open, close, message ({ role, content }), action ({ selector, action, params, result }), error ({ message })
How MCP and the widget compare:
| MCP Server | <z-agent> Widget |
|
|---|---|---|
| Where it runs | Developer's machine | Any deployed site |
| Who uses it | Developer via Claude Desktop | End users visiting the site |
| Transport | stdio + localhost WebSocket | HTTPS to LLM API or proxy |
| Dependencies | Node.js, npm packages | None (browser-native fetch) |
MCP is for building. The widget is for shipping.
The Zephyr.agent namespace gives any JavaScript-based agent structured access to components on the page. This is what the MCP server calls under the hood, but you can also use it directly in browser-based agents, testing scripts, or custom tooling.
// Snapshot all component states on the page
Zephyr.agent.getState()
// → [{ tag: 'z-modal', id: 'my-modal', state: {}, actions: ['open', 'close'] }, ...]
// Describe a specific component instance
Zephyr.agent.describe('#my-select')
// → { tag, id, state, actions: ['open','close','select'], slots, events, methods }
// Perform high-level actions (no DOM knowledge needed)
Zephyr.agent.act('#my-modal', 'open')
Zephyr.agent.act('#my-select', 'select', { value: 'red' })
Zephyr.agent.act('#carousel', 'next')
// Set/remove attributes directly
Zephyr.agent.setState('#dropdown', { 'data-open': true })
// Watch for state changes across all components
Zephyr.agent.observe((change) => {
console.log(change.tag, change.attribute, change.newValue);
});
// Get component schema with actions, descriptions
Zephyr.agent.getSchema()
// Generate an LLM-ready prompt for the current page
Zephyr.agent.getPrompt()
// Add data-z-actions attributes for DOM-level discovery
Zephyr.agent.annotate()
// Headless mode — disable transitions for fast agent operations
Zephyr.agent.headless(true) // enable
Zephyr.agent.headless(false) // disable
// Structured state diffs — richer than observe()
Zephyr.agent.observeDiffs((diff) => {
console.log(diff.component, diff.property, diff.from, '→', diff.to);
// { component: 'z-modal', id: 'settings', property: 'data-open', from: null, to: '', timestamp: 1711... }
});
// Record and replay agent actions
const rec = Zephyr.agent.record();
Zephyr.agent.act('#modal', 'open');
Zephyr.agent.act('#select', 'select', { value: 'red' });
const actions = rec.stop();
// Replay instantly, or with delays
await Zephyr.agent.replay(actions);
await Zephyr.agent.replay(actions, { delay: 500 });
await Zephyr.agent.replay(actions, { realtime: true });
// Multi-agent coordination — prevent conflicting mutations
Zephyr.agent.lock('#checkout-form', 'agent-checkout');
Zephyr.agent.act('#checkout-form', 'submit', { _agentId: 'agent-checkout' }); // works
Zephyr.agent.act('#checkout-form', 'submit'); // rejected — locked
Zephyr.agent.unlock('#checkout-form', 'agent-checkout');
Zephyr.agent.locks(); // list all active locks
// Guard destructive actions — require confirmation before executing
Zephyr.agent.guard('z-modal', ['close']);
Zephyr.agent.act('#modal', 'close');
// → { success: false, pending: true, confirmId: 'zg_1_...' }
Zephyr.agent.confirm('zg_1_...'); // executes the close
Zephyr.agent.deny('zg_1_...'); // cancels it
Zephyr.agent.guarded(); // list all pending confirmations
Zephyr.agent.unguard('z-modal'); // remove the guardA full machine-readable schema is available in zephyr-schema.json, and an LLM system prompt template in zephyr-prompt.md.
Most "zero-JS" frameworks sacrifice interactivity. Zephyr doesn't. The framework JavaScript registers custom elements and attaches event listeners at page load. From that point on, no JavaScript executes during user interactions — all animations, state changes, and visual feedback are driven by CSS.
This is fundamentally different from React, Vue, or Alpine.js, where JavaScript runs on every click, keystroke, and state change.
| Interaction | Mechanism |
|---|---|
| Accordion open/close | CSS Grid grid-template-rows: 0fr to 1fr transition |
| Tab switching | CSS :has([data-active]) + View Transitions API |
| Modal animation | Native <dialog> + CSS ::backdrop + keyframes |
| Dropdown toggle | CSS opacity/transform transition on [data-open] |
| Form validation | CSS :user-invalid / :user-valid pseudo-classes |
| Dark mode | CSS :has([data-theme="dark"]) cascade |
| Scroll effects | CSS animation-timeline: view() |
<link rel="stylesheet" href="zephyr-framework.css">
<script src="zephyr-framework.js"></script>
<z-accordion>
<z-accordion-item>
<button slot="trigger">Section title</button>
<div slot="content">
<div>Section content goes here.</div>
</div>
</z-accordion-item>
</z-accordion>That's it. No build step, no npm, no config files.
Optional add-on for data-dense, agent-composed dashboards. Load alongside the core framework:
<link rel="stylesheet" href="zephyr-dashboard.css">
<script src="zephyr-dashboard.js"></script>4 new components: z-stat (KPI cards), z-dashboard (grid layout), z-data-grid (sortable tables), z-chart (candlestick/line/area/histogram via lightweight-charts).
Agent Render API: Agents can dynamically create components with Zephyr.agent.render() and compose full dashboards with Zephyr.agent.compose().
// Agent builds a dashboard from natural language
Zephyr.agent.compose('#app', {
tag: 'z-dashboard', id: 'dash', attributes: { 'data-columns': '3' },
panels: [
{ id: 'chart', colspan: 2, title: 'Revenue', component: { tag: 'z-chart', attributes: { 'data-type': 'area' } } },
{ id: 'users', title: 'Users', component: { tag: 'z-stat', attributes: { 'data-label': 'Active Users', 'data-value': '12,345', 'data-trend': 'up' } } },
{ id: 'table', colspan: 3, title: 'Data', component: { tag: 'z-data-grid', id: 'grid' } }
]
});See the Agent Dashboard Demo for a live example with crypto, server monitoring, and IoT sensor themes.
The dashboard shows data. The runtime executes workflows. It's a terminal-style activity stream where events flow, agents respond inline, and UI panels appear on demand.
<link rel="stylesheet" href="zephyr-runtime.css">
<script src="zephyr-runtime.js"></script>4 new components:
z-stream— Scrolling, append-only activity container with auto-scroll, max-entries pruning, and jump-to-latestz-stream-entry— Typed rows (event, agent, command, ui, alert, trade, system) with CSS-driven collapse, pin, and status progressionz-command— Unified input bar for slash commands and natural language chat, with historyz-ticker— Horizontal scrolling ticker strip for live data (CSS-animated, pauses on hover)
Stream API: Agents append entries to the stream, optionally with inline components:
// Append a trade event
const stream = document.querySelector('z-stream');
stream.append({
type: 'trade',
header: 'BUY 100 AAPL @ MARKET — Smith Family Trust',
status: 'success',
autoCollapse: 30000
});
// Agent streams an inline chart
Zephyr.agent.stream('#my-stream', {
type: 'ui',
header: 'AAPL Price History',
component: { tag: 'z-chart', attributes: { 'data-type': 'line' } }
});Data Sources: Pull-based live data loops for real-time updates:
Zephyr.agent.registerSource('quotes', {
interval: 2000,
fetch: () => getLatestQuotes()
});
Zephyr.agent.connectSource('quotes', '#ticker', 'setItems');Electron demo: Run npm run demo:runtime in zephyr-browser/ for the full experience — a Bloomberg Terminal-style interface where an AI agent executes financial workflows in real time.
<z-accordion>
<z-accordion-item>
<button slot="trigger">Section 1</button>
<div slot="content">
<div>Content goes here</div>
</div>
</z-accordion-item>
</z-accordion><z-tabs>
<div role="tablist">
<button data-tab="tab1" role="tab">Tab 1</button>
<button data-tab="tab2" role="tab">Tab 2</button>
</div>
<div data-tab-panel="tab1" role="tabpanel">Panel 1</div>
<div data-tab-panel="tab2" role="tabpanel">Panel 2</div>
</z-tabs><z-carousel data-autoplay="3000">
<div slot="item">Slide 1</div>
<div slot="item">Slide 2</div>
<div slot="item">Slide 3</div>
<button data-prev>Previous</button>
<button data-next>Next</button>
</z-carousel><button data-action="open-modal" data-target="my-modal">Open</button>
<z-modal id="my-modal">
<h2>Modal Title</h2>
<p>Content</p>
<button data-action="close-modal" data-target="my-modal">Close</button>
</z-modal>Or programmatically:
document.getElementById('my-modal').open();
document.getElementById('my-modal').close();<z-dropdown>
<button slot="trigger">Menu</button>
<div slot="content">
<a href="#">Item 1</a>
<a href="#">Item 2</a>
</div>
</z-dropdown>Zephyr.toast('Message', 3000); // duration in msForm-associated custom element. Works with native <form> submission via ElementInternals.
<form>
<z-select name="color">
<button slot="trigger">Choose color</button>
<div slot="options">
<div data-value="red">Red</div>
<div data-value="blue">Blue</div>
</div>
</z-select>
</form>Filterable dropdown with type-ahead. Arrow keys to navigate, Enter to select, Escape to close.
<z-combobox name="language">
<input type="text" placeholder="Search...">
<div slot="listbox">
<div data-value="js">JavaScript</div>
<div data-value="py">Python</div>
<div data-value="rs">Rust</div>
</div>
</z-combobox>Wraps native <input type="date"> with a styled trigger and formatted display.
<z-datepicker name="birthday">
<button slot="display">Choose a date</button>
</z-datepicker>Drag-and-drop zone with progress bars. Call setProgress(index, percent) to update individual files.
<z-file-upload data-multiple data-accept="image/*">
<div slot="dropzone">Drop files here or click to browse</div>
<div slot="filelist"></div>
</z-file-upload>
<script>
document.querySelector('z-file-upload').addEventListener('upload', (e) => {
e.detail.files.forEach((file, idx) => {
// Upload file, then update progress:
// e.target.setProgress(idx, percentComplete);
});
});
</script>Fires loadmore when a sentinel enters the viewport. Set data-loading during fetches, call complete() when done.
<z-infinite-scroll data-root-margin="200px">
<div id="content"></div>
</z-infinite-scroll>
<script>
document.querySelector('z-infinite-scroll').addEventListener('loadmore', () => {
// Fetch and append more items
});
</script>Native HTML Drag and Drop. Children with [data-sortable] become draggable.
<z-sortable>
<div data-sortable="1">Item 1</div>
<div data-sortable="2">Item 2</div>
<div data-sortable="3">Item 3</div>
</z-sortable>Dispatches sort event with detail: { order }.
Renders only visible items. Handles 10,000+ rows with ~20 DOM nodes.
<z-virtual-list data-item-height="40" data-buffer="5" style="height: 400px;"></z-virtual-list>
<script>
const list = document.querySelector('z-virtual-list');
list.setRenderer((item, idx) => `<div>${item.name}</div>`);
list.setItems(arrayOf10000Items);
</script>All components dispatch DOM events for state changes:
| Component | Event | Detail |
|---|---|---|
z-accordion |
toggle |
{ index, open } |
z-modal |
open |
-- |
z-modal |
close |
-- |
z-select |
change |
-- |
z-carousel |
slide |
{ index, direction } |
z-dropdown |
toggle |
{ open } |
z-toast |
show |
{ message } |
z-toast |
hide |
-- |
z-combobox |
change |
-- |
z-combobox |
input |
-- |
z-datepicker |
change |
-- |
z-infinite-scroll |
loadmore |
-- |
z-sortable |
sort |
{ order } |
z-file-upload |
upload |
{ files } |
document.querySelector('z-carousel').addEventListener('slide', (e) => {
console.log(`Moved to slide ${e.detail.index}`);
});Override CSS custom properties to theme all components:
:root {
--z-transition-duration: 0.3s;
--z-transition-timing: ease;
--z-border-radius: 0.5rem;
--z-border-radius-sm: 0.375rem;
--z-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--z-shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--z-z-index-dropdown: 50;
--z-z-index-toast: 1000;
--z-backdrop-blur: 4px;
--z-toast-bg: rgb(17 24 39);
--z-toast-color: white;
}Or target components directly:
z-modal dialog {
max-width: 600px;
border-radius: 1rem;
}<button id="theme-toggle">Toggle Dark Mode</button>
<script>
document.getElementById('theme-toggle').addEventListener('click', () => {
document.body.toggleAttribute('data-theme');
});
</script>CSS automatically applies dark styles via :has([data-theme="dark"]).
<label>Email
<input type="email" required>
</label>Invalid inputs get styled via label:has(+ input:user-invalid). No JavaScript validation needed.
<button>
<span data-loading></span>
Submit
</button>Automatically shows a spinner and disables via button:has([data-loading]).
<div class="fade-in-on-scroll">
Content fades in as you scroll
</div>Uses animation-timeline: view() for scroll-driven animations.
<div class="container">
<div class="container-sm:grid-cols-2">
<!-- 2 columns when container > 640px -->
</div>
</div>Components respond to their container size, not the viewport.
Access component metadata programmatically:
Zephyr.components.modal
// { tag: 'z-modal', slots: [], attributes: [], events: ['open', 'close'], methods: ['open()', 'close()'] }- Chrome/Edge 111+ (View Transitions)
- Safari 15.4+ (
:has()) - Firefox 121+ (Container Queries)
Graceful degradation for older browsers — components work without animations.
- No innerHTML with user content: Components use DOM node manipulation (
appendChild) rather thaninnerHTMLto prevent XSS - CSP-compatible: No inline event handlers; all events attached via
addEventListener - Content trust model: Slot content is assumed to come from the page author (server-rendered HTML). Sanitize any dynamic user content before insertion
- No eval or Function constructor: The framework never evaluates strings as code
- 0kb JavaScript sent to users for interactions
- GPU-accelerated CSS animations (transform, opacity only)
- No layout thrashing — CSS transitions don't trigger reflow
- Shared event delegation — single document-level click-outside handler
- Cleanup on disconnect — all intervals and listeners cleaned up via
disconnectedCallback
| Package | npm | Description |
|---|---|---|
zephyr-framework |
npm install zephyr-framework |
Core framework — 14 web components, CSS, agent API, schema, prompt template, A2UI catalog |
zephyr-framework-mcp |
npm install zephyr-framework-mcp |
MCP server — bridges Claude Desktop, Cursor, and any MCP host to Zephyr components in the browser |
create-zephyr-framework |
npx create-zephyr-framework my-app |
CLI scaffolder — creates a ready-to-run project with framework, MCP server, and starter page |
Source: All three packages live in the zephyr-framework monorepo (zephyr-mcp/ and create-zephyr-framework/ subdirectories).
