Files
design-v3/design-system-v3.md
T
Joshua KingandClaude Opus 5 d9ed1898b9 Add the Extrudex Design System v3 bundle
Tokens (light + dark, neumorphism, 54/54 WCAG AA contrast pairs), the
self-hosted JetBrains Mono fonts, and interactive mockups of every screen
across three iterations. Iteration 3 is final: phone, tablet and desktop
tiers, device preview, and "empty spool weight" terminology. The build
scripts regenerate the screens byte-identically from source.

Implementation is tracked in Maestro feature UI Refresh - V3.0
(224b72a1-8d84-4970-97ea-5dafa4d8ea3b).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:31:50 -04:00

17 KiB
Raw Blame History

CubeCraftLabs Design System v3 (proposal): Soft UI

Status: proposal, not adopted. Nothing in the Extrudex repository has changed. Base: Design System v2.0 as served at http://pages.ccl/design-v2.0/, verified on 2026-09-18 to match web/src/theme.css on dev (321ce0d) token for token. Tokens: design-system-v3.css · Fonts: fonts/ (JetBrains Mono 400/500/700/800 woff2 from @fontsource/jetbrains-mono 5.3.0, the same package the app imports; OFL license included).


1. Principles

  1. The v2.0 palette stays. Charcoal, violet accent, green and red are unchanged. Every v2.0 token name still exists and still resolves. New colors are tints of v2.0 hues, except for two new status hues (warning, info) that v2.0 lacked.
  2. Shadow is decoration, never structure. Every button, input, select, toggle and checkbox has a border at 3:1 or better against its surface (WCAG 1.4.11). Turn the shadows off and every control is still findable.
  3. No state relies on shadow direction alone. See the state matrix in §4.
  4. Offline-identical. One self-hosted font. No CDN, no remote asset, no @import. The mockups embed the font as base64.
  5. Numbers stay square. Tables, code and numeric columns keep --radius-none. Soft corners belong to containers and controls, not data.

2. Two v2.0 rules this proposal overturns (deliberately)

v2.0 rule v3 Why
"Radius: the brand is square. 0 is the rule." A 0–26 px radius scale Neumorphism reads as extruded material. A soft raised shape on a square corner looks like a rendering error, not a style. --radius-none stays for tables, code and grid lines.
"Shadows: flat brand. Only a faint lift where truly needed." A four-level raised/inset shadow system Soft UI is the shadow system. The accessibility cost of that is paid by §1.2 and §4.

DECISIONS.MD says "do not edit a token to suit a component". v3 doesn't edit v2.0 tokens: it adds new ones and remaps the alias layer. The two changes above are brand-level decisions for you to accept or reject, and they're listed again in SUMMARY.md.


3. Every token that changed or was added compared to v2.0

Unchanged (kept verbatim): the whole --ccl-* base scale and its aliases, --font-*, --fw-*, --fs-*, --lh-*, --tracking-*, --space-*, --ease-out, --dur-fast, --dur-base, --scrollbar-width, --radius-none, --shadow-none, --border-width.

3a. Base scale: added

Token Value Reason
--ccl-gold-light #a99af0 v2.0's accent #8f7ae0 is 3.99:1 as text on charcoal (fails 4.5:1), and v2.0's own ink-on-accent button text is 4.32:1 (fails). A lighter step of the same hue passes both: 5.67:1 as text and 6.14:1 under ink.
--ccl-violet-deep #5b43b8 The accent for the light theme. #8f7ae0 on a light surface is about 2.6:1.
--ccl-charcoal-inset #232327 Neumorphic wells (inputs, tracks) need a step between page #2c2c30 and code #1b1b1e. Code black inside a well looked like a hole.
--ccl-amber #e8c170 v2.0 has no warning color. "Running low", "tare estimated" and "duration is an upper bound" all need a caution that isn't an error.
--ccl-blue #8fc3f0 An informational/provenance hue for "sliced", "recalled" and "on feature branch" labels, so provenance doesn't borrow success or error meanings.
--ccl-mist / -2 / -3 #e4e2ea / #f1f0f5 / #d9d6e1 The light theme's page, high and inset surfaces. They carry charcoal's slight violet cast, inverted, so both themes are one family. Soft UI can't use pure white: the highlight half of the shadow needs a lighter color than the surface.

3b. Semantic tokens: changed

