Skip to content

Documentation

smartwebUI in one page

Semantic HTML is the public API, CSS custom properties are the customization API, and one optional script adds interactive behavior. Everything on this page applies to version 1.2.0.

Getting started

Copy smartwebui.css into your project and link it. Add smartwebui.js when you need interactive behaviors such as tabs, the custom select, dialogs, menus, and toasts. There is no installation, build step, compiler, or dependency.

<link rel="stylesheet" href="/assets/smartwebui.css">
<script defer src="/assets/smartwebui.js"></script>

Components are ordinary semantic HTML with class names. The library works with any server stack. PHP can emit the same markup as any other host — there is no Composer package, PHP extension, or compiler. An optional single-file renderer, smartwebui-surface.php, is included for agent surfaces (PHP 8.0+, no dependencies). Styling holds up before JavaScript loads.

Design choices & tradeoffs

smartwebUI is not a React replacement. It is a trusted UI layer for applications where HTML stays authoritative and agents compose within a catalog — not arbitrary markup.

HTML is the runtime; JSON is the agent wire format

Humans, server templates, and agents can all write semantic HTML with smart-* classes. Alternatively, agents can emit validated JSON surfaces that render to the same HTML through the DOM API — the wire format rejects HTML strings inside JSON, not HTML as a medium. There is no third API — the catalog is a subset of what you can hand-author.

When smartwebUI fits

  • PHP or server-rendered apps that want polished components without a JS framework
  • Admin tools, internal dashboards, and agent-built operator interfaces
  • Progressive enhancement: server first paint → adopt() → live patches
  • Teams that accept a 36-month browser window in exchange for modern APIs and less legacy weight

When to reach for something else

  • Rich client-side SPAs with complex shared state — React, Vue, or Svelte component ecosystems will feel more natural
  • Mandated support for legacy browsers or locked-down corporate WebViews — Bootstrap or heavily polyfilled stacks may be required
  • Teams that need maximum Stack Overflow density and hiring familiarity above agent contracts

Dependency-free means you own the bugs

There is no npm dependency tree to absorb fixes — every picker, upload flow, and patch path is maintained in this website. Release gates and contrast requirements are documented in QUALITY.md; accessibility scope in ACCESSIBILITY.md.

Comparison at a glance

smartwebUIBootstrapReact + UI kit
Public APIHTML + design tokensCSS classes + JS pluginsComponents + JSX
Agent contractCatalog + surfacesNoneAd hoc per app
Build stepNoneNoneYes (bundler)
Browser windowLast 36 monthsBroad legacy supportDepends on bundler targets
Best forAgent UI, PHP-first, progressive enhancementGeneral marketing sitesRich client applications

FAQ

Is this a framework?
No compile step. Optional JavaScript hydrates hand-written HTML. Agent JSON is an optional layer on top — the entire showcase works without it.
Can agents write HTML?
Yes. Agents can output semantic smart-* markup the same way humans do — that is the library’s public API. They can also compose through validated surface JSON, which the agent runtime renders to the same HTML via the DOM API. What surfaces reject is raw HTML strings inside JSON declarations; that boundary keeps generative UI fail-closed. Trusted application code may still use update(..., { html: true }) for its own markup.
Does it work without agents?
Yes. Link smartwebui.css and optionally smartwebui.js. Every component in the showcase is plain markup.
Why only modern browsers?
The library uses the Popover API, native <dialog>, cascade layers, color-mix(), light-dark(), and :has() without polyfills. See browser policy.
Can I use it inside React or Vue?
Possible — mount HTML islands and call hydrate() after render. That is not the primary integration story; expect to manage lifecycle yourself.
What changed in 1.0.2 for validation APIs?
SmartWebUI.validate(form, rules) is the form helper (core). Surface declarations use SmartWebUI.validateSurface(surface) (agent). The 1.0.1 validate(surface) alias was removed — see changelog.
Where is the security model documented?
SECURITY.md — agent threat model, CSP guidance, and e-commerce boundaries. Summarized under agent security.

Canonical inventory

HTML is the public API. The agent catalog is a subset of that HTML: every catalog type has a documented snippet, and every showcase family agents may emit maps to one catalog type. There is no third API.

Two lists both happen to total 38. They are not the same 38 items.

  • HTML families (38). The component reference numbers 38 families. The showcase shows every family except Payment: foundations 01–15, application patterns 16–36, and Carousel 38 (Payment is family 37, shown in the reference and agent demo). Combobox, File, Date & time, Search, Tags, and Confirm keep their own snippets even though they map onto Field or Dialog. Split actions stay in Button; advanced Field controls stay in Fields; circular progress stays in Progress; groups stay in Avatar; structured feeds and attachments stay in List; consent stays in Alert; Chart is one family with five marks; the lightbox viewer stays in Carousel.
  • Catalog types (34 + 4). The agent catalog has 34 component types plus 4 layout primitives (Stack, Cluster, Grid, Text). That set includes Icon, which is a documented foundation rather than a numbered HTML family, plus FormErrors, AppHeader, Footer, CommandPalette, Payment, Chart, and Carousel. It does not give Combobox or Confirm their own type names.
#HTML familyCatalog type
Icons (foundation, not a numbered family)Icon
01Buttons (includes split actions)Button
02FieldsField control: input, textarea, select, range, otp, quantity, multiselect, numericrange, rating, color (mask is a property, not a control)
03–05Choices, Switches, SegmentedChoice, Switch, SegmentedControl
06–15Cards through ProgressMatching catalog names
16–20Table, Pagination, Alerts, Form errors, BreadcrumbsTable, Pagination, Alert, FormErrors, Breadcrumbs
21ComboboxField control: "combobox"
22–24Empty, Avatar, ListEmpty, Avatar, List
25–26File, Date & timeField control: "file" / "date" / "daterange" / "time"
27Confirm dialogDialog variant: "confirm"
28–31Page header, App shell, Chips, SearchPageHeader, AppShell, Chip, Field control: "search"
32–33Tags, StepperField control: "tags", Stepper
34Header / FooterAppHeader, Footer
35Command paletteCommandPalette
36ChartChart
37Payment (reference and agent demo)Payment
38Carousel & lightbox (strip, gallery grid, zoomable viewer)Carousel variant: "carousel" / "gallery", lightbox

Icons

smartwebui-icons.svg is a curated, dependency-free SVG sprite built from Lucide plus 17 popular social-network brands. Its 516 stable names are indexed in smartwebui-icons.json. No npm, icon font, custom element, or build step is required for consumers.

<svg class="smart-icon" style="--smart-icon-size: 37px" aria-hidden="true">
  <use href="/assets/smartwebui-icons.svg#smart-icon-sparkles"></use>
</svg>

--smart-icon-size accepts any valid CSS size: 17px, 2.4em, 10vw, or clamp(1rem, 4vw, 5rem). Set --smart-icon-stroke to tune line weight. The optional helper creates safe DOM nodes:

const icon = SmartWebUI.icon("arrow-right", {
  size: "clamp(1rem, 4vw, 5rem)",
  stroke: 1.75,
  label: "Continue"
});

Omit label for decorative icons next to visible text; provide it for a meaningful standalone icon. Directional icons flip automatically in right-to-left layouts. External SVG sprites should be served from the same origin for the most reliable browser behavior. A different trusted path can be configured with SmartWebUI.setIconSource().

Use sprite icons—not font glyphs—for interface actions and status marks such as close, search, previous/next, add/remove, overflow, warnings, and ratings. Keep punctuation, mathematical notation, keyboard characters, and prose symbols as text. Icon-only controls still require a textual aria-label; their SVG remains aria-hidden="true".

Customizing

Override design tokens after the stylesheet. Every component reads the same variables, so one override updates the whole interface immediately.

:root {
  --smart-accent: #ff3b30;
  --smart-radius-control: 999px;
  --smart-font-sans: "Avenir Next", sans-serif;
  --smart-motion-duration: 180ms;
  --smart-control-height: 3rem;
}

Theme colors are written as light-dark() pairs resolved through color-scheme. Light and dark themes follow the operating system preference automatically; <html data-theme="dark"> forces a theme. Density (data-density="compact") and the Liquid material (data-material="liquid") are opt-in the same way.

Library CSS lives in cascade layers. Unlayered host CSS wins. See the token reference for the public variable list and the layer order.

Token reference

Public custom properties on :root. Override these after loading smartwebui.css. Names that start with --_ are private to a component and are not part of the API.

Host CSS should stay unlayered so it wins over every @layer smart.* rule, including Liquid (smart.materials). If you use layers, declare them after the library list: smart.reset, smart.tokens, smart.base, smart.layout, smart.components, smart.materials, smart.utilities.

TokenDefaultUsed by
Typography
--smart-font-sansInter, system UI stackAll text
--smart-font-monoSFMono, ConsolasCode, metrics
--smart-font-size-070.75rem … clamp displayLabels through hero type
--smart-font-weight-regularbold400 / 560 / 650 / 740Body, labels, titles
--smart-letter-spacing-tight / --smart-letter-spacing-label-0.035em / 0.015emHeadings, uppercase labels
Spacing and shape
--smart-space-1120.25rem … 7remLayout, stacks, sections
--smart-container-sm / --smart-container-md / --smart-container-lg40rem / 56rem / 76rem.smart-container--* width caps
--smart-radius-control--smart-radius-smButtons, fields, segmented
--smart-radius-surface--smart-radius-lgCards, dialogs, customizer
Color
--smart-accentlight-dark indigoPrimary actions, focus, links
--smart-on-accentwhite / near-blackText on accent fills
--smart-danger / --smart-on-dangerred pairDestructive actions
--smart-success / --smart-warning / --smart-infostatus pairsBadges, alerts, toasts
--smart-canvas / --smart-surfacepage and card fillsSurfaces
--smart-text / --smart-text-secondary / --smart-text-tertiaryprimary / mutedCopy hierarchy
--smart-border / --smart-divider12% / 9% inkControls and rules
--smart-focus-coloraccent mixFocus rings
Controls and motion
--smart-control-height3remButtons, inputs, selects
--smart-control-padding-x1remHorizontal padding inside controls
--smart-form-gap--smart-space-5Row spacing in forms and form stacks
--smart-field-gap--smart-space-3Label-to-control spacing inside .smart-field
--smart-motion-duration220msStandard transitions, view transitions
Layering
--smart-z-raised / --smart-z-sticky4 / 10Shell sidebar, sticky regions
--smart-z-tooltip / --smart-z-toast / --smart-z-banner20 / 40 / 50Tooltips, toasts, compat banner
--smart-overlay34% / 62% inkDialog backdrop, shell scrim
Liquid
--smart-liquid-opacity56%Surface transparency
--smart-liquid-accent-opacity84%Primary / danger fills
--smart-liquid-blur / --smart-liquid-saturate38px / 190%Backdrop material
--smart-liquid-glow-size8remPointer highlight

