Skip to main content

Sizing

Sizing tokens define the physical bounds of UI: widths, heights, and min/max constraints used to build interfaces that are consistent, accessible, and natively responsive.

This system is built on two explicit layers:

  1. Core Tokens — intent-free primitives and the responsive engine
  2. Semantic Tokens — sizing contracts consumed by UI code

Rule: Core tokens are never referenced in components.


Core Tokens

Core sizing tokens are intent-free primitives and the single source of truth for responsiveness.

They exist to:

  • centralize fluid logic (all clamp() formulas live here)
  • keep semantic tokens as aliases (stable names, theme-tunable engine)
  • make the system predictable (no ad-hoc coefficients in semantics)

Core tokens are never consumed by components. Components consume semantic tokens only.

Output Guidance (Web)

The following is specific to CSS/web output and does not affect the semantic contract.

  1. A query container rule for layout surfaces:
.tt-container {
container-type: inline-size;
}
  1. A robust fallback strategy in build output:
  • emit a viewport-safe fallback first
  • override with container units when supported
@supports (width: 1cqi) {
/* container-based overrides */
}
  1. The core token set below (ramps + primitives).

Core groups

1) Fluid ramps (required)

Ramps are the engine. They are bounded ranges expressed with clamp(min, preferred, max).

  • core.sizing.ramp.ui.1..8 — small→medium objects such as icons and identity
  • core.sizing.ramp.layout.1..6 — medium→large structural bounds such as surfaces

Rule: semantic fluid tokens (icon.*, identity.*, surface.maxWidth) should map to ramp steps, not define new formulas.

2) Primitives (required)

  • core.sizing.relative.em = 1em
  • core.sizing.relative.rem = 1rem
  • core.sizing.behavior.auto = auto
  • core.sizing.behavior.full = 100%
  • core.sizing.behavior.fit = fit-content
  • core.sizing.behavior.min = min-content
  • core.sizing.behavior.max = max-content
  • core.sizing.viewport.height.full = 100dvh
  • core.sizing.viewport.width.full = 100dvw

3) Ergonomic hit primitives (required)

  • core.sizing.hit.fine — the single ergonomic floor for fine pointer (mouse, trackpad); may be fluid via clamp(floor, preferred, max) where floor is a fixed px minimum
  • core.sizing.hit.coarse — the single fixed px floor for coarse pointer (touch); never fluid

hit is one value per pointer profile, not a scale — the theme's single ergonomic minimum for an interactive target (ADR-020). Coarse is always fixed px — reliable ergonomic guarantees for touch. Fine may use clamp(floor, preferred, max) where floor is a fixed px ergonomic minimum, so the rem preferred respects user font-size while accessibility is always guaranteed. The build output emits the fine value as the baseline and the coarse value inside @media (any-pointer: coarse) automatically.

Example

const coreSizing = {
sizing: {
ramp: {
ui: {
1: 'clamp(12px, calc(0.6cqi + 10px), 16px)',
2: 'clamp(14px, calc(0.8cqi + 11px), 20px)',
3: 'clamp(16px, calc(1.0cqi + 12px), 24px)',
4: 'clamp(20px, calc(1.2cqi + 14px), 32px)',
5: 'clamp(24px, calc(1.5cqi + 16px), 40px)',
6: 'clamp(32px, calc(1.8cqi + 20px), 56px)',
7: 'clamp(40px, calc(2.2cqi + 24px), 72px)',
8: 'clamp(48px, calc(2.6cqi + 28px), 96px)',
},

layout: {
1: 'clamp(320px, 40cqi, 480px)',
2: 'clamp(384px, 50cqi, 640px)',
3: 'clamp(480px, 60cqi, 800px)',
4: 'clamp(560px, 70cqi, 960px)',
5: 'clamp(640px, 80cqi, 1120px)',
6: 'clamp(768px, 90cqi, 1280px)',
},
},

relative: {
em: '1em',
rem: '1rem',
},

behavior: {
auto: 'auto',
full: '100%',
fit: 'fit-content',
min: 'min-content',
max: 'max-content',
},

viewport: {
height: {
full: '100dvh',
},
width: {
full: '100dvw',
},
},

hit: {
// A single ergonomic floor per pointer profile (ADR-020).
// Fine: clamp(floor, preferred, max) — floor is fixed px; preferred scales
// with rem (not cqi), so a control's height never grows with the window.
// Tuned desktop-first at 32px (GitHub/Linear ~32, Stripe ~36).
fine: 'clamp(32px, 2rem, 36px)',
// Coarse: always fixed px — touch ergonomics require a predictable,
// reliable target. 48px sits above the 44px Apple HIG floor.
coarse: '48px',
},
},
};

Expected consumption pattern: semantic tokens reference core tokens by alias. Example: icon.md → core.sizing.ramp.ui.3, surface.maxWidth → core.sizing.ramp.layout.5.

Semantic Tokens

Sizing semantics are anchored in geometry and ergonomics, not UX categories. This avoids ambiguity and prevents token-per-component drift.

Token structure

{family}.{stepOrProperty}
  • family: what kind of physical sizing contract this is
  • stepOrProperty: the specific step or property inside that family

Families

{family}Description
hitthe single minimum interactive target floor (ergonomic contract). One value per pointer profile; adapts to input capability. Fine may be fluid with clamp() (floor must be fixed px). Coarse is always fixed px.
iconvisual glyph sizing only. May be fluid via core ramp.
identityvisual identity object sizing (profile / brand / entity). May be fluid via core ramp.
measurereadability measure (line-length contract, character-based).
surfacestructural bounds for UI surfaces (constraints, not components).
viewportviewport primitives for full-height and full-width layouts.

