Files
astrolabe/docs/architecture/09-visual-design.md
T

11 KiB
Raw Blame History

09 · Visual Design Language

Status: foundational design pass. This is the visual contract — the counterpart to docs/spec/ (behavior) and the rest of docs/architecture/ (structure). styles/tokens.css, styles/base.css, component CSS Modules, and src/core/vega-themes.ts implement to this doc.

Companion: visual-specimen.html — a standalone, openable "kitchen sink" that renders every token and element with a live theme/accent switcher. Edit tokens there first, eyeball them, then port the settled values into styles/tokens.css.

Astrolabe's look is inspired by the IBM Design Language / Carbon, but Carbon is not a dependency — we transcribe the values we want and reinterpret the principles in our own words. We borrow IBM's engineered structure; we keep color and theming free.


1. Principles

IBM's four design principles map almost exactly onto Astrolabe's SOUL ("the spec is the star; the UI is a thin, considered shell"). Restated for us:

  1. Consideredremove everything gratuitous. No decoration that isn't carrying meaning. Whitespace is a feature.
  2. Unified — a small fixed kit (one type family, a neutral ramp, one accent, a handful of components) reused systematically. Identity comes from consistency, not novelty per screen.
  3. Executedeverything communicates, including what we leave out. Alignment, rhythm, and empty space are decisions, not leftovers.
  4. Progressiveevery element reduces friction. If it doesn't help the user read, edit, or find a snippet faster, it doesn't earn its place.

…plus our own, where we part ways with IBM:

  1. Structure is rigorous; color is free. The grid, type scale, spacing, and square geometry are systematic and fixed. Color, accent, and theming are the expressive layer — open, swappable, and meant to be played with.

2. Deliberate divergences from Carbon

What we borrow vs. where we diverge — recorded so future readers know these were choices, not drift:

Topic IBM/Carbon Astrolabe
Adoption A framework + component lib Inspiration only. Transcribed tokens, our own components
Structure (grid, type, spacing) 8px mini unit, modular type scale Borrowed wholesale — it's the rigorous part worth having
UI chrome corners ~02px (near-square) Fully square, radius: 0 — one notch more austere/engineered
Icons Rounded exteriors, 2px soft corners + 90° interiors Kept rounded (use Carbon's icon set) — the one warm, human touch
Color "Blue at the core"; other hues only for purpose Dropped. Color/theming is free and expressive; accent is a token, many themes welcome
Neutrals Carbon gray ramp Borrowed — accessible, well-tuned, a good legible base
Motion Productive vs. expressive Productive only — subtle, purposeful, reduced-motion-aware

3. Tokens

All tokens are CSS custom properties on :root, themed by overriding them on [data-theme] (and, for accent, [data-accent]). As of M1.5 the settled values live in styles/tokens.css; the specimen remains the sandbox for trying new tokens/themes before porting them across.

3.1 Typography — IBM Plex

  • Families: IBM Plex Sans for UI, IBM Plex Mono for the editor, code, numeric/tabular data, and inline spec fragments. Self-hosted in production via @fontsource/ibm-plex-sans + @fontsource/ibm-plex-mono (offline/PWA — never a CDN). The specimen uses a CDN purely for preview convenience.
  • Scale (px), from Carbon's modular scale: 12 · 14 · 16 · 18 · 20 · 24 · 28 · 32 · 42. Body is 14/20 (already our --font-size-base). Captions/labels 12.
  • Weights: 400 regular, 600 semibold for emphasis/headings; 300 light reserved for large display only.
  • Breathing room: Plex "requires space to breathe." Don't over-tighten — body line-height ≥ 1.4, default tracking (no negative letter-spacing on text). Flush-left, clear hierarchy.

3.2 Spacing — the 8px base unit

IBM's product/web rule: "the 8px mini unit guides everything." Every gap, pad, and size is a relationship of 8 (with 2/4 as fine sub-steps):

--space-1: 2px · --space-2: 4px · --space-3: 8px · --space-4: 12px · --space-5: 16px · --space-6: 24px · --space-7: 32px · --space-8: 48px · --space-9: 64px.

Note: this renumbers our current M0 scale to anchor on 8. The migration is mechanical (search/replace --space-* usages) and lands with the design pass.

3.3 Color — role-based, theme-free

Color is expressed as roles, never raw hexes, so themes can repaint the whole UI by swapping one set of values. Borrowed from Carbon's layering model:

Role token Meaning
--bg App canvas (lowest layer)
--layer-01 / --layer-02 Raised surfaces (panels, cards, popovers) — elevation by lightness step, not shadow
--border / --border-strong Subtle and prominent separators
--text / --text-secondary / --text-placeholder Text hierarchy
--accent / --accent-hover / --accent-contrast The expressive accent — swappable; UI must never hardcode a hue
--focus Focus-ring color (defaults to --accent)
--support-error / -success / -warning / -info Status only — color = meaning
  • Neutrals use the Carbon gray ramp (#f4f4f4 … #161616) — accessible and legible. Accent and theming are open: the specimen ships several accents (indigo, teal, amber, rose) and light/dark themes to prove the system is free, not blue-bound. Pick, add, or invent themes freely.
  • Status palette (borrowed, stable): error #da1e28, success #198038, warning #f1c21b, info #0043ce — tuned per theme for contrast.
  • Contrast: target WCAG AA (4.5:1 text, 3:1 large/UI). Accent-on---bg and text-on---accent must both pass for any shipped theme.

3.4 Shape & elevation

  • --radius: 0 for all chrome (buttons, fields, cards, panels). Square is the look.
  • Icons are exempt — they keep their rounded geometry (Carbon icon set, 2px corners). Icons are SVG, not chrome, so --radius doesn't touch them.
  • Elevation is lightness, not shadow. Stack --bg → --layer-01 → --layer-02. Shadows, if ever used, are minimal and reserved for true overlays (modals, popovers).
  • Borders are 1px, --border subtle by default.

3.5 Motion

  • Durations (productive): --dur-fast: 70ms, --dur-fast-2: 110ms, --dur-moderate: 150ms. Nothing slower in the core UI.
  • Easing: standard productive cubic-bezier(0.2, 0, 0.38, 0.9).
  • Restraint: animate only what's vital (state changes, entrances of meaningful elements). No gratuitous motion. All transitions are already neutralized under @media (prefers-reduced-motion: reduce) in base.css.

4. Component conventions

  • Buttons: square, 32px (compact) / 40px (default) tall. Variants: primary (filled --accent), secondary (bordered), ghost (text-only), danger (filled --support-error). 600-weight label. Clear hover/active and a visible focus ring.
  • Focus ring: a 2px --focus outline (offset 12px). Always visible on keyboard focus — accessibility is non-negotiable (principle 4).
  • Fields (text, textarea, select, search): square, 1px --border, --layer-01 fill, accent border + focus ring on focus. Mono font for spec/JSON inputs.
  • List rows (snippet library): compact, full-row hover (--layer-01), active row marked by an accent left-border + --layer-01 fill, secondary metadata in --text-secondary. Row-level actions reveal on hover.
  • Status indicators: a small dot/tag for draft vs. published; a dataset glyph when references exist. Status colors only.
  • Toasts: --layer-02, 1px border in the support color, square, brief.
  • Code / editor surfaces: --font-mono, --layer-01, generous line-height.

5. Charts (src/core/vega-themes.ts)

The chart Config is themed to match the app, per theme:

  • background: transparent (inherits the surface), Plex font for titles/labels, axis/grid colors derived from the neutral ramp + --text-secondary.
  • Categorical palette for range.category is part of the free color layer — a distinct, colorblind-sequenced set (Carbon's data-viz palette is a good starting point, but not mandatory). Light and dark variants. This is where expressive color earns its keep.
  • Config is applied at embed time, never baked into the user's stored spec.

6. Implementation map

Artifact Role
visual-specimen.html Living preview + token sandbox. Iterate here first
styles/tokens.css The settled tokens — ported from the specimen in M1.5
styles/base.css Font wiring (@fontsource), reset, reduced-motion
component *.module.css Consume tokens only; no raw hexes, no hardcoded hue
src/core/vega-themes.ts Chart Config per theme; categorical palettes

Order of work: settle the specimen → port tokens to tokens.css → self-host Plex in base.css → restyle existing M1 components against the tokens → align vega-themes.ts. Verify by rendering the real app, not just the specimen.


7. Inspiration sources — where to look for more

We treat IBM/Carbon as inspiration, so we mine its source repos, not the live doc sites. The sites (carbondesignsystem.com, ibm.com/design/language) are JS-rendered and don't fetch cleanly — clone the repo and read it locally instead. Convention: clone under /Users/oleh/code/reference/ with git clone --depth 1 https://github.com/carbon-design-system/<repo>.git.

Need Repo Where it lives
Principles / the "why" (philosophy, 2x grid, color rationale, type, motion, icon geometry) design-language-website src/pages/: philosophy/principles.mdx, 2x-grid.mdx, color.mdx, typography/*.mdx, animation/overview.mdx, iconography/ui-icons/design.mdx (~1.4 GB clone — image-heavy; the MDX is what we want)
Token values (gray/blue ramps, type scale, font families, motion durations/easings, theme role→value maps) carbon packages/colors/src/colors.ts, packages/type/src/{scale,fontFamily,fontWeight}.ts, packages/motion/src/index.ts, packages/themes/src/{white,g100}.ts
Component-level usage guidance carbon-website src/pages/**/*.mdx
Data-viz categorical chart palette (for vega-themes.ts range.category) carbon-charts cloned in M1.5 → packages/core/scss/_color-palette.scss (the '14' pairing, white + g100); token→hex resolved against carbon packages/colors/src/colors.ts

The decisions we made from these sources are captured above (§16) and in the specimen, so we don't need to re-derive them — only return to the repos to extend the research (e.g. the chart palette, or a component pattern we haven't tackled).