When overriding --smart-accent or --smart-danger, also set --smart-on-accent and --smart-on-danger so contrast stays AA.

Utilities

Single-purpose classes in @layer smart.utilities — the last layer, so they win over components. Spacing steps reference the --smart-space-* scale, which means compact density and token overrides apply automatically.

Spacing

  • .smart-p-0.smart-p-8 — padding; .smart-px-* / .smart-py-* for inline / block axes; .smart-pt-* / .smart-pb-* / .smart-ps-* / .smart-pe-* for logical start/end sides. Steps 9–12 remain token-only for page-level spacing.
  • .smart-m-0.smart-m-8 — margin; .smart-mx-* / .smart-my-* for axes; .smart-mt-* / .smart-mb-* / .smart-ms-* / .smart-me-* for logical sides; .smart-m-auto / .smart-mx-auto / .smart-my-auto for centering
  • .smart-gap-0.smart-gap-8 — gap for .smart-stack, .smart-cluster, .smart-grid, or any flex/grid host

Layout

  • .smart-container with .smart-container--sm / --md / --lg — width cap + auto margins (40rem / 56rem / 76rem)
  • .smart-grid--2 / --3 / --4 — fixed column grids that collapse under 50rem; .smart-span-2, .smart-span-full for cell spans
  • .smart-span-desktop-2 / .smart-span-desktop-full apply spans only on desktop; .smart-sticky and .smart-sticky-top use the library sticky z-index.
  • .smart-z-raised, .smart-z-sticky, .smart-z-tooltip, .smart-z-toast, and .smart-z-banner expose the public z-index token scale without hard-coded numbers.
  • .smart-divider and .smart-divider--vertical create token-driven separators with consistent spacing.
  • .smart-ratio-1x1 / -4x3 / -16x9 / -21x9 — media boxes; children are stretched to cover. Override the crop with .smart-object-contain / -cover / -fill / -scale-down on the child (logos, screenshots).
  • Native <figure> / <figcaption> are styled in the base layer — caption tone, size, and spacing come free, no classes.
  • .smart-w-auto / -25 / -33 / -50 / -66 / -75 / -full and .smart-h-full — Bootstrap-style dimension utilities; thirds are exact calc() fractions.
  • .smart-control-sm / .smart-control-lg — size any form control by overriding --smart-control-height locally: inputs, selects, date pickers, segmented controls, tabs and buttons all consume the same token, so one class aligns a whole row. Density still applies on top.
  • Form spacing tokens — override --smart-form-gap (row spacing in form.smart-stack, card bodies, and dialogs) and --smart-field-gap (label-to-control spacing) without touching markup.
  • .smart-reveal — fade-slide in on first scroll into view; stagger with --smart-reveal-delay. Content stays visible if JS never runs.
  • .smart-animate plus an effect class — opt-in .smart-enter-fade, --up, --down, --start, --end, or .smart-enter-scale entrances; matching .smart-leave-* exits; and one-shot .smart-attention-pulse, --shake, or --flash cues. Tune a single element with --smart-animation-duration, --smart-animation-delay, --smart-animation-ease, and --smart-animation-iterations. Start/end mirror in RTL and all effects stop under reduced motion.

Typography

  • Semantic scale: h1 through h6 use the token scale automatically; h2 is the display/section size, while h5 and h6 cover supporting and eyebrow headings.
  • Sizes: .smart-text-xs, .smart-text-sm, .smart-text-base, .smart-text-lg, .smart-text-xl
  • Tones: .smart-text-secondary, .smart-text-tertiary, .smart-text-success, .smart-text-warning, .smart-text-danger (and .smart-muted)
  • Alignment: .smart-text-start, .smart-text-center, .smart-text-end — logical, RTL-safe
  • .smart-truncate (one line), .smart-line-clamp-2/3/4 (multi-line), .smart-balance, .smart-mono, and font-weight helpers.
  • .smart-kbd styles shortcut hints such as ⌘ K.