Canonical shapes

  • hit
  • icon.{sm|md|lg}
  • identity.{sm|md|lg|xl}
  • measure.reading
  • surface.maxWidth
  • viewport.height.full
  • viewport.width.full

Semantic Tokens Summary Table

tokenuse when you are building…contract (must be true)default value
hitany interactive target (buttons, inputs, toggles, list rows)minimum interactive area; not visual size; must not shrink; enforce via min-*theme-defined ergonomic floor
icon.smsmall glyphs / dense UIvisual only; bounded range via core rampcore.sizing.ramp.ui.2
icon.mdstandard iconsvisual only; bounded range via core rampcore.sizing.ramp.ui.3
icon.lgprominent iconsvisual only; bounded range via core rampcore.sizing.ramp.ui.4
identity.smcompact identity objectsvisual only; bounded range via core rampcore.sizing.ramp.ui.5
identity.mdstandard identity objectsvisual only; bounded range via core rampcore.sizing.ramp.ui.6
identity.lgprominent identity objectsvisual only; bounded range via core rampcore.sizing.ramp.ui.7
identity.xlhero identity / brand objectsvisual only; bounded range via core rampcore.sizing.ramp.ui.8
measure.readinglong-form text containerssingle bounded readability contractclamp(45ch, 60ch, 75ch)
surface.maxWidthcards, panels, dialogs, surface shellsbounded structural max width; container-firstcore.sizing.ramp.layout.5
viewport.height.fullfull-height layoutsmust use dynamic viewport units; use intentionally for full-height layoutscore.sizing.viewport.height.full
viewport.width.fullfull-width layoutsmust use dynamic viewport units; use intentionally for full-width layoutscore.sizing.viewport.width.full

Accessibility note: WCAG 2.2 Target Size (Minimum) defines a lower baseline of 24×24 CSS px, with exceptions. ttoss recommends stronger ergonomic baselines, especially for coarse pointer environments, while allowing themes to tune values based on product needs.

Hit target adaptation

hit defines an ergonomic contract, not a fixed pixel value.

Implementations should adapt hit targets based on input capability:

  • fine pointer (mouse, trackpad) → more compact targets
  • coarse pointer (touch) → larger targets

The build output handles this automatically. Fine values are emitted as the baseline; coarse values are injected based on input capability detection.

Output Guidance (Web)

In CSS output, the fine value is the baseline and the coarse value is injected inside @media (any-pointer: coarse):

:root {
--tt-sizing-hit: clamp(
32px,
2rem,
36px
); /* fine baseline — fluid via rem, ergonomic floor guaranteed */
}

@media (any-pointer: coarse) {
:root {
--tt-sizing-hit: 48px; /* touch override — fixed px, always reliable */
}
}

The semantic token remains stable (hit). The runtime adapts the value.

Control geometry adapts to user font (rem) — never to the container (cqi), which is reserved for layout spacing/sizing — so a control's height never grows just because the window is wider. See ADR-020.

Rules of Engagement (non-negotiable)

  1. Hit vs visual: never use icon.* or identity.* as hit targets; always enforce hit via min-width / min-height.
  2. Reading vs surface: use measure.reading for long text; use surface.maxWidth for structural wrappers.
  3. No responsive logic in components: responsiveness lives in Core (ramps + container units), not in component code.
  4. Dynamic dimensions: avoid 100vh and 100vw; use viewport.height.full and viewport.width.full.

Theming

Themes may tune:

  • the core ramps (core.sizing.ramp.ui.*, core.sizing.ramp.layout.*)
  • surface.maxWidth mapping to a different layout ramp step
  • measure.reading in rare cases, validated with real content
  • core.sizing.hit.fine to tune the ergonomic floor for mouse; may use clamp(floor, preferred, max) where floor is fixed px
  • core.sizing.hit.coarse to adjust the ergonomic floor for touch; always fixed px

Semantic token names never change across themes.


Validation

Errors (validation must fail when)

  • hit.coarse resolves to a fluid or intrinsic value, including clamp(...), cqi, %, auto, or content-sizing keywords

  • hit.fine uses clamp() without a fixed px floor (the minimum bound must be a literal Npx value, not a variable or formula)

  • the coarse-pointer hit floor is smaller than the fine-pointer floor:

    • coarse < fine (compare coarse fixed px to fine clamp floor)
  • measure.reading is not a bounded character-based measure

  • generated output does not emit fine-pointer hit values as the baseline and coarse-pointer hit values inside @media (any-pointer: coarse)

  • generated output does not emit a viewport-safe fallback before container-based overrides

  • generated output does not gate container-based overrides behind @supports (width: 1cqi)

  • generated output emits viewport.height.full as vh instead of dynamic viewport units

  • generated output emits viewport.width.full as vw instead of dynamic viewport units

Warning (validation should warn when)

  • any icon.* token resolves outside core.sizing.ramp.ui.*

  • any identity.* token resolves outside core.sizing.ramp.ui.*

  • surface.maxWidth resolves outside core.sizing.ramp.layout.*

  • viewport.height.full does not resolve to core.sizing.viewport.height.full

  • viewport.width.full does not resolve to core.sizing.viewport.width.full

  • a resolved hit value (fine or coarse) is below 24px

  • adjacent icon.* tokens resolve to the same effective value

  • adjacent identity.* tokens resolve to the same effective value

  • the fine-pointer and coarse-pointer hit floors resolve to the same value

  • measure.reading and surface.maxWidth resolve to the same effective value


Summary

  • Core sizing defines primitives plus the responsive ramps
  • Semantic sizing defines a small set of stable geometry contracts
  • hit is ergonomic, not visual
  • icon.* and identity.* are visual, not interactive
  • measure.reading and surface.maxWidth solve different problems
  • Responsiveness lives in the core engine, not in components
  • The system stays small, predictable, and scalable