---
title: RFC — Eidos structural systems (space · density · scale)
type: rfc
audience: human + agent
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress)
Re-audit of the whole component catalog at pilot depth (91 fichas + the
checkpoint verdicts in docs/audit/components/) and the executed fix packages.
- P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 +
API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d,
component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts
(role=application removed ×4, aria-selected off the Day, drp translationRef,
field data-state prune, pin-input commit-set, media-player renames), 13 new
sema packs + 12 morfos family-default → pack.
- P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar);
onValueCommit terminal-callback norm (pin-input/search/password/textarea +
date/time/color-field add); typed validation reason + onInvalid (tags-input,
css-field); index/onIndexChange (carousel); deselectable; openDelay/
groupSkipDelay; allowCustomValue; defaultValue prune.
- P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across
cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in
field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel);
touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual,
44 AAA). S6 (Field composition) + S8 (calendar-surface) pending.
Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline);
morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched
components; per-component vitest suites green.
Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md
Excluded (broken by the N1 rename, left broken per user decision, not staged):
words/**, palabras/**, chronos, web/routes/alpha/**.
Reconciliation pending: the touch-rows ::before for checkbox/switch reverses
changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed
markers to a labeled-row/Field task — flagged for the user in the handoff.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
status: implemented — the space builder (`buildSpaceScale`/`applySpacing`) landed, and the `applyTheme(seed)` capstone composes it (status corrected 2026-07-07, theming audit C)
source: migrated from src/uix/eidos/STRUCTURE_ENGINE_RFC.md (2026-07-02, docs-book F7.4)
---
# RFC — Eidos structural systems (space · density · scale)
> Sibling of [`rfc-color-engine.md`](./rfc-color-engine.md),
> [`rfc-typography.md`](./rfc-typography.md), [`rfc-depth.md`](./rfc-depth.md) and
> [`rfc-shape.md`](./rfc-shape.md). Takes the **structural** systems to
> reference-grade. Unlike the **expressive** channels (the book's 8), the
> structural is **state-only** — the stage, not the happening. So the novelty
> here is **not eventful**: it is **rhythm**, **fluidity** and **axis
> composition**, under the open cage.
## 0. Thesis
> **Space is not a lookup table of arbitrary pixels; it is a _rhythm_ — derived
> from a base unit, _fluid_ (it breathes with the viewport) and _composed_ with
> density and zoom from a minimal seed.**
Everybody ships a **flat** space scale (`4 · 8 · 12 · 16 · 24…`), **arbitrary** ,
**static** and detached from typography. Eidos already has the other two
structural axes —**density** (compactness) and **scaling** (zoom)— above the
average; what's missing is for **space itself** to be rhythm: modular, fluid and
with a runtime builder, as typography already did.
## 1. The survey — how the references do it and where they stop
| Framework | Space | Limit |
| ------------------------------------- | --------------------------- | ----------------------------------------------------------------------------- |
| **Tailwind** | fixed scale (`0.25rem` × N) | flat, arbitrary, **static** |
| **Material** | `8dp` grid | multiples of 8, static, no fluidity |
| **Radix / Chakra / Mantine** | space tokens | flat static scale; density (if any) = global preset |
| **Bootstrap / Ant / Carbon / Fluent** | spacer scale | same — flat + static |
| **Utopia.fyi** | fluid space (technique) | an **external calculator** , not a token system integrated with density + zoom |
**Common limit**: space is a **flat px scale** , **static** (doesn't breathe with
the viewport), **arbitrary** (derives from nothing), and **disconnected** from
density / zoom as a system. Utopia proved fluid space but as a spreadsheet, not
as a token engine.
## 2. Where Eidos stands today (strong on 2 of 3 axes)
- **Density** — 3 levels (`compact · comfortable · spacious`) × **2 axes**
(`spaceScale` + `controlScale` ). Tightens layout without touching text
legibility. ✓ (above average)
- **Scaling** — global zoom `90– 110` that scales the px **including typography**
(Radix parity), composing with density. ✓
- **Layout** — containers + padding + breakpoints + aspect-ratios. ✓
- **Composition** — `--space-{key}` is emitted as `calc(value ·
var(--density-space-scale) · var(--scaling))`: density × zoom already compose. ✓
- **BUT space ITSELF** (`STATIC_SPACE`) is **flat, arbitrary** px (base 4,
hand-placed half-steps), **static** (doesn't breathe) and **builder-less** —
unlike type, which has `buildTypeScale` (modular + fluid) + `applyTypeScale`
(runtime). It is the **lagging axis** .
## 3. The novel model — space as rhythm
1. **Modular** — every step = base unit × N (a coherent ladder), not loose px.
2. **Fluid** — `clamp()` : space **breathes with the viewport** (like fluid type —
almost no framework does it for space). Reuses the type scale's `fluidClamp` .
3. **Three orthogonal axes** — **rhythm** (the scale) × **density** (compactness)
× **scaling** (zoom), composed multiplicatively. One minimal seed governs them.
4. **Runtime builder** — `buildSpaceScale(seed)` (pure) + `applySpacing(seed)`
(DOM), sibling of `applyColorScheme` / `applyTypeScale` / `applyDepth` /
`applyShape` . Completes the quintet.
## 3.bis Structural = state-only (no two moments)
Unlike motion / depth / shape, space **does not "happen"** : it is the stage, not
the happening. The **two-moments** model (state vs event) belongs to the
**expressive** channels. Forcing an "eventful space" would be a costume — the
doctrinally honest position is that the novelty here is **rhythm + fluidity +
axis composition**, not eventfulness. (Same rigor: don't invent a moment that
doesn't exist.)
## 4. Doctrine — _strong default, open cage_
| Piece | Strong default | Open door |
| ---------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **space scale** | authored `STATIC_SPACE` (stable, curated) | `buildSpaceScale` / `applySpacing` = **modular + fluid opt-in** alternative (same stance as `applyTypeScale` over the authored scale) |
| **density** | 3 levels × 2 axes | config-driven + runtime (`[data-density]`) |
| **scaling** | `90– 110` , universal factors | runtime (`[data-scaling]`); composes with density |
| **composition** | `calc(value · density · scaling)` | the raw `--space-*` primitives always reachable; the builder **preserves** the composition |
| **whole system** | canonical theme | runtime `applySpacing(seed)` |
## 5. Token contract
```
--space-{key} value · var(--density-space-scale) · var(--scaling) (existing scale — kept)
```
The builder **rewrites the `value`** (managed block) with a modular/fluid one,
**preserving** the `calc(… · density · scaling)` so density and zoom keep
composing. Zero renames → zero breakage.
## 6. Phases
1. **Space builder** — `buildSpaceScale(seed)` (pure: base unit × ladder, fluid
via `fluidClamp` ) + `ActiveEidos.applySpacing` / `clearSpacing` (managed block
that preserves `· density · scaling` ) + export + test. Opt-in; `STATIC_SPACE`
untouched.
2. **Showcase + docs** — `/temas/estructura` (density × scaling × fluid space,
live) + theming reference §structure + this RFC.
3. ⏸️ (future) ** `applyTheme(seed)` ** — one seed composing type + space (shared
rhythm).
## 7. Composition with the existing
- **`fluidClamp`** (from the type scale) → fluid space (not reinvented).
- **`calc(value · density · scaling)`** → preserved (density + zoom keep composing).
- **`buildTypeScale`** → the exact pattern `buildSpaceScale` mirrors (seed →
fluid ladder).
- **Box/Flex/Grid/Stack/Container** → consume `--space-*` ; untouched.
## 8. Doctrine (parallel to color / typography / depth / shape)
- **Authored scale = stable canon**; the builder = **opt-in** mathematical
alternative (same as typography). The theme retunes, the builder recomposes.
- **Density and scaling = axes orthogonal** to rhythm; the three compose.
- **Open cage**: raw `--space-*` always one step away.
## 9. Out of scope
- **Rigid baseline grid** (pixel-perfect vertical rhythm): modular + fluid rhythm
gives cadence without imposing a rigid grid that fights real content.
- **Reinventing layout**: `Box · Flex · Grid · Stack · Container · AutoGrid`
already cover composition; here we elevate **space** , not the layout primitives.