Visibility

  • Prefer the native hidden attribute — the reset guards it against display overrides. SmartWebUI.hide(el) / SmartWebUI.show(el) toggle it, and agent nodes accept a universal hidden: true property.
  • .smart-hidden (display: none) when a class toggle is easier; .smart-invisible hides without removing layout
  • .smart-hidden-mobile hides under 50rem; .smart-visible-mobile shows only under 50rem
  • .smart-hidden-tablet / .smart-visible-tablet target 50–74.99rem; .smart-hidden-desktop / .smart-visible-desktop target 75rem and wider.
  • .smart-visually-hidden keeps content available to assistive technology only
  • .smart-skip-link is visually hidden until focused, then appears at the start of the page. Point href at the main landmark (#main).
<a class="smart-skip-link" href="#main">Skip to content</a>
<main id="main">…</main>

Surface

  • .smart-elevated-0 / -1 / -2 — none, small, medium shadow
  • .smart-rounded-sm / -md / -lg / -full, .smart-bordered
  • .smart-w-full, .smart-min-w-0 (flex/grid overflow fix), .smart-overflow-x-auto

Scroll panes

  • .smart-scroll-y / .smart-scroll-x — self-scrolling regions with thin scrollbars and contained overscroll, for message lists and long feeds.
  • .smart-pane-fill, .smart-pane-tall (min(60vh, 32rem)), .smart-pane-screen (100svh minus --smart-pane-offset) — pane heights for messenger-style layouts where a header or composer stays fixed.

Application layout helpers

Optional classes for common product shells — page main columns, auth forms, reading-width content, card grids, and log output. They compose with .smart-stack, .smart-cluster, and .smart-grid.

ClassPurpose
smart-page-mainDefault public page main spacing
smart-shell-mainApp shell main column spacing
smart-cluster--centerHorizontally center a flex cluster
smart-stack--tightReduced vertical stack gap
smart-auth-layoutNarrow centered auth form column (max 44rem)
smart-auth-logoLogin/register logo sizing
smart-reading-cardProse-width card container
smart-tile-cardIcon + title link tile (with smart-card--interactive)
smart-card__feature-iconLarge icon badge for tiles and pickers
smart-grid--cardsEqual-height card grid
smart-grid--tilesTile grid min width (combine with --cards)
smart-grid--pickersChoice/picker card grid
smart-card--pickerCentered picker tile styling
smart-log-panelScrollable monospace log output

Print

  • .smart-no-print hides any element on paper; popovers, toasts, dialogs, and app-shell controls are hidden automatically in @media print.
  • .smart-print-document flattens a card into a print-ready document — shadows removed, black on white — for invoices, quotes, and receipts.

JavaScript API

The optional script exposes a frozen SmartWebUI global. Every method works on ordinary DOM elements, so it composes with any server-rendered or client-rendered page.

One global, two validation roles. SmartWebUI.validate(form, rules) validates form fields (core). SmartWebUI.validateSurface(surface) validates agent JSON (requires smartwebui-agent.js). Do not pass surface declarations to validate(). With the agent script loaded, the same SmartWebUI object extends the core API — one script tag pair for simplicity.

SmartWebUI.update(target, content, options)

Replaces the content of an element with a View Transition when the browser supports one, or an immediate accessible update otherwise. Strings are inserted as plain text by default. Pass { html: true } only for trusted application markup, and { replace: true, html: true } to replace the element itself. Pass a Node to insert structured DOM without parsing HTML.

Agent-produced content must go through render() and patch() — not HTML strings in update().

SmartWebUI.toast({ title, message, duration, tone, placement })

Shows a dismissible notification in the polite live region. tone is one of success, info, warning, or danger; duration: 0 keeps it until dismissed. placement accepts top-start, top-center, top-end, bottom-start, bottom-center, or bottom-end.

SmartWebUI.setTheme(theme) · setDensity(density) · setMaterial(material)

Apply light/dark/auto themes, comfortable/compact density, or the solid/liquid material. Choices persist to localStorage when available and dispatch a matching smart: event.

SmartWebUI.setBusy(button, busy)

Sets aria-busy on a button and disables it while a save or other in-flight action runs. Call setBusy(button, false) to restore the previous disabled state.

SmartWebUI.validate(form, rules, options)

Runs declarative rules against a form and wires the results into the Field pattern: aria-invalid on failing controls, linked .smart-error messages, .smart-field--invalid on wrappers, and an optional FormErrors summary ({ summary: "#form-errors" }). The summary is built with DOM text nodes — labels and messages are never interpolated into HTML. Rules per field name: required, email, minLength, maxLength, pattern, match, min, max, custom(value, form), plus label and message overrides. Returns and dispatches { valid, issues } via smart:validate.

SmartWebUI.setLocale(strings)

Overrides component chrome strings — placeholders, aria labels, picker buttons, upload and validation messages — for any subset of keys (SmartWebUI.t(key) reads one back). Dates and currency always format through Intl; RTL works via dir="rtl" as always. Dispatches smart:locale.

SmartWebUI.upload(input, url, options)

Uploads the file in a dropzone-enabled file input with a live progress bar, returning a promise for the server response. Options: method, fieldName, fields (extra form values), headers. Progress and completion arrive as smart:upload-progress, smart:upload-done, and smart:upload-error events on the input.

SmartWebUI.countdown(target, seconds, options)

Renders an m:ss countdown into any element, ticking once per second, and returns a controller with stop(), start(), and reset(seconds). Options: format, warnBelow (adds .smart-countdown--warn), onTick, onExpire. Declaratively, data-smart-countdown="180" (with optional data-countdown-warn) starts on hydration — the session-timeout pattern. Events: smart:countdown, smart:countdown-expire.

copy() · confirm() · formatters

copy(text) writes plain text to the clipboard and returns it. confirm(options) opens an accessible native dialog and resolves to a boolean. formatDate(), formatNumber(), formatCurrency(), and formatRelativeTime() provide dependency-free Intl-based display formatting with optional locale settings.

debounce() · throttle() · shortcuts

debounce(callback, delay) and throttle(callback, interval) handle search and scroll work. SmartWebUI.shortcuts.register("mod+k", handler, options) registers a normalized keyboard shortcut and returns an unregister function; mod maps to Command on macOS and Control elsewhere.

observeBreakpoint() · observeLoadMore() · observeConnectivity()

observeBreakpoint(query, callback) exposes a cleanup-friendly matchMedia observer. observeLoadMore(sentinel, callback, options) wraps IntersectionObserver for infinite lists. observeConnectivity(callback) reports navigator.onLine immediately and on browser online/offline events. Each returns a cleanup function.

focusFirst() · trapFocus()

focusFirst(target, options) moves focus to the first usable descendant. trapFocus(target, options) exposes the library's focus-loop behavior for custom host surfaces and returns a cleanup function that can restore prior focus. Native dialogs remain preferred when modal semantics apply.

trackDirty() · queryState

trackDirty(form, options) compares live form data with a resettable baseline, toggles data-smart-dirty, dispatches smart:dirty, and optionally guards beforeunload. queryState.read/write/apply/bind synchronizes named filter and table controls with URLSearchParams through replace-state by default.

virtualize() · scrollToTop()

virtualize(target, items, renderItem, options) windows fixed-height rows with configurable itemHeight and overscan, returning update, refresh, scroll, and destroy controls. scrollToTop() honors reduced motion; data-smart-scroll-top turns a .smart-scroll-top button into an automatically revealed affordance.

SmartWebUI.hydrate(root)

Initializes popovers, custom selects, comboboxes, multi-selects, date and time pickers, dual ranges, ratings, masks, color outputs, command palettes, app headers, attachment removal, table selection and expandable rows, app shells, steppers, search, file/dropzone, quantity, password strength, OTP, countdowns, tabs, segmented controls, ranges, tooltips, and charts inside any element. Call it after inserting smartwebUI markup dynamically so new components behave like static ones.

TypeScript types for the core and agent globals live in smartwebui.d.ts.

Markup behaviors

Common interactions need no JavaScript of your own — add a data attribute.

  • data-smart-open="#dialog-id" opens that <dialog> modally and returns focus on close.
  • data-smart-close closes the closest dialog from any button inside it.
  • data-smart-collapse="#region-id" toggles the hidden attribute on any region from a remote trigger, with aria-expanded / aria-controls managed for you.
  • data-smart-scrollspy on a table-of-contents nav highlights the link whose section is in view via aria-current="location" — this page's sidebar uses it.
  • data-smart-toast with optional data-toast-title, data-toast-message, and data-toast-placement fires a toast on click.
  • data-smart-breadcrumbs-collapse folds deep breadcrumb middles; data-smart-scroll-top provides an automatically revealed return-to-top button; data-smart-connectivity reflects browser online/offline state.
  • data-smart-theme, data-smart-density, and data-smart-material apply preferences on click.

Events

Components announce changes through bubbling custom events, so integration stays decoupled from markup.

  • smart:tab from tab activation, smart:select from the custom select, smart:combobox from filterable fields.
  • smart:date and smart:time from custom date and time pickers; range pickers include start and end.
  • smart:tags from tag fields, smart:multiselect from multi-select dropdowns, smart:range from dual ranges, and smart:rating from ratings.
  • smart:attachment-remove from removable file rows; smart:copy after a successful copy action.
  • smart:sort from table sort buttons, smart:table-select and smart:table-action from bulk selection, smart:step from steppers, smart:chip-remove from removable chips.
  • smart:confirm after a confirm dialog is accepted, smart:toast-action from toast action buttons, smart:accordion from disclosure toggles, and smart:breadcrumbs when a collapsed path expands.
  • smart:connectivity, smart:dirty, smart:query-state, and smart:virtualize expose host-level state changes.
  • smart:update after update() replaces a region.
  • smart:quantity from stepper buttons, smart:strength while typing scored passwords, smart:otp from OTP digit rows ({ value, complete, length }), smart:validate after validate().
  • smart:file when a pick or drop is accepted or rejected; smart:upload-progress, smart:upload-done, smart:upload-error around upload().
  • smart:board and smart:move from Kanban card drops ({ cardId, fromColumnId, toColumnId, fromIndex, toIndex }); smart:send from conversation composers ({ value, textarea }); RichText fields also emit smart:change with { value, control } (agent-bound controls use the surface smart:change shape instead).
  • smart:locale after setLocale() overrides chrome strings.
  • smart:theme, smart:density, smart:material when preferences change.
  • smart:action and smart:change from agent-rendered controls (below).
  • smart:surface and smart:patch after agent rendering and patching; smart:adopt after server-rendered markup is registered; smart:agent-ready when the agent runtime loads.
  • smart:compat with the missing feature list when data-smart-compat detects an unsupported browser; smart:stripe-ready and smart:payment-ready from the payment companion.

Agent surfaces

The optional agent runtime (smartwebui-agent.js, loaded after smartwebui.js) lets generative systems compose interfaces from a declarative JSON contract. Agents can also write hand-authored smart-* HTML directly — surfaces are the fail-closed path when JSON is the wire format.

const surface = {
  surface: "project-summary",
  root: {
    type: "Stack", id: "summary", gap: "medium",
    children: [
      { type: "Card", id: "revenue", label: "Monthly revenue", value: "$48.2k", change: "+12.4%" },
      { type: "Button", id: "report", label: "View report", variant: "primary", action: "open-report" }
    ]
  }
};

SmartWebUI.render("#agent-surface", surface);

The renderer builds elements through the DOM API and never accepts HTML strings, JavaScript, classes, or inline styles inside a surface declaration. Each node is validated against its catalog property map — unknown keys fail closed. href and src accept only http(s), mailto:, relative paths, and in-page hashes. Surface data is otherwise structurally validated: plain objects only, unique IDs, and depth, node-count, and text-length limits. Buttons emit smart:action events; the runtime never executes agent-provided code.

Stable IDs support live updates with four patch operations — data.patch, node.upsert, node.remove, and node.append — and focus is preserved across patches when possible. node.append (or SmartWebUI.append(target, { parent, node | nodes })) grows a container as items arrive, the streaming shape for SSE and WebSocket token flows.

SmartWebUI.patch("#agent-surface", {
  type: "data.patch",
  id: "revenue",
  changes: { value: "$51.7k", change: "+14.1%" }
});
  • SmartWebUI.validateSurface(surface) checks a declaration before rendering, returning { valid, errors } plus structured issues: [{ path, message }] that agents can self-correct from.
  • SmartWebUI.snapshot(target) reads a live surface back: { capturedAt, nodes: { [id]: { type, value?, checked?, selected?, expanded?, step?, open?, hidden? } } } — field values, date ranges, switch states, tab selection, accordion panels, stepper progress, table selections, dialog state, all keyed by stable id.
  • SmartWebUI.loadCatalog() fetches smartwebui.catalog.json, the machine-readable contract of components, properties, composition rules, and accessibility expectations.
  • SmartWebUI.history(target) records every patch applied to a surface with its precise inverse — undo(), redo(), clear() — so agents and users can roll back interface changes safely.
  • Server-side rendering: smartwebui-surface.php (a dependency-free PHP class) emits the same semantic HTML with stable IDs and an embedded declaration via SmartWebUISurface::container($surface). In the browser, SmartWebUI.adopt("#target") registers that markup without a rebuild — dispatching smart:adopt — after which patch(), snapshot(), and history() behave exactly as on client-rendered surfaces.
  • /llms.txt summarizes the whole library — conventions, utilities, catalog, events — in the format coding agents look for.
  • Dialogs and drawers accept open: true to open modally once the surface mounts; toasts are announced through the standard toast region.

Server-first workflow

The recommended path for PHP and other server stacks:

  1. Agent or application produces a validated surface JSON object.
  2. Server renders first paint with SmartWebUISurface::container($surface, ["id" => "report"]) — same HTML, stable data-smart-id attributes, embedded declaration.
  3. Browser loads smartwebui.js + smartwebui-agent.js, then SmartWebUI.adopt("#report") registers the DOM without a client rebuild.
  4. Live updates use patch(), append(), snapshot(), and history() on the adopted surface.
<?php echo SmartWebUISurface::container($surface, ["id" => "report"]); ?>
<script defer src="/assets/smartwebui.js"></script>
<script defer src="/assets/smartwebui-agent.js"></script>
<script>document.addEventListener("DOMContentLoaded", () => SmartWebUI.adopt("#report"));</script>

See the agent demo for a live server-rendered surface with adopt, streaming append, and undo/redo. A conformance test suite for ports in other languages is planned.

Streaming & concurrency

  • node.append / SmartWebUI.append() is for growing lists and feeds as tokens arrive — not replacing whole trees mid-edit.
  • Concurrent patches to the same node are last-write-wins unless the host serializes an operation queue.
  • Focus is preserved across data.patch when possible; streaming append does not move focus — announce critical updates with your own live region if needed.
  • Use history() to roll back mistaken agent patches during development and operator workflows.

Agent security

The agent runtime validates every surface before render and refuses HTML strings in JSON, scripts, classes, and unsafe URLs. Actions are events — your application executes them; the library never runs agent code. Agent-generated markup outside the surface protocol is the same HTML API humans use — the host decides whether to trust or sanitize it.

ThreatMitigation
XSS via agent HTMLSurfaces use typed nodes only — no HTML strings in JSON
Open redirecthref / src allowlist
Payment spoofingPayment amounts are display-only; charge server-side
Malicious patchValidate before render; host may reject operations
Misleading copyAgent text is not verified — treat like user-generated content

Full threat model, CSP guidance, and reporting: SECURITY.md in the repository root.

E-commerce scope

In scope: checkout UI slot (Payment node), token-aware Stripe Elements via smartwebui-stripe.js, Address and Express Checkout element types, print styles for invoices and receipts.

Out of scope: cart logic, tax calculation, inventory, webhooks, PCI scope — the host application owns commerce rules and secrets.

“E-commerce ready” means checkout UI hooks and agent-safe payment intent — not a Shopify alternative.

Component reference

Thirty-eight HTML families for product screens. HTML is the public API. The agent catalog is a different list of 34 component types plus 4 layout primitives (also 38 entries, including Icon, which is not a numbered family). Variations stay with their parent family instead of inflating the inventory. See the inventory, the foundation showcase, and application patterns.

01 Buttons

Initiate an immediate user action with clear hierarchy and tactile feedback.

Classes
.smart-button, modifiers --primary, --quiet, --link, --danger, --small, --large, --icon, --full; group with .smart-button-group; attach a menu with .smart-split-button
Data attributes
data-smart-toast, data-toast-title, data-toast-message on triggers; use aria-pressed for toggle groups; aria-busy="true" for a saving spinner
Events
Native click; optional toast via data-smart-toast; agent buttons emit smart:action
Agent catalog
Buttonlabel, variant (including link), size, fullWidth, pressed, action, disabled, busy; add splitItems[] and menuLabel for the attached-menu variation
Accessibility
Visible label or aria-label on icon buttons; one primary action per decision area

Markup

<button class="smart-button smart-button--primary" type="button">Continue</button>
<button class="smart-button smart-button--primary" type="button" aria-busy="true" disabled>Save</button>
<button class="smart-button smart-button--danger smart-button--small" type="button"
  data-smart-toast data-toast-title="Deleted" data-toast-message="Item removed.">Delete</button>
<div class="smart-button-group" role="group" aria-label="Alignment">
  <button class="smart-button smart-button--small" type="button" aria-pressed="true">Left</button>
</div>
<div class="smart-split-button">
  <button class="smart-button smart-button--primary" type="button">Save</button>
  <button class="smart-button smart-button--primary smart-split-button__menu" type="button" id="save-menu-trigger"
    popovertarget="save-menu" aria-haspopup="menu" aria-expanded="false" aria-label="More actions">
    <span class="smart-button__menu-indicator" aria-hidden="true"></span>
  </button>
  <div class="smart-popover" id="save-menu" popover data-smart-anchor="save-menu-trigger">
    <div class="smart-menu" role="menu">
      <button type="button" role="menuitem">Save &amp; review</button>
      <button type="button" role="menuitem">Save as template</button>
    </div>
  </div>
</div>

Live example

02 Fields

Collect one labeled value — native inputs, textarea, range, and a custom select menu.

Classes
.smart-field, .smart-label, .smart-input, .smart-textarea, .smart-hint, .smart-error; select: .smart-select*; advanced controls: .smart-multiselect, .smart-range-dual, .smart-rating, .smart-color
Data attributes
data-smart-select, data-value on root and options; data-smart-anchor on popover; data-smart-password + data-smart-reveal; data-smart-copy; data-smart-strength creates a password meter; data-smart-counter pairs a textarea with its native maxlength
Events
smart:select { value, option }; smart:combobox; smart:multiselect; smart:range; smart:rating; smart:copy; smart:otp; smart:quantity; smart:strength; smart:file; native input / change on hidden input; range updates --smart-range-fill
Agent catalog
Field uses one control family: "input", "textarea", "select", "combobox", "search", "file", "range", "date", "time", "tags", "daterange", "otp", "quantity", "multiselect", "numericrange", "rating", or "color". Optional mask is a property on text inputs, not a control type. Inputs accept text prefix/suffix or catalog-backed prefixIcon/suffixIcon; textarea counters use maxLength + counter.
Accessibility
Programmatic label; custom select exposes combobox + listbox semantics; link errors with aria-describedby

Markup

<label class="smart-field">
  <span class="smart-label">Workspace</span>
  <input class="smart-input" name="workspace" placeholder="Acme Studio">
  <span class="smart-hint">This appears in your shared URL.</span>
</label>
<label class="smart-field">
  <span class="smart-label">Search</span>
  <span class="smart-input-group">
    <span class="smart-input-group__prefix smart-input-group__prefix--icon" aria-hidden="true">
      <svg class="smart-icon"><use href="smartwebui-icons.svg#smart-icon-search"></use></svg>
    </span>
    <input class="smart-input" type="search">
  </span>
</label>
<label class="smart-field">
  <span class="smart-label">Summary</span>
  <textarea class="smart-textarea" maxlength="160" data-smart-counter></textarea>
</label>
<label class="smart-field">
  <span class="smart-label">New password</span>
  <input class="smart-input" type="password" name="password" data-smart-strength>
</label>
<label class="smart-field">
  <span class="smart-label">Verification code</span>
  <div class="smart-otp">
    <input class="smart-otp__digit" type="text" inputmode="numeric" maxlength="1" aria-label="Digit 1 of 6">
    <!-- one input per digit… -->
    <input type="hidden" name="code">
  </div>
  <span class="smart-hint">Auto-advances; paste distributes across digits (smart:otp).</span>
</label>
<label class="smart-field">
  <span class="smart-label">Seats</span>
  <div class="smart-quantity">
    <button class="smart-quantity__step" type="button" data-smart-step="-1" aria-label="Decrease quantity"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-minus"></use></svg></button>
    <input type="number" name="seats" value="3" min="1" max="25" aria-label="Seats">
    <button class="smart-quantity__step" type="button" data-smart-step="1" aria-label="Increase quantity"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-plus"></use></svg></button>
  </div>
</label>

<div class="smart-select" data-smart-select data-value="designer">
  <input type="hidden" name="role" value="designer">
  <button class="smart-select__trigger" id="role-trigger" type="button" role="combobox"
    popovertarget="role-menu" aria-expanded="false" aria-labelledby="role-label role-value">
    <span class="smart-select__value" id="role-value">Designer</span>
  </button>
  <div class="smart-select__popover" id="role-menu" popover role="listbox"
    data-smart-anchor="role-trigger">
    <div class="smart-select__option" role="option" aria-selected="true" data-value="designer">Designer</div>
  </div>
</div>

Validation

Client-side states come from native constraint validation: :user-invalid / :user-valid style touched controls (danger border, success border), required fields get an automatic * on the label, and .smart-error--live reveals its message only while the field is invalid. Server-side errors stay class-driven: .smart-field--invalid plus a visible .smart-error — what the agent emits for Field.error.

<label class="smart-field">
  <span class="smart-label">Work email</span>
  <input class="smart-input" type="email" required placeholder="name@company.com">
  <span class="smart-error smart-error--live">Enter a valid email address.</span>
</label>

Live example · See also Combobox, File upload, Date & time, Search, Tags

03 Choice controls

Accessible browser-native checkboxes and radio buttons with full-row labels.

Classes
.smart-choice, .smart-choice__text; group radios with .smart-fieldset
Data attributes
None — use native name for radio groups
Events
Native change; agent emits smart:change
Agent catalog
Choicekind (checkbox / radio), label, name, checked, detail
Accessibility
The complete visible row is the input label; radios sharing a decision use the same name

Markup

<label class="smart-choice">
  <input type="checkbox" name="summary" checked>
  <span class="smart-choice__text">Weekly summary<small>A concise report every Monday.</small></span>
</label>
<label class="smart-choice">
  <input type="radio" name="plan" checked>
  <span class="smart-choice__text">Team workspace</span>
</label>

Live example

04 Switches

Immediately applied binary settings with a native checkbox under the hood.

Classes
.smart-switch, .smart-switch__track, .smart-switch-row with .smart-split
Events
Native change; agent emits smart:change
Agent catalog
Switchlabel, checked, disabled
Accessibility
Native checkbox; the complete row is the label. Do not use when changes require a separate Save action.

Markup

<label class="smart-split smart-switch-row">
  <span>Live collaboration</span>
  <span class="smart-switch">
    <input type="checkbox" name="collab" checked>
    <span class="smart-switch__track"></span>
  </span>
</label>

Live example

05 Segmented control

Compact mutually exclusive choices from two to five options.

Classes
.smart-segmented; JS adds .smart-segmented--moving during animation
Data attributes
data-smart-segmented (set by hydration)
Events
Native change on radio inputs; arrow, Home, and End keyboard navigation
Agent catalog
SegmentedControllabel, name, options
Accessibility
Native radio group with role="radiogroup". Use Tabs for peer content views instead.

Markup

<div class="smart-segmented" role="radiogroup" aria-label="View">
  <label><input type="radio" name="view" checked><span>Overview</span></label>
  <label><input type="radio" name="view"><span>Activity</span></label>
  <label><input type="radio" name="view"><span>Insights</span></label>
</div>

Live example

06 Cards

Group coherent content or a single metric with restrained depth.

Classes
.smart-card, --interactive, --media, --list, --selected; metrics use .smart-metric, .smart-metric__label, .smart-metric__value, .smart-metric__change
Events
click when interactive with optional data-smart-toast
Agent catalog
Cardtitle, label, value, change, interactive; accepts child nodes, including a Chart sparkline

Markup

<button class="smart-card smart-card--interactive smart-metric" type="button"
  data-smart-toast data-toast-title="Revenue details" data-toast-message="Up 12.4% this month.">
  <span class="smart-metric__label">Monthly recurring revenue</span>
  <strong class="smart-metric__value">$48.2k</strong>
  <span class="smart-metric__change"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-trending-up"></use></svg> 12.4% this month</span>
</button>

Live example

07 Badges

Compact status or classification — meaning always in text, not color alone.

Classes
.smart-badge, modifiers --accent, --success, --warning, --danger, --plain, --dot
Agent catalog
Badgelabel, tone, plain, dotOnly

Markup

<span class="smart-badge smart-badge--accent">In review</span>
<span class="smart-badge smart-badge--success">Published</span>
<span class="smart-badge smart-badge--plain">FREE</span>

Live example

08 Tabs

Switch between peer content views without leaving the current context.

Classes
.smart-tabs, .smart-tab-list, .smart-tab, .smart-tab-panel; vertical: .smart-tabs--vertical + aria-orientation="vertical" — side list for settings layouts, Up/Down keys move between tabs, collapses to horizontal under 50rem
Events
smart:tab { tab }; arrow, Home, and End keyboard navigation
Agent catalog
Tabslabel, selected, items[] with value, label, content
Accessibility
Full ARIA tablist / tab / tabpanel wiring with aria-controls and aria-selected

Markup

<div class="smart-tabs">
  <div class="smart-tab-list" role="tablist" aria-label="Project details">
    <button class="smart-tab" id="tab-overview" type="button" role="tab"
      aria-selected="true" aria-controls="panel-overview">Overview</button>
  </div>
</div>
<div class="smart-tab-panel" id="panel-overview" role="tabpanel" aria-labelledby="tab-overview">
  Everything is on track.
</div>

Live example

09 Accordion

Progressively disclose secondary information using native <details>.

Classes
.smart-accordion, .smart-accordion__content
Events
Native toggle on <details>; bubbling smart:accordion { index, open, details } on the accordion
Agent catalog
Accordionitems[], optional exclusive (shared name attribute)

Markup

<div class="smart-accordion">
  <details name="faq" open>
    <summary>Does it need JavaScript?<svg class="smart-icon smart-accordion__indicator" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-chevron-down"></use></svg></summary>
    <div class="smart-accordion__content">Only interactive enhancements do.</div>
  </details>
</div>

Live example

10 Dialog

Focused input or confirmation that temporarily blocks the page — native <dialog> semantics.

Classes
.smart-dialog, .smart-dialog__header, .smart-dialog__body, .smart-dialog__footer; variants --confirm, --wide, --scroll
Data attributes
data-smart-open="#dialog-id" on trigger; data-smart-close on dismiss buttons
Events
Native dialog open/close; Escape and backdrop dismiss; focus trapped and returned. Agent catalog lists close as a surface lifecycle event.
Agent catalog
Dialogtitle, text, open; child Button or Field nodes

Markup

<button class="smart-button smart-button--primary" type="button"
  data-smart-open="#welcome-dialog">Open dialog</button>

<dialog class="smart-dialog" id="welcome-dialog" aria-labelledby="welcome-title">
  <header class="smart-dialog__header smart-split">
    <div><h2 id="welcome-title">Welcome</h2></div>
    <button class="smart-button smart-button--icon smart-button--quiet" type="button"
      data-smart-close aria-label="Close"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-close"></use></svg></button>
  </header>
  <div class="smart-dialog__body"><p>Native dialog, polished by default.</p></div>
  <footer class="smart-dialog__footer">
    <button class="smart-button" type="button" data-smart-close>Cancel</button>
    <button class="smart-button smart-button--primary" type="button" data-smart-close autofocus>Continue</button>
  </footer>
</dialog>

Live example · See also Confirm dialog

11 Drawer

Focused secondary task in a side sheet while preserving page context.

Classes
.smart-dialog.smart-drawer — drawer is a dialog variant
Data attributes
Same as Dialog: data-smart-open, data-smart-close
Agent catalog
Drawer — same properties as Dialog

Markup

<button class="smart-button" type="button" data-smart-open="#settings-drawer">Open drawer</button>

<dialog class="smart-dialog smart-drawer" id="settings-drawer" aria-labelledby="settings-title">
  <header class="smart-dialog__header smart-split">
    <div><h2 id="settings-title">Project settings</h2></div>
    <button class="smart-button smart-button--icon smart-button--quiet" type="button"
      data-smart-close aria-label="Close"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-close"></use></svg></button>
  </header>
  <div class="smart-dialog__body smart-stack">…</div>
  <footer class="smart-dialog__footer">
    <button class="smart-button smart-button--primary" type="button" data-smart-close>Save</button>
  </footer>
</dialog>

Live example

12 Popover menu

Short list of actions anchored to one trigger — native Popover API, no positioning library.

Classes
.smart-popover, .smart-menu, .smart-button__menu-indicator
Data attributes
popovertarget / popover; data-smart-anchor="trigger-id" for viewport positioning
Events
Menu keyboard (arrows, Home, End, Escape); agent menu items emit smart:action
Agent catalog
PopoverMenulabel, items[]

Markup

<button class="smart-button" id="project-actions" type="button"
  popovertarget="project-menu" aria-haspopup="menu" aria-expanded="false">
  Project actions <span class="smart-button__menu-indicator" aria-hidden="true"></span>
</button>
<div class="smart-popover" id="project-menu" popover data-smart-anchor="project-actions">
  <div class="smart-menu" role="menu" aria-labelledby="project-actions">
    <button type="button" role="menuitem">Duplicate</button>
    <button type="button" role="menuitem">Share link</button>
  </div>
</div>

Live example

13 Tooltips

Brief label or clarification for compact icon controls.

Classes
.smart-tooltip on the trigger (usually with .smart-button--icon)
Data attributes
data-tooltip="Share project"; JS sets data-smart-tooltip-hidden after Escape
Agent catalog
Tooltiplabel, tooltip, optional action
Accessibility
Trigger needs aria-label; never put essential instructions only in a tooltip

Markup

<button class="smart-button smart-button--icon smart-tooltip" type="button"
  data-tooltip="Share project" aria-label="Share project"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-share-2"></use></svg></button>

Live example

14 Toasts

Non-blocking completion or status feedback in a polite live region.

Classes
.smart-toast-region; rendered toasts use .smart-toast, .smart-toast--{tone}
Data attributes (trigger)
data-smart-toast, data-toast-title, data-toast-message, data-toast-action, data-toast-action-label, data-toast-placement
Events
SmartWebUI.toast({ title, message, tone, duration, action, placement }); placements combine top|bottom with logical start|center|end; smart:toast-action { action }
Agent catalog
Toasttitle, message, tone, placement

Markup

<!-- Once per page -->
<div class="smart-toast-region" data-placement="bottom-end" aria-live="polite" aria-label="Notifications"></div>

<button class="smart-button smart-button--primary" type="button"
  data-smart-toast data-toast-title="Project saved"
  data-toast-message="Your changes are available to everyone."
  data-toast-action="undo-save" data-toast-action-label="Undo"
  data-toast-placement="top-end">Show notification</button>

<!-- Or programmatically -->
<script>SmartWebUI.toast({ title: "Saved", tone: "success" });</script>

Live example

15 Progress & skeleton

Linear, circular, indeterminate, and placeholder waiting states.

Classes
.smart-progress (native <progress>), .smart-progress-ring, .smart-progress-ring__value, .smart-spinner, .smart-skeleton
Agent catalog
Progressvariant: "bar" | "ring" | "spinner" | "skeleton", label, value, maximum, indeterminate, skeletons, size, showValue
Accessibility
aria-label on progress; spinner uses role="status" with visually hidden text

Markup

<progress class="smart-progress" max="100" value="42" aria-label="Upload progress">42%</progress>
<span class="smart-spinner" role="status"><span class="smart-visually-hidden">Loading</span></span>
<div class="smart-progress-ring" style="--smart-progress-value:78" role="progressbar" aria-label="Complete" aria-valuenow="78" aria-valuemin="0" aria-valuemax="100"><span class="smart-progress-ring__value">78%</span></div>
<span class="smart-skeleton"></span>

Live example

16 Table

Production data tables with sorting, selection, sticky headers, expandable detail, controlled widths, and totals.

Classes
.smart-table-wrap, .smart-table, .smart-table--sticky, .smart-table__sort, .smart-table__expand, .smart-table__detail, .smart-table__bar
Data attributes
data-smart-table on the wrap; data-smart-sort="columnKey" on sort buttons; data-smart-selected on <tr>; data-smart-table-all on select-all; data-smart-table-action on bulk actions
Events
smart:sort { key, direction }; smart:table-select { selected, values }; smart:table-action { action, selected, values }
Agent catalog
Tablecaption, columns (optional width), rows (optional detail), totals, stickyHeader, expandable, selectable, actions[]

Markup

<div class="smart-table-wrap">
  <table class="smart-table">
    <caption>Team members</caption>
    <thead>
      <tr>
        <th scope="col"><button class="smart-table__sort" type="button"
          data-smart-sort="name" aria-sort="none">Name<span class="smart-table__sort-icon" aria-hidden="true"><svg class="smart-icon smart-table__sort-up" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-arrow-up"></use></svg><svg class="smart-icon smart-table__sort-down" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-arrow-down"></use></svg></span></button></th>
        <th scope="col">Status</th>
      </tr>
    </thead>
    <tbody>
      <tr data-smart-selected="false">
        <td><label class="smart-choice"><input type="checkbox" aria-label="Select row"></label></td>
        <td>Maya Chen</td>
        <td><span class="smart-badge smart-badge--success">Active</span></td>
      </tr>
    </tbody>
  </table>
</div>

Live example

17 Pagination

Move between result pages — prefer real links for server-rendered pagination.

Classes
.smart-pagination, .smart-pagination__list, .smart-pagination__link, .smart-pagination__summary, .smart-pagination__size
Agent catalog
Paginationpages[], summary, pageSize, pageSizes[]
Accessibility
nav semantics; aria-current="page" on active link; aria-disabled="true" on unavailable prev/next

Markup

<nav class="smart-pagination" aria-label="Results pages">
  <ul class="smart-pagination__list">
    <li><a class="smart-pagination__link" href="?page=1" aria-disabled="true" aria-label="Previous page"><svg class="smart-icon smart-icon--directional" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-arrow-left"></use></svg></a></li>
    <li><a class="smart-pagination__link" href="?page=1" aria-current="page">1</a></li>
    <li><a class="smart-pagination__link" href="?page=2">2</a></li>
    <li><a class="smart-pagination__link" href="?page=2" aria-label="Next page"><svg class="smart-icon smart-icon--directional" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-arrow-right"></use></svg></a></li>
  </ul>
  <span class="smart-pagination__summary">Page 1 of 3</span>
</nav>

Live example

18 Alerts

Persistent inline status, policy, or error messaging — not for transient feedback.

Classes
.smart-alert, tone modifiers, .smart-alert__icon, .smart-alert__content, .smart-alert__close; viewport choice variation: .smart-alert--banner.smart-consent with .smart-alert__actions
Agent catalog
Alerttitle, message, tone, dismissible; use placement: "viewport-bottom" and actions[] for consent or policy decisions
Accessibility
role="alert" or role="status" depending on tone; dismiss control is keyboard operable

Markup

<div class="smart-alert smart-alert--warning" role="alert">
  <span class="smart-alert__icon" aria-hidden="true"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-triangle-alert"></use></svg></span>
  <div class="smart-alert__content">
    <strong>Trial ending soon</strong>
    <p>Your workspace trial ends in 3 days.</p>
  </div>
  <button class="smart-alert__close" type="button" aria-label="Dismiss alert"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-close"></use></svg></button>
</div>

Live example

19 Form errors

Form-level validation summary with jump links plus field-level errors.

Classes
.smart-form-errors, .smart-field--invalid, .smart-error (+ .smart-error--live for client-side reveal), .smart-fieldset
Agent catalog
FormErrorstitle, items[] with field targets
Accessibility
Summary uses role="alert"; fields use aria-invalid and aria-describedby; summary links target field IDs

Markup

<div class="smart-form-errors" role="alert">
  <h3>Fix 2 issues before saving</h3>
  <ul><li><a href="#email-field">Enter a valid email address</a></li></ul>
</div>
<label class="smart-field smart-field--invalid">
  <span class="smart-label">Email</span>
  <input class="smart-input" id="email-field" type="email" value="name@"
    aria-invalid="true" aria-describedby="email-error">
  <span class="smart-error" id="email-error">Enter a valid email address.</span>
</label>

Live example

20 Breadcrumbs

Current location within a hierarchy — current page is not a link.

Classes
.smart-breadcrumbs, .smart-breadcrumbs--collapsed
Data attributes
data-smart-breadcrumbs-collapse and optional data-collapse-after="3" fold deep middle paths behind an accessible ellipsis
Events
smart:breadcrumbs { expanded: true }
Agent catalog
Breadcrumbsitems[], collapse, collapseAfter

Markup

<nav class="smart-breadcrumbs" data-smart-breadcrumbs-collapse data-collapse-after="3" aria-label="Breadcrumb">
  <ol>
    <li><a href="/projects">Projects</a></li>
    <li><a href="/projects/atlas">A simpler web</a></li>
    <li><span aria-current="page">Billing</span></li>
  </ol>
</nav>

Live example

21 Combobox

Filterable listbox for searchable selection from a bounded option set.

Classes
.smart-combobox, .smart-combobox__input, .smart-combobox__popover, .smart-combobox__option, .smart-combobox__empty
Data attributes
data-smart-combobox, data-value; options use data-value and data-label
Events
smart:combobox { value, label, option }; native input / change on hidden input
Agent catalog
Field with control: "combobox" and options[]

Markup

<div class="smart-combobox" data-smart-combobox data-value="maya">
  <input type="hidden" name="assignee" value="maya">
  <input class="smart-combobox__input smart-input" id="assignee-input" type="text" role="combobox"
    popovertarget="assignee-list" aria-expanded="false" aria-autocomplete="list"
    placeholder="Search people…" value="Maya Chen">
  <div class="smart-combobox__popover smart-select__popover" id="assignee-list" popover role="listbox"
    data-smart-anchor="assignee-input">
    <div class="smart-combobox__option smart-select__option" role="option" aria-selected="true"
      data-value="maya" data-label="Maya Chen">Maya Chen</div>
    <div class="smart-combobox__empty" hidden>No matches</div>
  </div>
</div>

Live example

22 Empty state

Explain why a region is blank and offer a clear next step.

Classes
.smart-empty, .smart-empty__icon
Agent catalog
Emptytitle, message, actionLabel, action

Markup

<div class="smart-empty">
  <span class="smart-empty__icon" aria-hidden="true"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-plus"></use></svg></span>
  <h3>No projects yet</h3>
  <p>Create your first project to invite collaborators.</p>
  <button class="smart-button smart-button--primary" type="button">Create project</button>
</div>

Live example

23 Avatars

Identify a person or entity compactly with initials or an image.

Classes
.smart-avatar, size modifiers, .smart-avatar-wrap, .smart-avatar__status, .smart-avatar-group, .smart-avatar-group__more
Agent catalog
Avatar — single person with initials, src, alt, status; add items[], maximum, and label for a group
Accessibility
Image avatars need meaningful alt; initials-only avatars are decorative when paired with visible text

Markup

<span class="smart-avatar-wrap">
  <img class="smart-avatar smart-avatar--md" src="/avatars/maya.jpg" alt="Maya Chen">
  <span class="smart-avatar__status" data-status="online" aria-label="Online"></span>
</span>

<div class="smart-avatar-group" role="group" aria-label="Project team">
  <span class="smart-avatar smart-avatar--md" aria-label="Maya Chen">MC</span>
  <span class="smart-avatar smart-avatar--md" aria-label="Alex Rivera">AR</span>
  <span class="smart-avatar-group__more">+5</span>
</div>

Live example

24 List

Structured rows for activity feeds, settings, and notifications.

Classes
Standard .smart-list*; activity .smart-timeline*; facts .smart-key-value; inbox .smart-notification-list; files .smart-attachment-list
Events
smart:attachment-remove { id } from attachment rows
Agent catalog
Listitems[] with variant: "standard", "timeline", "key-value", "notifications", or "attachments"

Markup

<ul class="smart-list">
  <li class="smart-list__item">
    <span class="smart-avatar smart-avatar--sm" aria-hidden="true">AI</span>
    <span class="smart-list__main">
      <span class="smart-list__title">Agent summarized 14 responses</span>
      <span class="smart-list__detail">Customer research · Project Atlas</span>
    </span>
    <span class="smart-list__meta">9:38 AM</span>
  </li>
</ul>

Live example

25 File upload

Accessible file picker with live filename feedback.

Classes
.smart-file, .smart-file__control, .smart-file__label, .smart-file__name; post-upload display uses the List-family .smart-attachment-list and .smart-attachment
Data attributes
data-placeholder on .smart-file__name; data-smart-dropzone on .smart-file enables drag-and-drop; data-max-size (bytes) and accept validate picks with inline .smart-file__error feedback
Events
Native change on file input; smart:file on accept/reject; SmartWebUI.upload(input, url, { method, fieldName, fields, headers }) uploads with a progress bar and smart:upload-progress / -done / -error events
Agent catalog
Field with control: "file", accept, buttonLabel, dropzone: true

Markup

<label class="smart-file">
  <span class="smart-label">Import CSV</span>
  <span class="smart-file__control">
    <input type="file" name="import" accept=".csv">
    <span class="smart-file__label">Choose file</span>
    <span class="smart-file__name" data-placeholder="No file chosen">No file chosen</span>
  </span>
</label>

<label class="smart-file" data-smart-dropzone>
  <span class="smart-file__control">
    <input type="file" name="attachment" accept=".pdf" data-max-size="5242880">
    <span class="smart-file__label">Choose file</span>
    <span class="smart-file__name" data-placeholder="Drop a PDF or browse"></span>
  </span>
</label>

Live example

26 Date & time

Custom pickers with rounded popovers and circular day selection — not native <input type="date">.

Classes
Date: .smart-date, .smart-date__trigger, .smart-date__popover, .smart-date__grid, .smart-date__day; Time: .smart-time__* parallels
Data attributes
data-smart-date / data-smart-time, data-value (ISO date YYYY-MM-DD or time HH:MM); range: data-smart-date-range, data-start, data-end, data-smart-range-start, data-smart-range-end; nav: data-smart-date-prev, data-smart-date-next, data-smart-date-clear, data-smart-date-today; time: data-smart-time-clear, data-smart-time-now
Events
smart:date { value } or { start, end }, smart:time { value }; native input / change on hidden inputs
Agent catalog
Field with control: "date", "daterange", or "time"
Notes
Calendar grid is rendered by JavaScript on open. Popover opens below the trigger, left-aligned, at compact width (~14rem).

Markup

<div class="smart-date" data-smart-date data-value="2026-08-20">
  <input type="hidden" name="due_date" value="2026-08-20">
  <button class="smart-date__trigger" id="due-trigger" type="button"
    popovertarget="due-popover" aria-expanded="false" aria-labelledby="due-label due-value">
    <span class="smart-date__value" id="due-value">Aug 20, 2026</span>
  </button>
  <div class="smart-date__popover" id="due-popover" popover role="dialog"
    aria-label="Choose date" data-smart-anchor="due-trigger">
    <div class="smart-date__header">
      <button class="smart-date__nav" type="button" data-smart-date-prev aria-label="Previous month"><svg class="smart-icon smart-icon--directional" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-chevron-left"></use></svg></button>
      <div class="smart-date__title"></div>
      <button class="smart-date__nav" type="button" data-smart-date-next aria-label="Next month"><svg class="smart-icon smart-icon--directional" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-chevron-right"></use></svg></button>
    </div>
    <div class="smart-date__weekdays" aria-hidden="true"><span>M</span>…</div>
    <div class="smart-date__grid" role="grid"></div>
    <div class="smart-date__footer">
      <button class="smart-date__action" type="button" data-smart-date-clear>Clear</button>
      <button class="smart-date__action" type="button" data-smart-date-today>Today</button>
    </div>
  </div>
</div>

Live example

27 Confirm dialog

Destructive confirmations with native modal semantics and explicit accept/cancel.

Classes
.smart-dialog.smart-dialog--confirm; destructive action uses .smart-button--primary in the footer
Data attributes
data-smart-confirm="#dialog-id" on trigger; data-smart-confirm-accept on accept button; data-smart-close on cancel
Events
smart:confirm { accepted: true } on the trigger after accept
Agent catalog
Dialog with variant: "confirm"

Markup

<button class="smart-button smart-button--danger" id="delete-trigger" type="button"
  data-smart-confirm="#delete-dialog">Delete project</button>

<dialog class="smart-dialog smart-dialog--confirm" id="delete-dialog"
  aria-labelledby="delete-title">
  <header class="smart-dialog__header smart-split">
    <div><h2 id="delete-title">Delete project?</h2></div>
    <button class="smart-button smart-button--icon smart-button--quiet" type="button"
      data-smart-close aria-label="Close"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-close"></use></svg></button>
  </header>
  <div class="smart-dialog__body"><p>This action cannot be undone.</p></div>
  <footer class="smart-dialog__footer">
    <button class="smart-button" type="button" data-smart-close>Cancel</button>
    <button class="smart-button smart-button--primary" type="button"
      data-smart-confirm-accept>Delete project</button>
  </footer>
</dialog>

Live example

28 Page header

Introduce a page with title, description, an action cluster, or a responsive filter toolbar.

Classes
.smart-page-header, .smart-page-header__actions, .smart-page-header--toolbar, .smart-page-header__toolbar
Agent catalog
PageHeadertitle, description, actions[]
Accessibility
Page title renders as <h1>; one primary action in the cluster

Markup

<header class="smart-page-header">
  <div>
    <h1>Billing</h1>
    <p>Manage your plan, payment method, and invoices.</p>
  </div>
  <div class="smart-page-header__actions">
    <button class="smart-button" type="button">Download invoices</button>
    <button class="smart-button smart-button--primary" type="button">Upgrade plan</button>
  </div>
</header>

Toolbar variation

Use the toolbar modifier for filters, search, view controls, and a page-level action. The toolbar remains in one compact row from tablet widths upward and wraps naturally on smaller screens.

<header class="smart-page-header smart-page-header--toolbar">
  <div class="smart-page-header__content">
    <h1 class="smart-page-header__title">Tickets</h1>
  </div>
  <div class="smart-page-header__actions">
    <div class="smart-page-header__toolbar">
      <!-- filters, search, or view controls -->
    </div>
    <a class="smart-button smart-button--primary" href="/tickets/new">New ticket</a>
  </div>
</header>

Live example

29 App shell

Flat or nested collapsible sidebar navigation with a main content region.

Classes
.smart-app-shell, .smart-sidebar, .smart-sidebar__group, .smart-sidebar__nav, .smart-sidebar__link, .smart-app-shell__main, .smart-app-shell__menu, .smart-app-shell__collapse, .smart-app-shell__backdrop
Data attributes
data-smart-shell; data-collapsed for the icon rail; data-nav-open for the mobile drawer; data-smart-shell-menu, data-smart-shell-collapse, data-smart-shell-close
Agent catalog
AppShellbrand, recursive items[] groups with open; leaf items accept icon; collapsed activates the icon rail; accepts child content nodes
Accessibility
Sidebar uses nav; current page link has aria-current="page"; menu and collapse expose aria-expanded

Markup

<div class="smart-app-shell" data-smart-shell>
  <nav class="smart-sidebar" aria-label="Project">
    <div class="smart-sidebar__top">
      <div class="smart-sidebar__brand" data-smart-brand-mark="A">A simpler web</div>
      <button class="smart-app-shell__collapse" type="button" data-smart-shell-collapse
        data-smart-collapse-icon="chevron-left" aria-label="Collapse navigation" aria-expanded="true"><svg class="smart-icon smart-icon--directional" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-chevron-left"></use></svg></button>
    </div>
    <ul class="smart-sidebar__nav">
      <li><a class="smart-sidebar__link" href="/overview" aria-label="Overview" aria-current="page">
        <svg class="smart-icon" aria-hidden="true">…</svg><span class="smart-sidebar__label">Overview</span>
      </a></li>
      <li><a class="smart-sidebar__link" href="/settings" aria-label="Settings">
        <svg class="smart-icon" aria-hidden="true">…</svg><span class="smart-sidebar__label">Settings</span>
      </a></li>
    </ul>
  </nav>
  <div class="smart-app-shell__main">
    <button class="smart-app-shell__menu" type="button" data-smart-shell-menu
      aria-label="Open navigation" aria-expanded="false"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-menu"></use></svg></button>
    …
  </div>
</div>

Live example

30 Chips

Compact filters or removable token labels.

Classes
.smart-chip, --accent, .smart-chip__remove
Events
smart:chip-remove { label }; chip removed from DOM
Agent catalog
Chiplabel, tone, removable

Markup

<span class="smart-chip smart-chip--accent">Design
  <button class="smart-chip__remove" type="button" aria-label="Remove Design filter"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-close"></use></svg></button>
</span>

Live example

32 Tags

Collect multiple token values in one field. Enter or comma adds a chip; Backspace removes the last.

Classes
.smart-tags, .smart-tags__control, .smart-tags__input, plus .smart-chip
Data attributes
data-smart-tags on the root; hidden input holds a comma-separated value
Events
smart:tags { value, tags }; chip removal also emits smart:chip-remove
Agent catalog
Field with control: "tags"

Markup

<div class="smart-tags" data-smart-tags>
  <input type="hidden" name="topics" value="Design,Published">
  <div class="smart-tags__control">
    <span class="smart-chip">Design
      <button class="smart-chip__remove" type="button" aria-label="Remove Design"><svg class="smart-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-close"></use></svg></button>
    </span>
    <input class="smart-tags__input" type="text" placeholder="Add a tag">
  </div>
</div>

Live example

33 Stepper

A short wizard with current, complete, and pending steps.

Classes
.smart-stepper, .smart-stepper__list, .smart-stepper__step, .smart-stepper__panel, .smart-stepper__actions
Data attributes
data-smart-stepper, data-step, data-smart-step, data-step-panel, data-smart-step-prev, data-smart-step-next, data-continue-label, data-finish-label
Events
smart:step { step, finished }
Agent catalog
Steppersteps[], step, continueLabel, finishLabel
Accessibility
Current step uses aria-current="step"; completed steps use data-complete and a check mark

Markup

<div class="smart-stepper" data-smart-stepper data-step="0">
  <ol class="smart-stepper__list">
    <li><button class="smart-stepper__step" type="button" data-smart-step="0" aria-current="step">Account<svg class="smart-icon smart-stepper__complete-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-check"></use></svg></button></li>
    <li><button class="smart-stepper__step" type="button" data-smart-step="1">Workspace<svg class="smart-icon smart-stepper__complete-icon" aria-hidden="true"><use href="smartwebui-icons.svg#smart-icon-check"></use></svg></button></li>
  </ol>
  <div class="smart-stepper__panel" data-step-panel="0">…</div>
  <div class="smart-stepper__panel" data-step-panel="1" hidden>…</div>
  <div class="smart-stepper__actions">
    <button class="smart-button" type="button" data-smart-step-prev disabled>Back</button>
    <button class="smart-button smart-button--primary" type="button" data-smart-step-next>Continue</button>
  </div>
</div>

Live example

35 Command palette

Searchable modal commands opened by a normalized shortcut — the keyboard-first navigation pattern.

Classes
.smart-command, .smart-command__item, .smart-command__group, .smart-kbd
Data attributes
data-smart-command on the <dialog>; data-smart-command-shortcut (for example mod+k); data-smart-command-search on the filter input; data-smart-command-item with role="option" on each action; optional data-smart-command-group for labeled sections.
Events
smart:action { action, id } when an item is chosen; the dialog uses native close. Arrow keys move between results, Enter activates, Escape closes.
Agent catalog
CommandPalette — required label + items[] (label, action, optional group, shortcut); optional placeholder, open, shortcut. See smartwebui.catalog.json for the validated property map.
Accessibility
The palette is a native <dialog> with role="dialog". The listbox uses role="listbox" with role="option" items. Active selection follows aria-activedescendant. The search input is auto-focused on open; focus returns to the trigger on close.

Markup

<dialog class="smart-dialog smart-command" data-smart-command
  data-smart-command-shortcut="mod+k" aria-label="Commands">
  <input data-smart-command-search placeholder="Search commands…">
  <div role="listbox">
    <button data-smart-command-item role="option" data-action="create">Create</button>
    <button data-smart-command-item role="option" data-action="invite">Invite</button>
  </div>
</dialog>

Live example

36 Chart

One family of token-drawn SVG marks for dashboards: sparkline, bar, line, area, and donut.

Classes
.smart-chart, .smart-chart--sparkline / --bar / --line / --area / --donut, .smart-chart__plot, .smart-chart__caption, .smart-chart__legend
Data attributes
data-smart-chart, data-mark, optional data-values + data-labels, or a JSON <script type="application/json"> payload. Series tones are accent, success, warning, danger, secondary only.
Events
smart:chart-select { index, x, values, summary } from click or arrow keys; hover and keyboard focus show a value tooltip in the same language as .smart-tooltip
Agent catalog
Chart — required label; mark; optional series[] with tone + points[{x,y}] or a single-series values + labels shorthand. Sparkline composes as a Card child. At most 8 series and 48 finite points. No SVG, hex, or path data from JSON.
Accessibility
Figure + caption, decorative SVG, visually hidden data table, keyboard selection on cartesian marks. Progress ring stays the one-number circular indicator.

Markup

<article class="smart-card smart-metric">
  <span class="smart-metric__label">Monthly revenue</span>
  <strong class="smart-metric__value">$48.2k</strong>
  <figure class="smart-chart" data-smart-chart data-mark="sparkline"
    data-values="36,48,42,67,72,91" data-labels="Mar,Apr,May,Jun,Jul,Aug"
    aria-label="Revenue trend"></figure>
</article>

<figure class="smart-chart" data-smart-chart data-mark="bar"
  data-values="36,48,42,67,72,91" data-labels="Mar,Apr,May,Jun,Jul,Aug"
  aria-label="Monthly revenue">
  <figcaption class="smart-chart__caption">Monthly revenue</figcaption>
</figure>

Live example

37 Payment

An intent-only checkout slot: the agent reserves the surface, your host app mounts the real payment element into it.

Classes
.smart-payment, .smart-payment__summary, .smart-payment__label, .smart-payment__amount, .smart-payment__element
Data attributes
data-smart-payment on the container; the mount slot id is derived from the node id
Events
smart:payment-ready when the host-mounted element finishes loading; aria-busy on the slot while it loads
Agent catalog
Paymentlabel, amount + currency (display only), description. Never keys, secrets, or card data.
Stripe
Load the optional smartwebui-stripe.js companion and call SmartWebUIStripe.mount(slot, { publishableKey, clientSecret }). Stripe.js lazy-loads from js.stripe.com, and the Elements appearance is derived from your --smart-* tokens — re-synced automatically on theme, density, and material changes.

Markup

<!-- What an agent surface renders for { type: "Payment", id: "checkout", label: "Pro plan", amount: 24, currency: "USD" } -->
<div class="smart-payment" data-smart-payment>
  <div class="smart-payment__summary">
    <span class="smart-payment__label">Pro plan</span>
    <span class="smart-payment__amount">$24.00</span>
  </div>
  <!-- The mount slot id is derived from the node id (here "checkout-element") -->
  <div class="smart-payment__element" id="checkout-element"></div>
</div>

<script type="module">
  // Host code — keys and clientSecret never appear in agent JSON.
  // Select the renderer-derived slot id; do not hardcode a different selector.
  await SmartWebUIStripe.mount("#checkout-element", {
    publishableKey: "pk_live_…",
    clientSecret: paymentIntentClientSecretFromYourServer,
  });

  // Address and Express Checkout (Apple Pay / Google Pay) mount the same way:
  await SmartWebUIStripe.mount("#address-element", {
    publishableKey, clientSecret,
    elementType: "address",
    addressMode: "shipping",
  });
  await SmartWebUIStripe.mount("#express-element", {
    publishableKey, clientSecret,
    elementType: "expressCheckout",
  });
</script>

Layout primitives

Four composable layout nodes for agent surfaces. They also map to utility classes in hand-written HTML.

Stack
Vertical arrangement with consistent spacing. CSS: .smart-stack. Property: gap (small, medium, large). Forms use --smart-form-gap automatically when marked form.smart-stack.
Cluster
Inline wrapping group. CSS: .smart-cluster. Property: gap.
Grid
Responsive peer grid. CSS: .smart-grid. Property: minimum (compact, standard, wide). Grids containing .smart-field widen column gap and default to an 18rem minimum.
Text
Trusted text with semantic emphasis. Property: text, as (p, strong, small, h2, h3), tone.

See also application layout helpers in Utilities — page shells, auth columns, card grids, and log panels built from the same stack/cluster/grid primitives.

Global behaviors

Shared data attributes and library events used across multiple components.

Data attributes

  • data-smart-open — open a <dialog> by selector
  • data-smart-close — close the closest dialog
  • data-smart-confirm / data-smart-confirm-accept — confirm dialog flow
  • data-smart-toast + data-toast-* — trigger a toast
  • data-smart-counter, data-smart-connectivity, data-smart-breadcrumbs-collapse, data-smart-scroll-top — hydrate counters and application-state affordances
  • data-smart-anchor — popover positioning anchor ID
  • data-smart-select, data-smart-combobox, data-smart-date, data-smart-time, data-smart-tags, data-smart-table, data-smart-shell, data-smart-stepper, data-smart-chart, data-smart-carousel — hydrate interactive controls
  • data-smart-collapse="#id" — toggle a region's hidden state from a remote trigger
  • data-smart-scrollspy — TOC nav highlights the section in view (aria-current="location")
  • data-smart-sort — table column sort key
  • data-smart-theme, data-smart-density, data-smart-material — apply preferences on click

Library events

  • smart:ready — library loaded (document); smart:compat — missing baseline features (data-smart-compat)
  • smart:tab, smart:select, smart:combobox, smart:date, smart:time, smart:tags, smart:copy, smart:sort, smart:table-select, smart:table-action, smart:step, smart:chip-remove, smart:confirm, smart:toast-action, smart:otp, smart:accordion, smart:breadcrumbs, smart:chart-select, smart:slide, smart:multiselect, smart:rating, smart:attachment-remove
  • smart:update, smart:theme, smart:density, smart:material
  • Agent runtime: smart:surface, smart:patch, smart:action, smart:change, smart:adopt, smart:agent-ready; payment companion: smart:stripe-ready, smart:payment-ready
  • smart:locale, smart:validate, smart:quantity, smart:strength, smart:file, smart:upload-progress, smart:upload-done, smart:upload-error, smart:countdown, smart:countdown-expire
  • Host utilities: smart:connectivity, smart:dirty, smart:query-state, smart:virtualize

Call SmartWebUI.hydrate(root) after inserting markup dynamically. Machine-readable agent contracts live in smartwebui.catalog.json.

Materials

Solid surfaces are the default. Liquid is an opt-in global material using the same markup: set <html data-material="liquid"> or call SmartWebUI.setMaterial("liquid"). Tune it with the --smart-liquid-* tokens; lower --smart-liquid-opacity values create greater transparency.

Liquid controls track a precise pointer through CSS variables, with no component initialization. Connected controls share one optical container, while content surfaces use restrained edge light, tint, and depth rather than opaque Solid fills. The effect disables itself for reduced motion, touch-only input, and accessibility preferences, and smartwebUI increases opacity when the browser requests reduced transparency or increased contrast.

Browser policy

smartwebUI targets browser releases from the preceding 36 months — covering color-mix(), cascade layers, the Popover API, @starting-style, and light-dark(). No legacy bundles or polyfills are shipped. Where a newer animation feature is missing, components remain fully functional with simpler motion.

Who this is for

Teams that choose maintainability and modern platform APIs over supporting browsers outside the window. Internal tools, public SaaS on evergreen browsers, and agent-built dashboards fit well.

Who should not adopt the full stack

  • Environments with frozen corporate browsers or old embedded WebViews
  • Kiosks or devices that cannot receive security updates
  • Products with contractual legacy-browser SLAs — use tokens and hand-authored HTML without Popover-dependent components if you need a partial fit

What unsupported clients see

With data-smart-compat, a dismissible banner asks users to update. Core content remains readable where CSS parses; interactive components that depend on Popover or dialog may degrade. The default banner uses inline styles so it appears even when the main stylesheet cannot load.

Bidirectional layouts are supported throughout: spacing, borders, and radii use logical properties, so setting dir="rtl" mirrors the interface. A handful of physical-direction exceptions (switch knob, combobox chevron, calendar nav glyphs) are corrected with html[dir="rtl"] overrides.

Compatibility banner

Add data-smart-compat to your <html> element and smartwebUI feature-detects the baseline at boot — Popover API, <dialog>, color-mix(), light-dark(), :has(), logical properties. On a browser that misses any of them, a dismissible warning banner appears telling the end user the app deliberately uses the latest standards and asking them to update. Provide your own markup with <div class="smart-compat-banner" data-smart-compat-banner hidden> (optionally a data-smart-compat-dismiss button), or let the library inject a default — inline-styled so it survives even browsers too old for the layered stylesheet. The result also lands on <html data-smart-compat="supported|unsupported"> and a smart:compat event with the missing feature list.

Quality & accessibility

smartwebUI targets WCAG 2.2 AA at the component level. Keyboard operation, visible focus, reduced-motion support, and AA contrast in the default themes are engineering requirements, verified per release and documented in QUALITY.md and ACCESSIBILITY.md.

Complete pages still require an application-level audit: heading order, landmarks, error identification, zoom and reflow, custom themes, dir="rtl", and dynamic content. Date grids follow the locale week start. See the changelog for release history.

QUALITY.md documents contrast targets and release acceptance gates. ACCESSIBILITY.md separates component responsibilities from host-app obligations.

Agent-specific security boundaries: agent security and SECURITY.md.

The marketing site loads optional first-party analytics from analytics.elonovo.com only when the browser does not send Do Not Track or Global Privacy Control. The agent demo page does not load analytics.