Token v2.0 v3 dark v3 light Reason
--surface-raised #36363a #2c2c30 (= page) #e4e2ea In soft UI a raised surface is the same material as the page. The lift comes from the shadow pair. A lighter fill plus a shadow looks like a card lying on top.
--shadow-raised 0 1px 0 0 #3d3d44 var(--nm-raised-2) same The v2.0 name now points to the level-2 soft shadow, so existing rules pick it up.
--accent-fill (new) (none) #a99af0 #5b43b8 Primary button fill. The text on it passes AA, which v2.0's did not (4.32:1).
--text-on-accent #2a2431 #2a2431 #ffffff Unchanged in dark. White in light, because the light accent fill is dark.
--focus-ring #8f7ae0 #a99af0 #5b43b8 v2.0's ring was 3.45:1 on a raised surface. v3's is at least 5.6:1 everywhere.
--radius-sm / --radius-md 0 8 px / 12 px same See §2.
--border-hairline (light) (none) #3d3d44 #cfccd8 The light theme needs its own value. Hairlines are decorative and not relied on.
--scrollbar-thumb #4e4e57 var(--border-strong) #b9b5c6 Now follows the theme.

3c. Semantic tokens: added

Token Dark Light Reason
--surface-high #36363a #f1f0f5 Dialogs and menus. Reuses v2.0's charcoal-2 in dark.
--surface-inset #232327 #d9d6e1 Inputs, wells and progress tracks.
--text-accent #a99af0 #5b43b8 Links and accent text that pass 4.5:1.
--accent-fill-press #9785e6 #4d37a3 The pressed primary button is a different fill, not just an inset shadow.
--accent-wash 10% of #a99af0 10% of #5b43b8 The pressed/selected background for secondary controls.
--status-warning / --status-info amber / blue #7c5100 / #1d5e94 Explained in §3a.
--status-success / --status-error (light) (v2.0 hues) #2c6619 / #a8202f The v2.0 hues are too light for a light surface.
--wash-success/error/warning/info 10% alpha 10% alpha Chip backgrounds. 16% dropped chip text below 4.5:1, so it's 10%.
--border-control #85858f #736e86 The 1.4.11 token. v2.0's strongest border #4e4e57 is 1.69:1 on charcoal, so it can't identify a control. v3's is 3.29–4.30:1 on every surface.
--border-control-width / --border-error-width 1 px / 2 px same An error changes the border's width as well as its color.
--focus-width / --focus-offset 2 px / 3 px same An offset outline, visually separate from the shadow.
--dur-press 60 ms same The press responds faster than hover, so the push feels physical.

3d. Neumorphism tokens: added

Token Value Purpose
--radius-xs … --radius-pill 4 / 8 / 12 / 18 / 26 / 999 px Chips / inputs / buttons / cards / dialogs / tracks and badges
--nm-d-1…4, --nm-b-1…4 2/5, 4/10, 8/18, 14/30 px Shadow distance and blur per intensity level
--nm-shadow-dark dark rgba(0,0,0,.62) · light rgba(88,80,120,.30) Bottom-right half of the raised pair
--nm-shadow-light dark rgba(255,255,255,.055) · light rgba(255,255,255,.95) Top-left half of the raised pair
--nm-inset-dark / --nm-inset-light dark .55 / .045 · light .28 / .9 Inset (pressed/well) pair
--nm-rim dark inset 0 1px 0 rgba(255,255,255,.06) Top-edge rim light (the dark-mode fix, §5)
--nm-raised-gradient dark #313136 → #29292d · light #eceaf1 → #dcd9e3 A 145° surface gradient that sells the convex shape
--nm-raised-1…4 composed Level 1 chips · 2 buttons/rows · 3 cards · 4 dialogs
--nm-inset-1…2 composed Level 1 inputs/pressed buttons · 2 wells/panels
--nm-flat none Disabled controls. They lose their shadow entirely.

4. State matrix

What changes in each state. The "besides shadow" column is what makes each state identifiable without the shadow.

State Shadow Besides shadow (the part that must be visible)
Rest raised-2 1 px solid --border-control (3:1+)
Hover raised-2 Border becomes --text-accent
Pressed / selected inset-1 Background --accent-wash, accent border, ✓ glyph on toggles; primary uses the --accent-fill-press fill
Focused unchanged 2 px solid outline, 3 px offset, --focus-ring. Combines with any other state.
Disabled none Dashed border, transparent fill, secondary text, not-allowed cursor
Error inset-1 (inputs) 2 px border in --status-error, ⚠ icon, and a message under the field

Because each state uses a different channel (outline, fill and glyph, dash, width and icon), any two states stay distinguishable in grayscale.


