You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/rfcs/rfc-scaling.md

7.3 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 in theming/reference.md §23. --density-scale and --density-content-scale were 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: commit 845d6579 (2026-06-22) had made radius "scaling-responsive" against this RFC without updating any doc — radius participation is now canonically false (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 (%). Default 100. 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 in STATIC_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-distance declares false in 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; shadow is declared for completeness but is structurally fixed (composite values can't wrap in one calc()).
  • 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):

  • :root declares --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-size move from verbatim to calc(value × var(--scaling)).
  • space + control-height add × var(--scaling) to the calc they 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

  1. STATIC_SCALING + remove scale/contentScale from STATIC_DENSITY.
  2. Types (SCALING_KEYS/ScalingKey; adjust DensityPrimitiveSet).
  3. render-css: scaling emission + blocks + × var(--scaling) on the 4 families; prune density scale/content-scale emission.
  4. ActiveEidos: scalingSource + data-scaling + cleanup.
  5. contract: scaling tokens; remove density scale/content-scale.
  6. Regenerate generated/base.css.
  7. Tests: update the shape of --space-4/--control-height (× scaling); scaled font-size test; [data-scaling] block; scalingSource test.
  8. Verify (check + vitest src/uix/eidos + browser) + doc in theming reference.

Powered by TurnKey Linux.