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}) mapctx.paramsonto 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 withwithUpdatePolling),civ-service-ai-input(Services)
- sync —
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.