5. Dark-mode neumorphism: the problem and the fix

The problem. Soft UI draws the same surface with a lighter shadow top-left and a darker one bottom-right. On #2c2c30, the conventional recipe (darker and lighter charcoal, for example #1b1b1e and #3d3d44) gives about 1.3:1 each way. The shape vanishes, or turns into a muddy halo.

The fix, as four rules in the tokens:

  1. The dark half is true black at high alpha (rgba(0,0,0,.62)), not a darker gray. Alpha black darkens whatever it lands on, so it stays visible even beside the v2.0 raised gray.
  2. The light half is white at about 5% alpha. At that level it reads as a sheen. Any brighter and dark soft UI starts to glow.
  3. A 1 px rim light (inset 0 1px 0 rgba(255,255,255,.06)) on every raised surface. The rim is sharp where the shadow is soft, so it keeps the top edge visible even when the drop shadow merges into the page.
  4. The page stays at v2.0's #2c2c30, not near-black. Soft UI needs room below the surface for the dark half, and #1b1b1e would leave none. That is also why wells use the new in-between #232327.

The fifth rule is the accessibility floor, and it's why none of this is fragile: every control has a 3:1 border anyway. If a monitor crushes the blacks, or someone forces high contrast, the UI loses depth but keeps its structure.


6. Alternative palettes (not the main proposal)

Both would reuse every structural and neumorphism token. Only the base scale would change.

A: Warm graphite B: Slate and teal
Page (dark / light) #2e2b29 / #e9e5e0 #27303a / #e1e7ec
Accent (dark text / light text) #e0a36b / #8a4b12 #5fd0c0 / #0f6b61
Mood Workshop, wood and brass. It suits a print farm but moves away from the CCL violet. Cooler and more technical. It reads as "instrument panel" and gives the swatches more room.
Risk Amber becomes the accent, so warning needs a new hue Teal is close to success green, so success would need re-tuning

The main proposal keeps v2.0 violet. Both alternatives change the brand color.


7. Contrast checks

The script used the WCAG 2.1 relative luminance formula. The v2.0 rows show what v3 fixes.

7a. v2.0 as it stands today (dark only)

Pair Ratio Needs Result
Text #f4f4f6 on page #2c2c30 12.66:1 4.5 Pass
Muted #b2b2ba on page 6.60:1 4.5 Pass
Ink #2a2431 on accent #8f7ae0 (primary button) 4.32:1 4.5 FAIL
Accent #8f7ae0 as text on page 3.99:1 4.5 FAIL
Strongest border #4e4e57 vs page (control edge) 1.69:1 3 FAIL
Focus ring #8f7ae0 on raised #36363a 3.45:1 3 Pass (narrowly)

7b. v3, both themes

All 54 checks pass.

