# @ponchia/ui > CSS-first, framework-agnostic UI framework. Zero runtime dependencies. > One `@layer bronto` cascade, a typed class vocabulary, design tokens as > data, and optional SSR-safe vanilla behaviors. Token-driven restraint: > neutral by default, one core accent for UI emphasis, dot-matrix display type, > plus separate governed tiers for status, display colorways, and data-viz. > > This file orients an LLM/agent. The authoritative, always-correct API > is the TypeScript declarations shipped in this package (paths below): > they are generated from the runtime sources and CI-drift-checked, so > they never lie. Read them before guessing class or token names. For the > manifest-level inventory, use `docs/package-contract.md`: it is generated > from package.json and lists every export, shipped path, and provenance gate. ## Use the framework CSS (pick one; do not import both): ```css @import '@ponchia/ui'; /* dist/bronto.css — flattened, minified, one file (recommended) */ @import '@ponchia/ui/css'; /* css/core.css — @import fan-out, needs a bundler */ ``` `import '@ponchia/ui'` is supported only as a CSS side-effect import in a CSS-aware bundler. Do not import the package root from Node/runtime JS; use the explicit JS subpaths below. JS subpaths are ESM-only. In CommonJS, use dynamic `import()`. Everything lands in `@layer bronto`. Consumer styles in no layer (or a later layer) win ordinary framework styles regardless of selector specificity — this is the intended override mechanism. Do not fight normal component styling with `!important`; the framework reserves `!important` for print and reduced-motion safeguards. Loading a stylesheet — read before copying a `` below. The opt-in leaves are written as `@ponchia/ui/css/.css` for brevity. That package-specifier form resolves ONLY inside a CSS-aware bundler (Vite/webpack); in a standalone `.html` file it does not resolve. For plain HTML (the usual case when an LLM emits a report) use a real URL — and note the path changes from source `css/` to built `dist/css/`: ```html ``` The flattened default bundle is `dist/bronto.css` (bundler shorthand `@ponchia/ui`). A complete, copy-pasteable CDN report is in `docs/reporting.md`. Typed class vocabulary (returns plain strings — works in any framework): ```js import { cls, ui, cx } from '@ponchia/ui/classes'; // ui.button({ variant: 'ghost' }) -> "ui-button ui-button--ghost" // cx(ui.dot({ tone: 'success' }), 'my-extra') -> joined, falsy-skipped // cls is the frozen flat registry of every class the framework defines ``` Tokens as data (theming + reading the palette): ```js import { cssVars, tokens, themeColor } from '@ponchia/ui/tokens'; ``` Optional vanilla behaviors (theme toggle, etc. — SSR-safe, tree-shakeable): ```js import { applyStoredTheme } from '@ponchia/ui/behaviors'; ``` Notable behaviors beyond the obvious: `initMenu()` adds close affordances to a native `
` dropdown (outside-click/Escape/select-close); `initModal()` wires the controlled `.ui-modal.is-open` path (inert focus-trap + role/aria-modal); `initDisabledGuard()` makes every `aria-disabled` control keyboard-inert (CSS alone is only pointer-inert). For the value-bearing fills, `attrs.meter(value)` / `attrs.progress(value)` from `@ponchia/ui/classes` return `role`+`aria-valuenow/min/max`+the `--value` style to spread onto the host. `initSources()` wires `[data-bronto-sources]` citation/source islands: `.ui-citation[href^="#"]` and `[data-bronto-source-ref]` focus/highlight the matching `.ui-source-card`, seed lightweight preview metadata, and emit `bronto:source:focus`; the host still owns numbering, fetching, trust decisions, and any rich preview popover. Optional lifecycle adapters wrap those behaviors without owning markup or state. React/Solid/Qwik use hook bindings (peer deps `react` / `solid-js` / `@builder.io/qwik`, optional — core stays zero-dep); Svelte uses dependency-free actions, and Vue uses dependency-free directives/plugin helpers: ```js import { useDialog, useToast } from '@ponchia/ui/react'; // or '@ponchia/ui/solid' or '@ponchia/ui/qwik' // React: scope with { root: reactRef } or a resolver, not { root: ref.current } during render. // Qwik: hooks run in useVisibleTask$; scope with a Qwik signal — useDialog({ root: useSignal() }). // Svelte: import { dialog, toast } from '@ponchia/ui/svelte'. // Vue: import { vDialog, useToast } from '@ponchia/ui/vue', or app.use(brontoVue). ``` Optional display glyphs — dot-matrix bitmaps on the `.ui-dotmatrix` primitive (decorative by default; `renderGlyph` is SSR-safe, `initDotGlyph` is the DOM form). `findGlyphs('delete')` resolves an intent word to real names; `renderReadout` composes digits into a big Nothing-style numeric. Details: `docs/glyphs.md`. ```js import { renderGlyph, findGlyphs, renderReadout, GLYPH_NAMES } from '@ponchia/ui/glyphs'; el.innerHTML = renderGlyph('check', { label: 'Done' }); el.innerHTML = renderReadout('73%', { label: '73 percent of quota' }); ``` The dot family also has data-bound reporting surfaces — `.ui-waffle` (unit chart), `.ui-activity` (contribution grid), `.ui-level` (LED meter), `.ui-dotgauge` (radial gauge), `.ui-halftone`, `.ui-readout` — same host-sets- the-data / leaf-paints boundary as `ui-spark`/`ui-meter`. Details: `docs/dots.md`. Layout primitives (core bundle, no media queries — intrinsic Every-Layout composition that responds to content + available space): `ui-stack` (vertical rhythm), `ui-cluster` (wrapping inline row), `ui-grid` (auto-fit columns), `ui-sidebar` (sidebar + main that stacks when cramped), `ui-switcher` (row → column together), `ui-center` (measure-bounded column), `ui-ratio` (aspect-ratio box — one child only), and the `ui-app-shell`/`ui-app-rail`/`ui-app-nav` admin shell. `ui-cq` makes descendant layouts respond to THAT box (container queries), not the viewport — wrap a `ui-grid`/`ui-statgrid` in it for an island-safe collapse in a slim pane. Each is tuned with an inline custom property rather than a modifier — set `style="--grid-min: 12rem"`, `--sidebar-width`, `--sidebar-gap`, `--switcher-min`, `--switcher-gap`, `--center-max` (the INNER measure; gutters add to the total), `--ratio`, `--stack-gap`, `--cluster-gap`, `--app-rail`, etc. The full list with defaults is in `classes.json` `customProperties`. App-shell/sitenav current page is `aria-current="page"`. Motion utilities (core bundle, all reduced-motion-safe — they collapse to the static end state, not just a zeroed duration): `ui-animate-in`, `ui-animate-fade`, `ui-animate-dot`, `ui-animate-matrix` (enter animations), `ui-stagger` (+ `--i` per child, or `ui-stagger--auto` to derive the delay from `:nth-child`), `ui-reveal` (JS/IntersectionObserver — adds `is-visible`), `ui-scroll-reveal` and `ui-scroll-progress` (zero-JS, scroll-driven; the recommended reveal for static reports), and `ui-vt` (View Transitions — needs the required `--ui-vt-name`, which must be **unique per document**: reusing one name across sibling items silently drops the transition). Prefer `ui-scroll-reveal` over `ui-reveal` when no JS will run. Details: `docs/usage.md`. Optional display colorways — opt-in, never in the default bundle. Root-level, like `data-theme` (the derived accent family only recomputes at `:root`): ```html ``` Each re-points the one `--accent` (OKLCH, per-theme, contrast-gated); status colours and the neutral canvas are untouched. Details in `docs/theming.md`. Optional data-viz palette for dashboards — opt-in, charts-only (never UI chrome), never in the default bundle: ```html ``` ```js import charts from '@ponchia/ui/charts.json' with { type: 'json' }; // charts.dark.categorical → ['#ff3b41', …] (series 0 = the resolved accent) ``` On-brand Mermaid diagrams use the same tokens-as-data path — Mermaid is the consumer's renderer, bronto just hands it a resolved `base` theme (Mermaid can't read `var(--x)`): ```js import { brontoMermaidTheme } from '@ponchia/ui/mermaid'; mermaid.initialize(brontoMermaidTheme('dark')); // { theme: 'base', themeVariables } ``` Annotate a rendered diagram with the SVG annotation layer as a build-time recipe. Details: `docs/mermaid.md`. D2 (d2lang.com) is themed the same way — it compiles to a frozen SVG, so the slots take resolved hex; the base ramp is monochrome and the accent is opt-in per node: ```js import { brontoD2Vars } from '@ponchia/ui/d2'; const source = brontoD2Vars() + 'a -> b'; // prepend the vars block, then render ``` Details: `docs/d2.md`. Charting is the same path: bronto does NOT ship a chart component (a chart needs scales + data binding, which the analytical layer refuses). For charts, recommend Vega-Lite — a declarative JSON spec an external system can emit — themed by the on-brand `config` (resolved hex; Vega bakes colours into SVG/canvas, can't read `var()`). `range.category` is the `--chart-*` palette, series 1 = accent: ```js import { brontoVegaConfig, brontoVegaAccent, brontoVegaNeutral } from '@ponchia/ui/vega'; vegaEmbed('#chart', spec, { config: brontoVegaConfig('dark'), renderer: 'svg', actions: false }); ``` Pass `renderer: 'svg'` (canvas is the wrong default for print/inspection). `brontoVegaAccent(theme)`/`brontoVegaNeutral(theme)` give the exact hexes to spend the accent on one mark. From a CDN, pin the `/build/*.min.js` UMD bundles (a bare `vega@6` tag has no `window.vega`; keep Vega-Lite 6 / Vega 6 / vega-embed 7 aligned) and INLINE the config from `vega.json` — a `file://` report can't `import`/`fetch` it (CORS). A label on an accent fill in a foreign renderer (a themed Vega/D2/Mermaid node or bar) reads the `--on-accent` token, never `--accent-text` (that is accent-coloured text for a neutral bg). `--on-accent` is a READ-ONLY export for those renderers — no in-DOM `.ui-*` rule consumes it, so setting it does not re-ink a component; to colour text on an accent-filled DOM control, set `--button-text`. Details: `docs/vega.md`. (Observable Plot works too — it inherits the page CSS, so it needs even less theming; Vega-Lite is the recommended LLM-emittable path.) `--chart-1..8` (categorical; series 1 = accent; colourblind-safe, gated under simulated protan/deutan/tritan), `--chart-seq-*` (sequential), `--chart-div-*` (diverging), and `--chart-pattern-1..8` (dot-matrix fills — pair colour N with pattern N; colour is never the sole signal). Details in `docs/theming.md`. Building analytical / generated-report UI? Import the roll-up `@ponchia/ui/css/analytical.css` (bundles figure + annotations + legend + marks + connectors + spotlight + crosshair + selection + highlights) instead of the nine leaves; add `css/dataviz.css` for chart colours and `css/report.css` for the document grammar. Interval and clamp are report/evidence leaves but are NOT in the roll-up; link them individually. The individual layers (each opt-in, none in the default bundle): Optional figure stage for charts, diagrams, screenshots, and annotated media — opt-in, never in the default bundle. It composes with `ui-report__figure` but does not replace it: ```html ``` Use `ui-figure` on the `
`, `ui-figure__caption` on the caption, `ui-figure__body` and optional `ui-figure__body--key-right` for stage + key layout, `ui-figure__stage` for the centered media box, `ui-figure__media` for the chart/SVG/img/canvas, `ui-figure__overlay` for pointer-transparent absolute overlays, `ui-figure__key` for legends, and `ui-figure__data` for fallback tables. Author-set knobs: `--figure-max-inline`, `--figure-min-block`, `--figure-key-width`. Bronto owns layout only; the host owns data, scales, chart rendering, overlay content, and accessibility text. Details: `docs/figure.md`. Optional SVG annotations for charts/reports — opt-in, never in the default bundle. This is Bronto's subject / connector / note grammar inspired by d3-annotation, not an authoring engine. For placement, collision handling, renderers, editing, and chart/diagram adapters, use the sibling `@ponchia/annotations` package; `@ponchia/ui/annotations` stays a dependency-free Bronto static-helper compatibility surface: ```html ``` ```js import { annotationParts, connectorLine, circleSubjectPath } from '@ponchia/ui/annotations'; ``` Use `ui-annotation` groups inside SVG with `ui-annotation__subject`, `ui-annotation__connector`, `ui-annotation__note`, `ui-annotation__title`, and `ui-annotation__label`. `ui.annotation({ variant, tone, motion })` builds the base class string. Variants cover labels, callouts, elbow/curve connectors, circle/rect/threshold subjects, badges, brackets, bands, slopes, comparisons, clusters, axis marks, timeline pins, and evidence markers. Motion is opt-in (`draw`, `reveal`, `pulse`, `focus`) and respects reduced-motion preferences. Use `notePlacement()` for one-note bounded placement, but do not treat it as a whole-chart collision solver. `declutterLabels(items,{gap,min,max})` is a deterministic 1-D declutter; `directLabels(items,{axis,cross,gap,min,max,shape})` adds the leader line (it declutters labels along an axis and returns each placed point plus a leader path `d` for `ui-annotation__connector`) — neither owns scales, DOM, or 2-D placement. Status tones are only for status-bearing callouts. Keep charts sparse; dense mobile figures need horizontal scrolling, a simplified SVG, or complete caption/fallback text. Details: `docs/annotations.md`. Optional legends / data keys for charts and figures — opt-in, never in the default bundle. Reads the `--chart-*` tokens, so a key never drifts from its series: ```html ``` Use a `ui-legend` container with `ui-legend__item` rows, each holding a `ui-legend__swatch` (decorative, `aria-hidden`) and a `ui-legend__label` (the required non-colour channel — WCAG 1.4.1; colour alone never carries meaning). Set the swatch colour inline (`style="--chart-color: var(--chart-3)"`, plus `--chart-pattern` to match a patterned mark) or with a `ui-legend__swatch--1..8` index helper. Variants: `ui-legend--vertical`/`--compact`/`--with-values`, `ui-legend--gradient` (+`--diverging`, with `ui-legend__track`/`__ticks`/`__tick`), and `ui-legend--threshold`. `ui.legend()`/`ui.legendItem()`/`ui.legendSwatch()` build the class strings. Interactive (series-toggling) legends are opt-in: author entries as `