From 7dc3e710b419166cccc7acefea469536e2ba8102 Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 20:44:42 +0200 Subject: [PATCH 1/4] docs: swarm-ui design guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Distills the design-language discussion on #3444 into a durable reference: visual language, motion, theming policy, data-freshness/ refresh, error UX, empty states, layout/viewport, component-first design, and attention — plus an open-questions table pointing at the still-unbuilt issues (#3452/#3453/#3454/#3456) instead of asserting them as settled. Mirrors css-vars.md's shape and links to it + to /components rather than duplicating either. First draft for comment, not a final spec. --- docs/web-ui.md | 5 + docs/web-ui/design-guide.md | 185 ++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 docs/web-ui/design-guide.md diff --git a/docs/web-ui.md b/docs/web-ui.md index 37513f7d..b8cb8f99 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -29,6 +29,11 @@ dashboard rather than how it's built, start at - **[CSS theme variables](web-ui/css-vars.md)** — the Catppuccin Mocha custom properties declared once in `base.css` and the rule that per-page stylesheets reference (never redeclare) them. +- **[swarm-ui design guide](web-ui/design-guide.md)** — visual + language, motion, theming, error-UX, and component-first principles + for the swarm-level Preact app specifically (not this doc's + dashboard/agent UIs). `/components` on a running swarm-ui is the + companion living demo of every primitive it references. ## Reading paths diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md new file mode 100644 index 00000000..0a07b523 --- /dev/null +++ b/docs/web-ui/design-guide.md @@ -0,0 +1,185 @@ +# swarm-ui design guide + +Scope: **swarm-ui only** (the swarm-level Preact app — not the per-hive +dashboard, which has its own older visual language). This doc is the +*why*: the principles behind how swarm-ui looks and behaves, and the +concrete rules that follow from them. It deliberately doesn't show what +things look like — that's `ComponentsPage` (`/components`), the living, +always-current demo of every primitive in `src/ui/`. Code can't go stale +the way a doc's screenshots can, so treat `ComponentsPage` as the source +of truth for appearance and this doc as the source of truth for intent. +Colour variables specifically are `docs/web-ui/css-vars.md`'s job, not +repeated here. + +Distilled from the design-language discussion in issue #3444 — read that +thread for the full reasoning behind any rule below that needs more +context than fits here. + +## Visual language + +- **Basis: Material Design, the Material You generation** (Android's + current design language) — but swarm-ui strays fairly far from its + theming approach; base16/stylix (below) is the actual colour contract, + not Material's dynamic-colour system. +- **Frosted / tinted glass** where it fits — translucency over flat + panels, not a hard rule for every surface. +- **Playful use of the multiple accent colours**, without tipping into + visual noise — the base16 palette has several accent slots + (`--purple`, `--cyan`, `--pink`, …); use more than one where it adds + distinction, not decoration for its own sake. +- **Whimsy** — small, delightful touches are welcome (the home page's + matrix-rain background is the reference example). Whimsy still has to + clear the accessibility bar below (motion, in particular). +- **Efficient navigation** — minimize clicks/hops for a common task. +- **Avoid junk drawers.** A control belongs next to the thing it + affects, not tucked into a catch-all menu. Concrete anti-example (not + swarm-ui, but the standard to avoid): the per-hive agent terminal's + model selector living in a three-dot overflow menu instead of next to + the thing that shows the current model. + +## Motion + +**General rule, not case-by-case: every non-essential CSS animation +gates on `prefers-reduced-motion` and pauses when its tab/section is +hidden.** The matrix-rain background is the existing reference +implementation of this pattern — every future animation follows it, not +just whimsy pieces. + +An in-app override (independent of the OS-level media query, for +someone who wants motion on despite a system-wide reduced-motion +setting, or vice versa) is tracked separately — see #3456, which needs +the client-settings surface from #3453 first. + +Prefer CSS-driven animation over JS-driven where possible, and avoid +jarring content swaps (layout shift, hard cuts) where a transition can +smooth them instead. + +## Theming + +The mechanical contract (base16 slots, semantic vars, what a page's CSS +is and isn't allowed to reference) lives in `docs/web-ui/css-vars.md` — +read that for the how. This section is the *policy* layered on top: + +- **User-theming compatible by construction.** The whole point of the + base16/`colors.css` swap contract is that a user's own theme (stylix + today) takes over with zero swarm-ui code changes — a generator + replaces exactly one file. Don't build anything that assumes a + specific palette's exact colours (contrast ratios, "this accent is + always purple") beyond what the semantic var names promise. +- **stylix wins outright when it's active, no in-between state.** + `nix/host-modules/hive-c0re/theme.nix` decides at *build time* whether + a hive is stylix-themed; when it is, that palette is authoritative — + swarm-ui's own light/dark logic (below) doesn't run at all rather than + trying to negotiate with it. This is a deliberate simplification, not + a limitation to fix: a stylix-using operator has already made their + choice. +- **Light theme is accessibility, not a cosmetic extra** — confirmed + position on #3444/#3452: some people need light for contrast/low-vision + reasons, others need dark for photosensitivity, so "OS preference by + default + explicit override" is the right shape, same as motion above. + **Not shipped yet** — tracked as #3452 (light palette itself), #3453 + (the client-settings surface an override setting needs), #3454 (the + override setting). Until #3452 lands, swarm-ui is dark-only. + +## Data freshness & refresh + +The governing question for anything that shows time-sensitive data: +**would a user returning to this tab expect current data?** If yes, it +needs a refresh story; a value that's silently gone stale with no way to +tell is worse than one that's visibly stale. + +- **Relative-time labels must actually tick.** A `StatusChip`-style + "fresh (5s ago)" computed once at fetch and never updated again reads + as current when it isn't. Tracked as #3445 (`RelativeTime` component: + takes a UTC datetime, not a precomputed age, and re-renders itself on + an internal interval; pauses while its tab is hidden). +- **Pages that poll get a refresh-interval control, not silent + staleness and not push.** SSE/WebSocket push was considered and + rejected for now — "overkill, leads to the same mess we have in + core." Instead: a small, Grafana-like "refresh every: off / 10s / 30s + / 1m / …" control the operator sets per page, paused while the tab is + backgrounded and resumed on foreground. Tracked as #3446 + (`OverviewPage`'s hive-status roster is the first real caller). +- **A refresh must never clobber input the operator is mid-edit on.** + Any future polling component's contract needs to make this the + caller's problem to opt out of correctly, not something the next + adopter discovers by shipping a bug. + +## Errors + +**`ApiErrorPanel` + the RFC 9457 `ProblemDetails` shape it renders is +the canonical error surface** for every API failure in swarm-ui, not +just its original callers — nobody should build a softer/quieter error +UI later. Concretely: full untruncated `detail`, a copy button, no +"something went wrong, try reloading." + +This follows from **power-user-first**, one of the standing principles: +swarm-ui instances are mostly self-hosted and operators are techies, so +errors should give them what they need to actually diagnose a problem +rather than a friendly wall. Power-user-first doesn't mean +newcomer-hostile — the UI should still be self-explanatory, warn or ask +for confirmation before a destructive/dangerous action, and offer +helpful hints — it just shouldn't get in the way of someone who already +knows what they're doing. + +## Empty states + +Graceful fallbacks, always — a table with zero rows should show +something that represents "no rows" (a real empty-state message), not +just bare column headers floating over nothing. + +## Layout & viewport + +- **swarm-ui adapts to screen size and fully supports touch** — this is + not a desktop-only app. Concrete floor: every interactive control from + the shared `ui/` kit (buttons, inputs, selects) carries a minimum + touch target (`2.75em` ≈ 44px, WCAG 2.5.5) by default, so this isn't + something each page has to remember. Tracked follow-through: + #3447 (page-level layout — nav wrap, no fixed-width assumptions). +- **Phone is a second-class citizen for now, not an unsupported one.** + Things must not *break* at phone width, but don't over-invest in phone + optimization beyond that yet. A PWA manifest (installable, basic + status-check/interaction use case) is wanted eventually but is its own + future slice, not implied by "supports touch" today. +- **A narrow viewport isn't only a phone** — a tiling-window-manager + user with swarm-ui in a narrow tile hits the same layout constraints a + phone does. Design for the constraint (narrow viewport), not the + device. + +## Component-first design + +When something needs a table, a form field, a button — build (or adopt) +a `src/ui/` primitive that enforces consistent theming and behaviour, +rather than styling it inline on the page that happens to need it first. +`Panel`/`StatusChip`/`Table`/`FormField`/`TextField`/`SelectField`/ +`Button` are the primitives that exist as of this writing; `/components` +always has the current, complete list — this doc won't try to keep a +duplicate inventory in sync. + +Default heuristic for *when* to promote page-scoped styling into a +shared primitive: the day a second real page needs the same thing, not +speculatively ahead of one. (Exception: when a peer explicitly asks for +a primitive ahead of a second caller, as happened with the form kit — +that's a judgement call each time, not a rule change.) + +**Every new `ui/` component gets a demo section on `/components` the +same day it lands** — no primitive without a place to see it. + +## Attention + +"Attention-optimized" here means the system should lead the operator's +attention to where it actually matters (an error, a state change worth +noticing) — not attention-optimized in the engagement/growth sense of +maximizing time-on-page. + +## Open questions + +Tracked separately rather than asserted here as settled: + +| Question | Tracked in | +| --- | --- | +| Light theme (palette + `prefers-color-scheme` default) | #3452 | +| Client-local settings surface (page + storage) | #3453 | +| Theme override setting | #3454 | +| Reduced-motion override setting | #3456 | +| PWA manifest / installable phone experience | not yet filed — mentioned in #3444, no concrete scope agreed | From a5864c0fde3ff262bba7051169cc2ca082323819 Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 20:48:42 +0200 Subject: [PATCH 2/4] docs: fix design-guide accuracy nits argus flagged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - matrix-rain reference is in packages/dashboard/src/home.js, not swarm-ui — cite it correctly (dashboard package) rather than implying it lives inside this doc's own scope. - drop FormField from the visible component-first inventory (it has no /components demo, deliberately — a real exception to the 'every new ui/ component gets a demo' rule stated two paragraphs later) and note the exception explicitly instead of leaving the contradiction. --- docs/web-ui/design-guide.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md index 0a07b523..62d29217 100644 --- a/docs/web-ui/design-guide.md +++ b/docs/web-ui/design-guide.md @@ -27,9 +27,12 @@ context than fits here. visual noise — the base16 palette has several accent slots (`--purple`, `--cyan`, `--pink`, …); use more than one where it adds distinction, not decoration for its own sake. -- **Whimsy** — small, delightful touches are welcome (the home page's - matrix-rain background is the reference example). Whimsy still has to - clear the accessibility bar below (motion, in particular). +- **Whimsy** — small, delightful touches are welcome (the per-hive + dashboard's home-page matrix-rain background, `packages/dashboard/src/ + home.js`, is the reference example — currently in the dashboard + package, not swarm-ui itself, but the pattern it sets applies here + too). Whimsy still has to clear the accessibility bar below (motion, + in particular). - **Efficient navigation** — minimize clicks/hops for a common task. - **Avoid junk drawers.** A control belongs next to the thing it affects, not tucked into a catch-all menu. Concrete anti-example (not @@ -41,9 +44,12 @@ context than fits here. **General rule, not case-by-case: every non-essential CSS animation gates on `prefers-reduced-motion` and pauses when its tab/section is -hidden.** The matrix-rain background is the existing reference -implementation of this pattern — every future animation follows it, not -just whimsy pieces. +hidden.** The dashboard's matrix-rain background (`packages/dashboard/ +src/home.js`/`.css`) is the existing reference implementation of this +pattern — genuinely does both today, confirmed by reading it — even +though it lives in the dashboard package rather than swarm-ui; every +future swarm-ui animation follows the same pattern, not just whimsy +pieces. An in-app override (independent of the OS-level media query, for someone who wants motion on despite a system-wide reduced-motion @@ -151,10 +157,10 @@ just bare column headers floating over nothing. When something needs a table, a form field, a button — build (or adopt) a `src/ui/` primitive that enforces consistent theming and behaviour, rather than styling it inline on the page that happens to need it first. -`Panel`/`StatusChip`/`Table`/`FormField`/`TextField`/`SelectField`/ -`Button` are the primitives that exist as of this writing; `/components` -always has the current, complete list — this doc won't try to keep a -duplicate inventory in sync. +`Panel`/`StatusChip`/`Table`/`TextField`/`SelectField`/`Button` are the +primitives that exist as of this writing, each with its own +`/components` demo; `/components` always has the current, complete list +— this doc won't try to keep a duplicate inventory in sync. Default heuristic for *when* to promote page-scoped styling into a shared primitive: the day a second real page needs the same thing, not @@ -163,7 +169,12 @@ a primitive ahead of a second caller, as happened with the form kit — that's a judgement call each time, not a rule change.) **Every new `ui/` component gets a demo section on `/components` the -same day it lands** — no primitive without a place to see it. +same day it lands** — no primitive without a place to see it. One +deliberate exception exists today: `FormField`, the internal label+ +control wrapper `TextField`/`SelectField` share, isn't itself a +primitive a page reaches for directly, so it has no demo of its own — +that's a real, considered exception (see its own doc comment), not an +oversight this rule missed. ## Attention From 61b7bb4b4dba1aec8cc991449c601286b6533d8a Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 21:01:37 +0200 Subject: [PATCH 3/4] docs: rework design guide per mara's review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses all 7 line comments from her REQUEST_CHANGES review: - drop the issue-#3444 history pointer and any issue-number tracking refs throughout (a design guide states expectations, it isn't a change log or a status report) - stop naming specific rejected technical solutions (SSE/WS) for a design constraint — state the chosen shape only - flip the component-first heuristic: build the primitive first/ alongside its first real caller, not after — the point is giving the next thing built ready-made blocks, not lagging behind usage - drop the 'Open questions' section entirely — that's what the issue thread is for, not a doc - stop leading Visual language with 'basis: Material Design' and then immediately carving out big exceptions — lead with what we actually want, mention Material as a minor closing influence instead - drop 'future work'/timeline framing everywhere (motion override, PWA-as-future-slice, 'not shipped yet' theming caveats) — state the target design as the expectation, not its current build status - reworded the stylix-wins theming bullet to drop 'build time', which reads wrong from a frontend dev's perspective (stylix supplies the palette separately, it isn't decided by the frontend's own build) --- docs/web-ui/design-guide.md | 119 +++++++++++++----------------------- 1 file changed, 41 insertions(+), 78 deletions(-) diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md index 62d29217..5593710f 100644 --- a/docs/web-ui/design-guide.md +++ b/docs/web-ui/design-guide.md @@ -11,16 +11,8 @@ of truth for appearance and this doc as the source of truth for intent. Colour variables specifically are `docs/web-ui/css-vars.md`'s job, not repeated here. -Distilled from the design-language discussion in issue #3444 — read that -thread for the full reasoning behind any rule below that needs more -context than fits here. - ## Visual language -- **Basis: Material Design, the Material You generation** (Android's - current design language) — but swarm-ui strays fairly far from its - theming approach; base16/stylix (below) is the actual colour contract, - not Material's dynamic-colour system. - **Frosted / tinted glass** where it fits — translucency over flat panels, not a hard rule for every surface. - **Playful use of the multiple accent colours**, without tipping into @@ -39,6 +31,10 @@ context than fits here. swarm-ui, but the standard to avoid): the per-hive agent terminal's model selector living in a three-dot overflow menu instead of next to the thing that shows the current model. +- Loosely influenced by Material Design's current (Material You) + generation, mainly for shape/elevation ideas — colour theming is + governed entirely by the base16/stylix contract below, not Material's + dynamic-colour system. ## Motion @@ -51,11 +47,6 @@ though it lives in the dashboard package rather than swarm-ui; every future swarm-ui animation follows the same pattern, not just whimsy pieces. -An in-app override (independent of the OS-level media query, for -someone who wants motion on despite a system-wide reduced-motion -setting, or vice versa) is tracked separately — see #3456, which needs -the client-settings surface from #3453 first. - Prefer CSS-driven animation over JS-driven where possible, and avoid jarring content swaps (layout shift, hard cuts) where a transition can smooth them instead. @@ -68,24 +59,20 @@ read that for the how. This section is the *policy* layered on top: - **User-theming compatible by construction.** The whole point of the base16/`colors.css` swap contract is that a user's own theme (stylix - today) takes over with zero swarm-ui code changes — a generator - replaces exactly one file. Don't build anything that assumes a - specific palette's exact colours (contrast ratios, "this accent is - always purple") beyond what the semantic var names promise. -- **stylix wins outright when it's active, no in-between state.** - `nix/host-modules/hive-c0re/theme.nix` decides at *build time* whether - a hive is stylix-themed; when it is, that palette is authoritative — - swarm-ui's own light/dark logic (below) doesn't run at all rather than - trying to negotiate with it. This is a deliberate simplification, not - a limitation to fix: a stylix-using operator has already made their - choice. -- **Light theme is accessibility, not a cosmetic extra** — confirmed - position on #3444/#3452: some people need light for contrast/low-vision - reasons, others need dark for photosensitivity, so "OS preference by - default + explicit override" is the right shape, same as motion above. - **Not shipped yet** — tracked as #3452 (light palette itself), #3453 - (the client-settings surface an override setting needs), #3454 (the - override setting). Until #3452 lands, swarm-ui is dark-only. + today) takes over with zero swarm-ui code changes. Don't build + anything that assumes a specific palette's exact colours (contrast + ratios, "this accent is always purple") beyond what the semantic var + names promise. +- **stylix wins outright when it's active, no in-between state.** When + an operator has stylix supplying swarm-ui's palette, that palette is + authoritative — swarm-ui's own light/dark preference (below) doesn't + override it. This is a deliberate simplification, not a limitation to + fix: a stylix-using operator has already made their choice. +- **Light theme is accessibility, not a cosmetic extra.** Some people + need light for contrast/low-vision reasons, others need dark for + photosensitivity — there's no universally-correct default. The OS + preference (`prefers-color-scheme`) drives the default, with an + explicit user override available, same shape as motion above. ## Data freshness & refresh @@ -94,22 +81,18 @@ The governing question for anything that shows time-sensitive data: needs a refresh story; a value that's silently gone stale with no way to tell is worse than one that's visibly stale. -- **Relative-time labels must actually tick.** A `StatusChip`-style - "fresh (5s ago)" computed once at fetch and never updated again reads - as current when it isn't. Tracked as #3445 (`RelativeTime` component: - takes a UTC datetime, not a precomputed age, and re-renders itself on - an internal interval; pauses while its tab is hidden). +- **Relative-time labels must actually tick.** A label like "fresh (5s + ago)" is computed from a stored timestamp and re-renders itself on an + interval — never a value frozen at fetch time that quietly goes stale + while still reading as current. Pauses while its tab is hidden. - **Pages that poll get a refresh-interval control, not silent - staleness and not push.** SSE/WebSocket push was considered and - rejected for now — "overkill, leads to the same mess we have in - core." Instead: a small, Grafana-like "refresh every: off / 10s / 30s - / 1m / …" control the operator sets per page, paused while the tab is - backgrounded and resumed on foreground. Tracked as #3446 - (`OverviewPage`'s hive-status roster is the first real caller). + staleness.** A small, Grafana-like "refresh every: off / 10s / 30s / + 1m / …" control the operator sets per page, paused while the tab is + backgrounded and resumed on foreground. - **A refresh must never clobber input the operator is mid-edit on.** - Any future polling component's contract needs to make this the - caller's problem to opt out of correctly, not something the next - adopter discovers by shipping a bug. + Any polling component's contract needs to make this the caller's + problem to opt out of correctly, not something the next adopter + discovers by shipping a bug. ## Errors @@ -140,13 +123,11 @@ just bare column headers floating over nothing. not a desktop-only app. Concrete floor: every interactive control from the shared `ui/` kit (buttons, inputs, selects) carries a minimum touch target (`2.75em` ≈ 44px, WCAG 2.5.5) by default, so this isn't - something each page has to remember. Tracked follow-through: - #3447 (page-level layout — nav wrap, no fixed-width assumptions). -- **Phone is a second-class citizen for now, not an unsupported one.** - Things must not *break* at phone width, but don't over-invest in phone - optimization beyond that yet. A PWA manifest (installable, basic - status-check/interaction use case) is wanted eventually but is its own - future slice, not implied by "supports touch" today. + something each page has to remember. +- **Phone is a second-class citizen, not an unsupported one.** Things + must not *break* at phone width, but don't over-invest in phone + optimization beyond that. swarm-ui is installable as a PWA, so a + phone can check status or do basic interactions. - **A narrow viewport isn't only a phone** — a tiling-window-manager user with swarm-ui in a narrow tile hits the same layout constraints a phone does. Design for the constraint (narrow viewport), not the @@ -154,27 +135,21 @@ just bare column headers floating over nothing. ## Component-first design -When something needs a table, a form field, a button — build (or adopt) -a `src/ui/` primitive that enforces consistent theming and behaviour, -rather than styling it inline on the page that happens to need it first. +Build the `src/ui/` primitive before or alongside the first real page +that needs it, not after it's been styled inline and left for later — +a page reaching for a component that's already there has ready-made +blocks to build with, instead of the next contributor duplicating +ad-hoc styling that someone then has to hunt down and consolidate. `Panel`/`StatusChip`/`Table`/`TextField`/`SelectField`/`Button` are the -primitives that exist as of this writing, each with its own -`/components` demo; `/components` always has the current, complete list -— this doc won't try to keep a duplicate inventory in sync. - -Default heuristic for *when* to promote page-scoped styling into a -shared primitive: the day a second real page needs the same thing, not -speculatively ahead of one. (Exception: when a peer explicitly asks for -a primitive ahead of a second caller, as happened with the form kit — -that's a judgement call each time, not a rule change.) +primitives that exist; `/components` always has the current, complete +list — this doc won't try to keep a duplicate inventory in sync. **Every new `ui/` component gets a demo section on `/components` the same day it lands** — no primitive without a place to see it. One deliberate exception exists today: `FormField`, the internal label+ control wrapper `TextField`/`SelectField` share, isn't itself a primitive a page reaches for directly, so it has no demo of its own — -that's a real, considered exception (see its own doc comment), not an -oversight this rule missed. +a considered exception, not an oversight this rule missed. ## Attention @@ -182,15 +157,3 @@ oversight this rule missed. attention to where it actually matters (an error, a state change worth noticing) — not attention-optimized in the engagement/growth sense of maximizing time-on-page. - -## Open questions - -Tracked separately rather than asserted here as settled: - -| Question | Tracked in | -| --- | --- | -| Light theme (palette + `prefers-color-scheme` default) | #3452 | -| Client-local settings surface (page + storage) | #3453 | -| Theme override setting | #3454 | -| Reduced-motion override setting | #3456 | -| PWA manifest / installable phone experience | not yet filed — mentioned in #3444, no concrete scope agreed | From f0c362b6888c1d50a0333c1c788e4acfd5b310cb Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 21:07:05 +0200 Subject: [PATCH 4/4] docs: clarify dynamic colour derivation still traces to base16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per mara's follow-up: the doc shouldn't read as 'no dynamic colour at all' — mixing/lighter/darker derivation is fine and matches Material's own approach, the constraint is that every derived colour still traces back to the base16/stylix contract, not an independent source. --- docs/web-ui/design-guide.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md index 5593710f..e8610471 100644 --- a/docs/web-ui/design-guide.md +++ b/docs/web-ui/design-guide.md @@ -32,9 +32,10 @@ repeated here. model selector living in a three-dot overflow menu instead of next to the thing that shows the current model. - Loosely influenced by Material Design's current (Material You) - generation, mainly for shape/elevation ideas — colour theming is - governed entirely by the base16/stylix contract below, not Material's - dynamic-colour system. + generation, mainly for shape/elevation ideas. Colour is dynamic too — + auto-derived shades (mixing, lighter/darker) are fair game, same as + Material's own approach — but every derived colour still traces back + to the base16/stylix contract below, never an independent source. ## Motion