|
|
---
|
|
|
title: RFC — The `scaling` axis (global zoom), separate from density
|
|
|
type: rfc
|
|
|
audience: human + agent
|
|
|
status: implemented
|
|
|
source: 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`),
|
|
|
> `lib/primitives/static.ts` (`STATIC_SCALING`), `lib/render-css.ts`
|
|
|
> (`appendScalingDeclarations` / `appendScaledMetricDeclarations` / `renderScalingBlocks`),
|
|
|
> `active-eidos.svelte.ts` (`scaling` / `scalingSource` / `getScaling()` / `data-scaling`).
|
|
|
> Doc consolidated in [`theming/reference.md`](../theming/reference.md) §23.
|
|
|
> `--density-scale` and `--density-content-scale` were removed in this sprint.
|
|
|
|
|
|
## 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 parity: `90 / 95 / 100 / 105 / 110` (%). Default `100`.
|
|
|
Projected as `[data-scaling='90']` (blocks, cacheable) + `--scaling`.
|
|
|
- **Scales** (px): `font-size`, `space`, `control-height`, `icon-size`.
|
|
|
- **Does NOT scale**:
|
|
|
- `line-height` → it is a **unitless ratio** (1.45…); it already scales via the
|
|
|
scaled font-size. Multiplying it would **double** the effect (bug). Exclude.
|
|
|
- `border-width` (crisp 1px), `radius` (independent — as in Radix),
|
|
|
`shadow`, `z-index`, `opacity`, `motion`. Exclude.
|
|
|
- **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`):
|
|
|
```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 (Radix parity + correctness). |
|
|
|
| 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.
|