Skip to main content

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 OSapps/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-x forbidden on html, body, canvas, and bands (overflow-x: clip).

3. Y axis — growth (rows and bands)

  • The canvas grows downward. overflow-y allowed (scroll-y); the top origin never moves.
  • Band = one packed row of groups. Bands stack in order of priority / volatility:
    1. Constant (origin, never move) →
    2. Live (changes often) →
    3. 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:

PropertyMeaning
gs / gsMaxpreferred / maximum width in columns
gr / grMaxpreferred / maximum height in rows
prioritypacking order (volatility)
huegroup 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 flex widget (full height) that absorbs the extra width, and a vbottom widget (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

  1. Pack the groups into rows in priority order (greedy), respecting the available width.
  2. Balance: avoid the last band ending up with a single group if the previous one has 3+ (drop one down to even it out).
  3. Expand in X (a little): distribute each band's remainder among its groups up to gsMax; the extra width is absorbed by the flex widget.
  4. 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

AxisRule
XForbidden. overflow-x: clip on html/body/canvas/band.
YAllowed. The canvas grows and scrolls; dense feeds scroll within.

10. Radii (R1–R5)

TokenUseValueCSS token
R1region / group14pxrounded-xl
R2widget / card10pxrounded-lg
R3control8pxrounded-md
R4chip6pxrounded-sm
R5pillfullrounded-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> by next/font in layout.tsx and resolved through the --font-sans / --font-mono tokens). 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)

TopicResolution
φ (spacing, type, proportions, 8+4/7+5 splits)Repealed entirely
Fixed 12 columns + canonical spansRepealed — variable columns 4…20 by --cell
64px row module (slide 13)Repealed — square unit --row = column width
Gutter/margin clamp() 16→40 / 24→80Repealed--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 headerSurvives 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.