14 KiB
09 · Visual Design Language
Status: foundational design pass. This is the visual contract — the counterpart to
docs/spec/(behavior) and the rest ofdocs/architecture/(structure).styles/tokens.css,styles/base.css, component CSS Modules, andsrc/core/vega-themes.tsimplement 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 intostyles/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:
- Considered — remove everything gratuitous. No decoration that isn't carrying meaning. Whitespace is a feature.
- 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.
- Executed — everything communicates, including what we leave out. Alignment, rhythm, and empty space are decisions, not leftovers.
- Progressive — every 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:
- 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 | ~0–2px (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 Sansfor UI,IBM Plex Monofor 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-
--bgand text-on---accentmust both pass for any shipped theme.
3.4 Shape & elevation
--radius: 0for 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
--radiusdoesn'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,
--bordersubtle 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)inbase.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
--focusoutline (offset 1–2px). Always visible on keyboard focus — accessibility is non-negotiable (principle 4). - Fields (text, textarea, select, search): square, 1px
--border,--layer-01fill, 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-01fill, 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.categoryis 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 (§1–6) 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).