8.6 KiB
| title | type | audience | status | source |
|---|---|---|---|---|
| RFC — The `scaling` axis (global zoom), separate from density | rfc | human + agent | implemented | migrated from src/uix/eidos/SCALING_RFC.md (2026-07-02, docs-book F7.4) |
RFC — The scaling axis (global zoom), separate from density
Status: ✅ IMPLEMENTED. (designed 2026-06-01 · implemented 2026-06-02) Born from P1-2: density-driven typography (
content-scale) was wrongly framed. Density and zoom are different things; this RFC introduces zoom as its own axis.Code:
lib/config-types.ts(SCALING_KEYS/ScalingKey/DEFAULT_SCALING/ScalingParticipationMap— the axis definition, added 2026-07-06),lib/primitives/static.ts(STATIC_SCALING+STATIC_SCALING_PARTICIPATION, the canonical per-family defaults),lib/render-css.ts(appendScalingDeclarations/appendScaledMetricDeclarations/renderScalingBlocks— the emitters consume the map, they never decide participation),active-eidos.svelte.ts(scaling/scalingSource/getScaling()/data-scaling). Doc consolidated intheming/reference.md§23.--density-scaleand--density-content-scalewere removed in this sprint.2026-07-06 — participation became model data. Which families the zoom multiplies is now DECLARED per element (
primitives.scaling, partial maps merge over the canonical defaults) instead of hardcoded in the emitters. This also reconciles an unrecorded drift: commit845d6579(2026-06-22) had made radius "scaling-responsive" against this RFC without updating any doc — radius participation is now canonicallyfalse(chrome family), and the original "as in Radix" justification below was corrected (Radix DOES scale its radius; crispness is our own rationale).
0. Motivation
scaling in Radix Themes (90/95/100/105/110%) is a global zoom that scales
space, typography, heights and line-box with ONE factor — accessibility/zoom, not
density. Eidos had a dead --density-content-scale (±4%) that tried to scale
typography under the density axis, which conflates density with zoom:
- Density (
compact/comfortable/spacious): tightens layout (space + control height), text stays stable (legibility — Material/Carbon). Already done. - Scaling (this RFC): zoom — scales the px metrics including typography.
The two axes are orthogonal and compose (you can have compact + 110%).
1. Decisions
- Discrete, Radix-class axis:
90 / 95 / 100 / 105 / 110(%). Default100. Projected as[data-scaling='90'](blocks, cacheable) +--scaling. - The axis is DEFINED by a per-element participation map (user decision
2026-07-06): every orthogonal scale family declares whether the zoom
multiplies it —
ScalingParticipationMap(config-types.ts), canonical defaults inSTATIC_SCALING_PARTICIPATION(primitives/static.ts). The generator derives ALL emission from the map; participation is model data (introspectable, guarded), never an emitter side effect. Canonical taxonomy:- METRIC families scale (what things occupy):
space,control-height,font-size,icon-size,blur(blur joined 2026-06-15, §29). - CHROME families stay crisp (what draws the edges):
radius,border-width,shadow— design-integer px under zoom. - Excluded outright:
line-height(a unitless ratio; it already scales via font-size — multiplying it would double the effect),z-index,opacity,motion(durations/eases;motion-distancedeclaresfalsein the map). - Corrected reference note: Radix Themes DOES scale its radius
(
calc(base × scaling × radius-factor)). Keeping radius crisp is OUR decision on our own rationale (chrome crispness + canonical axis semantics), not Radix parity. Radius keeps its own magnitude knob (--radius-factor) — roundness is themeable; zoom participation is a system-definition decision (config → regeneration), not a runtime theme knob. - Map boundaries: it governs DIRECT multiplication of raw px only — a
value referencing a participating token (a gap on
var(--space-*)) scales through the reference;shadowis declared for completeness but is structurally fixed (composite values can't wrap in onecalc()).
- METRIC families scale (what things occupy):
- Composition with density (multiplicative) on the 2 shared metrics:
--space-N: calc(base × var(--density-space-scale) × var(--scaling)),--control-height-N: calc(base × var(--density-control-scale) × var(--scaling)). - Prune
--density-scale(master) +--density-content-scale: they were the half-baked attempt at what this axis does properly. Reconciles §20.1 (density = stable text; scaling = zoom includes text).
2. Design
Primitive (lib/primitives/static.ts):
export const STATIC_SCALING = { '90': 0.9, '95': 0.95, '100': 1, '105': 1.05, '110': 1.1 };
Active default: 100.
Types (config-types.ts): SCALING_KEYS = ['90','95','100','105','110'],
ScalingKey. The density primitive loses scale + contentScale.
Emission (render-css.ts):
:rootdeclares--scaling-90: 0.9 … --scaling-110: 1.1(constants) and--scaling: var(--scaling-100)(active).- Blocks
[data-scaling='90'] { --scaling: var(--scaling-90); }… (the ≠100 ones). font-size+icon-sizemove from verbatim tocalc(value × var(--scaling)).space+control-heightadd× var(--scaling)to thecalcthey already have.line-height,radius,border,shadow, etc.: unchanged.
Runtime (active-eidos.svelte.ts): new scalingSource (get/onChange,
mirror of densitySource); apply() writes data-scaling on the host (next to
data-mode / data-density); dispose() cleans it up. EIDOS_SCALING_ATTR.
Contract (contract.ts): emits --scaling + the constants as knobs.
3. Conflicts (resolved)
| Point | Resolution |
|---|---|
| Density × scaling on space/control-height | Multiplicative composition in the calc. Orthogonal in intent. |
| line-height (ratio) | Do NOT scale — scales via font-size. |
| radius/border/shadow/z/opacity/motion | Do NOT scale — chrome/structural families per the participation map. (The original "Radix parity" claim was wrong: Radix scales its radius; this is our own crispness decision.) |
Dead --density-scale + content-scale |
Pruned — superseded by this axis. |
size prop |
Orthogonal — size picks the tier, scaling multiplies the tier's value. Composes. |
| Browser zoom | Composes multiplicatively (both px). App-level, distinct. |
| §20.1 (typography doesn't scale with density) | Still true — density doesn't scale it; scaling does. Two axes. |
4. Implementation plan
STATIC_SCALING+ removescale/contentScalefromSTATIC_DENSITY.- Types (
SCALING_KEYS/ScalingKey; adjustDensityPrimitiveSet). render-css: scaling emission + blocks +× var(--scaling)on the 4 families; prune density scale/content-scale emission.ActiveEidos:scalingSource+data-scaling+ cleanup.contract: scaling tokens; remove density scale/content-scale.- Regenerate
generated/base.css. - Tests: update the shape of
--space-4/--control-height(× scaling); scaled font-size test;[data-scaling]block;scalingSourcetest. - Verify (
check+vitest src/uix/eidos+ browser) + doc in theming reference.