TED OS Grid System v2 — Foundation Spec
canonical· designer · updated 2026-06-17 · source
Issue #360 (repeals the φ-based version of #277/#356 — git is the archive). Visual source of truth: Paper, page "Grid System v2" (slide deck) — https://app.paper.design/file/01KS9HJRSXFAP5FART7R5Y07CM Citable directives for agents:
.claude/rules/grid-system.md. Written by José 2026-06-12; conflict reconciliation signed off the same day (§13).
0. Principles
- The grid is the rule. Every widget falls on the grid lines by construction (subgrid), never "by eye".
- Base-8, no φ. All spacing (gap, padding, row module) is a multiple of 8. The golden ratio is repealed in spacing, typography, and proportions.
- Anti-stretch. On wide screens the system gains columns and adds more widgets; it does not inflate the existing ones. Groups may expand a little (with a cap) to fill, never stretch without control.
- Context always. No widget floats loose: every widget lives inside a group (concern). At any size it renders by groups.
- Color with intention. The board is mostly neutral; color appears only when the data calls for it.
Scope: universal foundation for TED OS — apps/admin is the first validation field.
This spec governs the widget canvas; the shell (sidebar/header) is a separate spec (§13).
1. Base unit (aspect ratio)
- Square unit. Row height equals column width:
--row = column_width. A 1×1 widget is a true square, 1×2 is 1:2 portrait, 2×1 is 2:1 landscape. --gap = 16px,--pad = 16px(base-8).- Target column width: ~108px (design parameter; defines how many columns fit).
--gap: 16px; /* gutter */
--pad: 16px; /* canvas margin */
--cell: 108px; /* target column width (anti-stretch) */
--row: /* = computed column width → square unit */;
2. X axis — the invariable rule (columns)
Variable columns by width. The number of columns is computed from the target width, not from fixed breakpoints:
cols = clamp(4, floor((innerWidth + gap) / (cell + gap)), 20)
Approximate reference: Phone 4 · Tablet 6 · Laptop 10 · Desktop 12 · Ultra-wide / TV 20.
- More screen = more columns, keeping the widget's target width. Content is never centered nor capped.
overflow-xforbidden on html, body, canvas, and bands (overflow-x: clip).
3. Y axis — growth (rows and bands)
- The canvas grows downward.
overflow-yallowed (scroll-y); the top origin never moves. - Band = one packed row of groups. Bands stack in order of
priority / volatility:
- Constant (origin, never move) →
- Live (changes often) →
- Fresh (grows daily).
- Band height = the height of the tallest group it contains. Vogel philosophy: the angle doesn't change, only the count grows.
4. Groups (concerns) — first-class citizens
Each group is a subgrid in X and Y aligned to the main grid. Group contract:
| Property | Meaning |
|---|---|
gs / gsMax | preferred / maximum width in columns |
gr / grMax | preferred / maximum height in rows |
priority | packing order (volatility) |
hue | group color identity (§7) |
Rules:
- The group is contiguous and cohesive (subgrid on both axes) — its widgets stay aligned to the grid by construction.
- Each group declares a
flexwidget (full height) that absorbs the extra width, and avbottomwidget (bottom) that absorbs the extra height. - Move-in-block vs. reorder-internal by priority remain the two modes of manipulation.
5. 2D packing and filling
- Pack the groups into rows in priority order (greedy), respecting the available width.
- Balance: avoid the last band ending up with a single group if the previous one has 3+ (drop one down to even it out).
- Expand in X (a little): distribute each band's remainder among its groups up to
gsMax; the extra width is absorbed by theflexwidget. - Expand in Y: the shorter groups of a band stretch to the band height; the
extra height is absorbed by
flex+vbottom, leaving the band as a clean rectangle.
The result: nothing stretches without control, no widgets are left loose, and the extreme
remainder on very wide screens stays as editorial air (lever: gsMax / grMax).
6. Separation of concerns — at most one track, and only when it applies
Separation uses the grid, the same on both axes:
- X (between groups) = one grid column. Appears only on screens that allow it
(
cols ≥ 12). At most 1. - Y (between bands) = one grid row. At most 1, and only if it doesn't cause scroll: if adding the separator row overflows the content past the visible height, the separation doesn't exist.
- Both carry the Azafrán seed as the boundary mark.
Hard rule: never more than one track per gap, and separation is never used as filler.
7. Color — group + attention
Reconciled 2026-06-12 (José): the hex palette the spec draft carried is ignored — it was written without context of the color system and does not change the current color spec (
color-scope.md,admin-design-system.md). What survives from this section is the structure:
- Hue = group identity. Hue values are assigned by the Designer from the existing token system — this spec introduces no new hex.
- Cian never as a group hue — it is the admin's accent (
color-scope.md, signed 2026-06-11) and stays intact. - Intensity = data attention. Over the group's hue, saturation/alpha rises with attention: neutral → notable → high.
- A single "active" Azafrán seed per group (max. 1 accent per group). Azafrán = the current brand token.
- Semantic states inherited from the existing tokens: positive (
success), negative (danger), date-urgent (e.g. SAT due).
8. Widget anatomy
Aspect ratio matters: tall items (heroes, portraits) and clusters of squares (KPIs) coexist.
- Subtle background in the group's color (intensity by attention).
- Image / PNG with transparency as a hint in heroes.
- Icon top-left · text bottom-left.
- Full-clickable widget (the entire surface is interactive; hover at any point).
- Space between items = base-8 gutter (16px), constant across the whole grid.
9. Overflow
| Axis | Rule |
|---|---|
| X | Forbidden. overflow-x: clip on html/body/canvas/band. |
| Y | Allowed. The canvas grows and scrolls; dense feeds scroll within. |
10. Radii (R1–R5)
| Token | Use | Value | CSS token |
|---|---|---|---|
| R1 | region / group | 14px | rounded-xl |
| R2 | widget / card | 10px | rounded-lg |
| R3 | control | 8px | rounded-md |
| R4 | chip | 6px | rounded-sm |
| R5 | pill | full | rounded-pill |
Concentric stays: inner = outer − padding; the span never changes the radius.
11. Typography
- No φ scale. Steps in base-8 / legible multiples (12 · 14 · 20 · 24 · 32 · 48),
adjustable with
clamp()by density. Repeals the 12·20·36·60·96 scale (2026-06-11). - Tighten the 13–16px body band to ≤ 3 tiers. Across the 13–16px range pick at most three distinct sizes (e.g. 12 / 14 / 16) — never a smear of 13/14/15/16 near-duplicates; collapse adjacent near-sizes to the nearest scale step.
- Display: Geist · UI/data: Geist Mono (tabular for numbers).
Implementation note: matches the admin's shipped fonts (Geist / Geist Mono, set on
<html>bynext/fontinlayout.tsxand resolved through the--font-sans/--font-monotokens). Corrects the earlier Space Grotesk / JetBrains Mono drift (#492).
12. Anti-pattern (what the system avoids)
The typical repeat(auto-fit, minmax(X, 1fr)) grid that stretches elements on wide monitors.
The group model exists precisely to guarantee a designed composition: target
width + bounded expansion + bands, instead of uniform stretching.
13. Reconciliation with previous decisions (signed 2026-06-12)
| Topic | Resolution |
|---|---|
| φ (spacing, type, proportions, 8+4/7+5 splits) | Repealed entirely |
| Fixed 12 columns + canonical spans | Repealed — variable columns 4…20 by --cell |
| 64px row module (slide 13) | Repealed — square unit --row = column width |
Gutter/margin clamp() 16→40 / 24→80 | Repealed — --gap/--pad fixed at 16px |
| v2 draft hex palette + "avoid cian" | Ignored — the current color system doesn't change; Cian stays as the admin's accent (color-scope.md intact); only the hue/intensity/seed structure survives (§7) |
Shell: sidebar w-18 md:w-60 2xl:w-80 + 64px header | Survives as a separate spec (no φ justification); this spec governs only the canvas |
--gs-scale rem zoom 0.9/1.0/1.15 (ex-G16, signed 2026-06-11) | Retained — orthogonal to φ; pending José's re-reading |
| DnD/keyboard model (ex-G17, signed 2026-06-11) | Retained adapted — snap to slots of the variable grid and to the square unit |
Animate only transform/opacity (GSAP power3.inOut) | Retained (engineering rule, not φ) |
| A11y gate (AA, focus-visible, tone+label+shape, reduced-motion) | Retained |
| Brand asymmetry (1 top-left corner, seed in empty/hero) | Retained — doesn't contradict v2 |
| Demo copy tone (dashboard, not HUD) | Retained (§ tone — Linear/Stripe board, not HUD) |
Operational summary
X axis → target-width columns (4…20), anti-stretch, overflow-x forbidden.
Y axis → bands by volatility, canvas grows, scroll-y allowed.
Group → 2D subgrid, context always, bounded expansion in X (flex) and in Y (vbottom).
Sep → max. 1 track: X = column (cols≥12); Y = row (only if it doesn't cause scroll).
Color → hue = group, intensity = attention, 1 Azafrán seed per group (current tokens).
Widget → square base aspect ratio, subtle background + image/icon/text, full-clickable.