Group / Separator / AspectRatio / Surface`) and their props. A block ships
no `.css` file; a scoped `<style>` is the justified exception, never the
pattern.
4.**Blocks are removable by construction** — an app that never imports a
block pays zero bytes for it.
## The B contract (the block quality floor)
Guarded mechanically by `npm run blocks:check` (self-testing: the guard
asserts its own detectors against inline fixtures on every run).
| B | Obligation |
| --- | --- |
| B-1 | No morfo, no sema pack, no audit row. Behavior with contract surface is promoted to canon BEFORE the block composes it. |
| B-2 | Every interactive element is a canon eidos component (`Button`, `Link`, `Field`, …). Raw interactive natives (`button/input/select/textarea/a`) are an error. (Single exception: the already-rendered HTML that `Prose` receives — that content belongs to the app.) |
| B-3 | Composed components are consumed AS-IS through their public props (`variant/size/color/…`). Re-styling their internals from the block (CSS or `style=`) is forbidden. Colors are always roles/tokens via props. |
| B-4 | One-way imports (hard boundary 1). Deleting the tier leaves `check` green. Blocks do not import each other (see B-10). |
| B-5 | Content enters by composition (children/snippets) — never `root={tree}` data-tree props (`items={...}` only where the composed canon component is already data-driven). |
| B-6 | Responsive via the framework's mechanisms (responsive props of the layout components, canonical breakpoints). No `matchMedia`/listeners of its own — needing to observe something is the admission rule firing. |
| B-7 | A block owns NO visible string: all text arrives from the app as children/props. An unavoidable string is contract surface → the underlying canon component owns it (`texts:` + langs). |
| B-8 | Correct landmarks: sectioning element + `aria-label`/`aria-labelledby` where landmarks repeat; heading hierarchy coherent and documented in the block README (which level it emits, how to adjust). |
| B-9 | Every block ships `README.md` (**Function · Composition map** — which canon components, which props — **· Decisions · Gaps-with-disposition**) and a live demo page under `web/routes/blocks/{kebab}/`, built on the shared demo shell (`web/routes/blocks/_lib/BlockDemo.svelte`): the block is shown FULL-BLEED on the page — never inside a padded frame or a scroll box, which would change what it does — with the device widths served by its own `preview` route. Anatomy in [`src/uix/blocks/README.md`](../../src/uix/blocks/README.md). |
| B-10 | A block does not import another block. Shared structure is either a canon component or a conscious duplication (recorded in Gaps). Declared exception: the shells (`app-shell`, `docs-shell`) compose F1 pieces and blocks by design — allow-listed in `blocks:check`. |
| B-11 | Motion only through the composed components' `motion` props/presets or `Cascade` for entrance choreography. No `@keyframes`/transitions of its own. |
## Conventions
- **API**: compound component with nested parts (`<SiteHeader>` /
`<SiteHeader.Nav>` / `<SiteHeader.Actions>`); content always via children.
- **Demos**: `web/routes/blocks/{kebab}/+page.svelte`, indexed by the gallery
at `web/routes/blocks/`.
- **Services**: blocks do not consume `uix.prefs` / langs / eidos directly
(v1) — wiring (theme, language, submits, transport) arrives as
handlers/props from the app. Revisable when ≥2 blocks demonstrate a real