# PettyUI v2: Web Components Architecture ## Overview PettyUI v2 is a complete rewrite from SolidJS to vanilla Web Components. Zero framework dependencies. Zero external runtime. Built on modern web platform APIs (Popover, ``, Invoker Commands, Navigation API, View Transitions). AI-native with Zod schemas and MCP tools. **Goal:** The smallest, fastest headless UI component library that exists, because it builds on the browser instead of replacing it. Capable of powering full SPA experiences with zero client-side framework. **Non-goals:** Styled components. Shadow DOM. Framework-specific bindings. Build step requirements. ## Architecture ### Core Principles 1. **Browser-first** — use native APIs before writing JS. Popover API for overlays, `` for modals, Invoker Commands for triggers, Navigation API for routing, View Transitions for animations, `
` for disclosure. 2. **Progressive enhancement** — components that can work before JS loads DO work before JS loads. JS upgrades add ARIA, keyboard navigation, and programmatic APIs. 3. **Zero dependencies** — no Lit, no Floating UI, no framework. The only runtime is a ~30-line signals utility (~500 bytes) bundled inline. 4. **AI-native** — Zod schemas describe every component's API. MCP tools (discover, inspect, compose, validate, add) let AI agents generate correct HTML without reading docs. 5. **Full SPA capability** — Navigation API + View Transitions replace client-side routers. PettyUI apps feel like SPAs with zero framework overhead. ### Tech Stack - **Language:** TypeScript, compiled to ES modules - **Reactivity:** Homegrown signals (~30 lines, ~500 bytes), aligned with TC39 Signals proposal - **Components:** Custom Elements v1 (no Shadow DOM) - **Positioning:** Simple CSS (relative/absolute). Consumer adds Floating UI if they need viewport-edge flipping — that's a styling concern, not a library concern. - **Overlays:** Popover API + `` element - **Triggers:** Invoker Commands API (`commandfor`/`command` attributes) - **Routing:** Navigation API (browser-native SPA router, Baseline Jan 2026) - **Transitions:** View Transitions API (smooth page/component animations, Baseline 2025) - **Build:** tsdown (same as v1) - **Testing:** Vitest + @web/test-runner (real browser tests for Custom Elements) - **Validation:** Zod v4 schemas (carried over from v1) ### Signals Utility The entire reactivity system, bundled with the library (not an external dep): ```ts let current: (() => void) | null = null; interface Signal { get(): T; set(value: T): void; } function signal(value: T): Signal { const subs = new Set<() => void>(); return { get() { if (current) subs.add(current); return value; }, set(v: T) { value = v; for (const fn of subs) fn(); }, }; } function effect(fn: () => void): void { const prev = current; current = fn; fn(); current = prev; } ``` Components use `signal()` for internal state and `effect()` to sync state to DOM attributes/properties. ## Component Anatomy ### Three Tiers **Tier 1 — HTML-native (works without JS):** - Dialog, Popover, Tooltip, Collapsible/Accordion (via `
`), Alert, Separator, Link - JS upgrade adds: ARIA linking, keyboard nav, custom events, programmatic API **Tier 2 — Popover-enhanced (partially works without JS):** - Select, DropdownMenu, ContextMenu, HoverCard, DatePicker, CommandPalette - Popover API handles open/close. JS adds: keyboard nav, typeahead, ARIA, selection state **Tier 3 — JS-required (inert until upgrade):** - Tabs, Slider, Calendar, DataTable, VirtualList, Combobox, Wizard, Form, NumberField, Toast, Pagination - These components need JS for core functionality. They render as plain HTML until the custom element upgrades. ### Composition: Nested Custom Elements Complex components use nested custom elements. Each part is a registered element with its own lifecycle. Parts find their parent via `this.closest()`. ```html Confirm Are you sure? ``` **Rule:** If a part needs its own behavior (keyboard handling, ARIA management, event emission), it's a custom element. If it's pure structure, it's a plain element with `data-part` for styling hooks. ### Data Attributes Following the established convention (Radix, Shoelace): - `data-state` — component state: `open`, `closed`, `active`, `inactive`, `checked`, `unchecked`, `on`, `off` - `data-part` — structural identification for CSS targeting - `data-disabled` — disabled state flag - `data-orientation` — `horizontal` or `vertical` ### Event Pattern Components dispatch standard `CustomEvent`s with the `petty-` prefix: ```ts this.dispatchEvent(new CustomEvent("petty-close", { bubbles: true, detail: { value: "confirm" }, })); ``` Consumers listen with standard DOM: ```js dialog.addEventListener("petty-close", (e) => { if (e.detail.value === "confirm") deleteItem(); }); ``` ### Property/Attribute Reflection Components expose both property and attribute APIs: ```ts // Attribute API (HTML) // Property API (JS) document.querySelector("petty-select").value = "apple"; ``` Properties are the source of truth. Attributes reflect via `attributeChangedCallback` → property setter. This avoids the string-only limitation of attributes for complex values. ## Component List (44 components, same as v1) ### Layout & Display (6) - `petty-avatar` — image with fallback - `petty-badge` — status indicator (simple element, no sub-parts) - `petty-card` — container with `petty-card-header`, `petty-card-content`, `petty-card-footer` - `petty-image` — image with fallback - `petty-separator` — `
` with role and orientation - `petty-skeleton` — loading placeholder ### Inputs & Forms (14) - `petty-button` — `