Theme Pair Foreground Background Ratio Needs Result
Dark Body text #f4f4f6 #2c2c30 12.66:1 4.5:1 Pass
Dark Body text on inset (inputs) #f4f4f6 #232327 14.25:1 4.5:1 Pass
Dark Body text on high surface (dialog) #f4f4f6 #36363a 10.95:1 4.5:1 Pass
Dark Secondary text #b2b2ba #2c2c30 6.60:1 4.5:1 Pass
Dark Secondary text on inset #b2b2ba #232327 7.43:1 4.5:1 Pass
Dark Secondary text on high #b2b2ba #36363a 5.71:1 4.5:1 Pass
Dark Accent text / links #a99af0 #2c2c30 5.67:1 4.5:1 Pass
Dark Accent text on high #a99af0 #36363a 4.91:1 4.5:1 Pass
Dark Text on primary button #2a2431 #a99af0 6.14:1 4.5:1 Pass
Dark Success text #a9d88b #2c2c30 8.53:1 4.5:1 Pass
Dark Error text #f08b93 #2c2c30 5.84:1 4.5:1 Pass
Dark Error text on high #f08b93 #36363a 5.05:1 4.5:1 Pass
Dark Warning text #e8c170 #2c2c30 8.14:1 4.5:1 Pass
Dark Info text #8fc3f0 #2c2c30 7.44:1 4.5:1 Pass
Dark Control border vs page (1.4.11) #85858f #2c2c30 3.81:1 3:1 Pass
Dark Control border vs inset #85858f #232327 4.29:1 3:1 Pass
Dark Control border vs high #85858f #36363a 3.29:1 3:1 Pass
Dark Focus ring vs page #a99af0 #2c2c30 5.67:1 3:1 Pass
Dark Focus ring vs high #a99af0 #36363a 4.91:1 3:1 Pass
Dark Primary button fill vs page #a99af0 #2c2c30 5.67:1 3:1 Pass
Dark Error border vs inset #f08b93 #232327 6.58:1 3:1 Pass
Dark Warning border vs page #e8c170 #2c2c30 8.14:1 3:1 Pass
Dark ok chip text on 10% wash #a9d88b #383d39 6.80:1 4.5:1 Pass
Dark err chip text on 10% wash #f08b93 #40363a 4.88:1 4.5:1 Pass
Dark warn chip text on 10% wash #e8c170 #3f3b36 6.50:1 4.5:1 Pass
Dark info chip text on 10% wash #8fc3f0 #363b43 6.03:1 4.5:1 Pass
Dark acc chip text on 10% wash #a99af0 #383743 4.77:1 4.5:1 Pass
Light Body text #2a2431 #e4e2ea 11.73:1 4.5:1 Pass
Light Body text on inset (inputs) #2a2431 #d9d6e1 10.51:1 4.5:1 Pass
Light Body text on high surface (dialog) #2a2431 #f1f0f5 13.28:1 4.5:1 Pass
Light Secondary text #55505f #e4e2ea 6.06:1 4.5:1 Pass
Light Secondary text on inset #55505f #d9d6e1 5.43:1 4.5:1 Pass
Light Secondary text on high #55505f #f1f0f5 6.86:1 4.5:1 Pass
Light Accent text / links #5b43b8 #e4e2ea 5.57:1 4.5:1 Pass
Light Accent text on high #5b43b8 #f1f0f5 6.30:1 4.5:1 Pass
Light Text on primary button #ffffff #5b43b8 7.14:1 4.5:1 Pass
Light Success text #2c6619 #e4e2ea 5.42:1 4.5:1 Pass
Light Error text #a8202f #e4e2ea 5.62:1 4.5:1 Pass
Light Error text on high #a8202f #f1f0f5 6.36:1 4.5:1 Pass
Light Warning text #7c5100 #e4e2ea 5.39:1 4.5:1 Pass
Light Info text #1d5e94 #e4e2ea 5.31:1 4.5:1 Pass
Light Control border vs page (1.4.11) #736e86 #e4e2ea 3.80:1 3:1 Pass
Light Control border vs inset #736e86 #d9d6e1 3.41:1 3:1 Pass
Light Control border vs high #736e86 #f1f0f5 4.30:1 3:1 Pass
Light Focus ring vs page #5b43b8 #e4e2ea 5.57:1 3:1 Pass
Light Focus ring vs high #5b43b8 #f1f0f5 6.30:1 3:1 Pass
Light Primary button fill vs page #5b43b8 #e4e2ea 5.57:1 3:1 Pass
Light Error border vs inset #a8202f #d9d6e1 5.03:1 3:1 Pass
Light Warning border vs page #7c5100 #e4e2ea 5.39:1 3:1 Pass
Light ok chip text on 10% wash #2c6619 #d2d6d5 4.74:1 4.5:1 Pass
Light err chip text on 10% wash #a8202f #decfd7 4.81:1 4.5:1 Pass
Light warn chip text on 10% wash #7c5100 #dad4d3 4.73:1 4.5:1 Pass
Light info chip text on 10% wash #1d5e94 #d0d5e1 4.64:1 4.5:1 Pass
Light acc chip text on 10% wash #5b43b8 #d6d2e5 4.83:1 4.5:1 Pass

Additional pairs checked:

Theme Pair Ratio Needs Result
Dark Ink on primary fill #a99af0 6.14:1 4.5 Pass
Dark Ink on pressed primary #9785e6 4.88:1 4.5 Pass
Light White on primary fill #5b43b8 7.14:1 4.5 Pass
Light White on pressed primary #4d37a3 8.78:1 4.5 Pass
Dark Progress fill #a99af0 vs track #232327 6.39:1 3 Pass
Light Progress fill #5b43b8 vs track #d9d6e1 4.99:1 3 Pass
Dark Low-stock fill #f08b93 vs track 6.58:1 3 Pass
Light Low-stock fill #a8202f vs track 5.03:1 3 Pass
Dark Accent text on pressed wash (≈#3a3848) 4.66:1 4.5 Pass

Not covered by the table: filament swatch colors are user data, not tokens. Each swatch gets a --border-control ring, so a charcoal-black or mist-white filament still shows an edge on either theme, and the color name is always printed beside it.