UI — router, components, and styling

@civility/ui is the presentation layer: a landmark-aware layout router, the ui-* / civ-* web components, the CSS system (civility.css / utilities.css / theme.css), and the client runtime (createApp). civ init wires it in.

Component design and theming contract: ui spec (§6.4 components, §6.5 tokens). Full API: jsr.io/@civility/ui/doc.


1. Install #

deno add jsr:@civility/ui

Import the components once (usually in index.ts) to register the custom elements:

import '@civility/ui'

2. The layout router #

createLayoutRouter<Meta>() is the declarative router the reference apps use. It swaps one custom element into a <main> landmark per route and manages headers and nav:

import { createLayoutRouter } from '@civility/ui'

const { router, ready } = createLayoutRouter<NavMeta>({
  router: { selectorAttrib: 'data-route', useHash: true },
  defaultRoute: '/',
  landmarks: { main: 'main', header: 'body > header' },
  routes: {
    '/': { main: () => ({ tag: 'home-page' }) },
    '/items/{id}': {
      main: (ctx) => ({
        tag: 'item-page',
        attrs: { 'item-id': ctx.params.id },
      }),
    },
    '/settings': { main: () => ({ tag: 'settings-page' }) },
  },
})
ready()

Conventions worth copying:

  • Light DOM routes. createRenderRoot() { return this } so the shared CSS cascade reaches your markup; shadow DOM would isolate it.
  • Dynamic segments (/items/{id}) map ctx.params onto element attributes; the element reads the attribute and loads from the store.
  • Hash routing (useHash: true) means deep links need no server rewrite rules — what makes the drag-and-drop deploy work.
  • Mark nav links with data-route; the router manages active state.

createLayoutRouter is built on the lower-level Router primitive (Router.new({ selectorAttrib, useHash, defaultHandler }) + router.on(path, { on })), which stays available for imperative control.


3. Components #

Two prefixes, split by dependency:

  • ui-* — Civility-agnostic chrome, usable in any app: ui-icon, ui-dialog, ui-back-header, ui-header-content, ui-counter, ui-error-display, ui-notifications, ui-progress, plus CSS-only elements (ui-card, ui-grid, ui-alert, ui-badge, ui-bottom-bar, ui-spinner, …).
  • civ-* — require Civility runtime objects:
    • sync — civ-sync-input, civ-sync-state, civ-usage (Sync), civ-store-import (Store), civ-devices (Devices)
    • encryption — civ-locked-screen, civ-phrase-field, civ-pin-field (Encryption)
    • sharing — civ-share-dialog, civ-share-link, civ-shares-list, civ-comments, civ-comment-box (share recipe)
    • other — civ-version (update banner, pairs with withUpdatePolling), civ-service-ai-input (Services)

The per-component attribute/slot/event catalogue is §6.4.

The app shell

createApp() wires a typical www/index.ts in one call — the layout router plus connectivity, notifications (including progress), and version state, each an independently subscribable AppState. <ui-notifications> and <ui-progress> bind to it and render live. See 19_client_runtime.md.

Standalone HTML demos of the components and layouts live in examples/ — open them directly in a browser to see the chrome, colors, drawers, and page layouts without scaffolding an app.


4. Styling and theming #

An app ships three stylesheets, copied into www/static/ by the scaffold:

| File | Layer | Role | | ————— | ––––––– | ––––––––––––––––––––––––––––––––––––– | | civility.css | base + ui | The framework: landmark layout, resets, and the ui-* component styles. | | utilities.css | utilities | Atomic helpers (spacing, flex, text) for one-off tweaks. | | theme.css | @layer theme | Yours to edit — the design tokens (colors, radii, fonts) for this app. |

Because routes render into the light DOM, this cascade reaches every component: set CSS custom properties in @layer theme and both the framework and the ui-* components pick them up, in light and dark mode. No per-component overrides. Token list and dark-mode contract: §6.5.

/* www/static/theme.css */
@layer theme {
  :root {
    --color-accent: oklch(62% 0.19 255);
    --radius: 0.5rem;
    --font-sans: 'Inter', system-ui, sans-serif;
  }
}

Keep the base civility.css and utilities.css as-is (copy fresh from packages/cli/stubs/pwa/www/static/ or packages/ui/dist/civility.css on upgrade); put every app-specific choice in theme.css.


Next #

  • Store — the state your components render.
  • Sync — the civ-sync-* and civ-locked-screen components.
  • Recipes — components in the context of complete apps.