docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
---
|
|
|
|
|
title: Eidos Theming — Authoring guides
|
|
|
|
|
type: guide
|
|
|
|
|
audience: human + agent
|
|
|
|
|
status: current
|
|
|
|
|
source: migrated from src/uix/eidos/THEMING_GUIDE.md (2026-07-02, docs-book F7.3; originally extracted from THEMING §8 + §9)
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Eidos Theming — Authoring guides
|
|
|
|
|
|
|
|
|
|
> The two theming how-tos: add a new component and define a theme. Extracted
|
docs(book): F7.3 (9/9) — THEMING reference translated; theming/ + canon/ batch COMPLETE
src/uix/eidos/THEMING.md (1499 L, Spanish) translated to English as
docs/theming/reference.md, same s1-s38 numbering: the mental model,
s1.bis (theming lives in eidos — the 2-of-3 derivation and the canonical
split), the CSS layers, the 7 token layers, the 9 roles, the size canon
(universal 1:1 + label step-down + container cap + the icon scale), the
naming conventions, the s7/s8/s9/s13/s15/s17/s18 stubs repointed into the
book (canon/tsc, theming/guide, theming/notes, theming/changelog,
theming/motion), runtime overrides + builders, bundle/purge, validation
tooling, s14 motion (the pickup lift), s16 anti-patterns, s19 variants
canon, and the s20-s38 standing-decision stubs. One internal
reconciliation applied: the s35 stub cited the control/compact/dense size
archetypes that s5 declares superseded by the universal 1:1 — aligned.
One stale v1 DEMO_AUTHORING s12.8 citation in s5 replaced by the v2 s6
parity rule. Stub at the old path carries the full s1-s38 map (the most
sN-cited doc in the repo: code comments, CLAUDE.md, RFCs). Corpus links
swept (CANON, docs map, comparison, glossary, eidos chapter, canon/tsc,
theming/guide + notes). docs:check 0 errors (260 docs).
F7.3 complete: docs/canon/ (tsc, recipe-contract) + docs/theming/
(reference, guide, notes, channels, motion, motion-guide, changelog).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
> from [`THEMING.md`](./reference.md) (the E1 reference) to
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
> live as an E4 guide. The token contract holding all of this together is
|
|
|
|
|
> [`canon/tsc.md`](../canon/tsc.md); the transversal systems every recipe
|
|
|
|
|
> must consume are [`canon/recipe-contract.md`](../canon/recipe-contract.md).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## How to add a new component
|
|
|
|
|
|
|
|
|
|
Assuming you already have the morfo, the soma and the eidos wrapper
|
|
|
|
|
scaffolding (`src/uix/eidos/components/{name}/`):
|
|
|
|
|
|
|
|
|
|
### Step 1 — Decide which tokens you need
|
|
|
|
|
|
|
|
|
|
Look at similar components (`button`, `toggle`, `switch`). Identify which
|
|
|
|
|
dimensions your component exposes:
|
|
|
|
|
|
|
|
|
|
- Does it have `data-color`? → palette tokens.
|
|
|
|
|
- Does it have `data-variant`? → variant tokens.
|
|
|
|
|
- Does it have `data-size`? → size tokens.
|
|
|
|
|
- How many parts does it have? → per-part tokens.
|
|
|
|
|
|
|
|
|
|
### Step 2 — Add the recipe in `lib/recipes/base.ts`
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
// Within THEME_BASE_RECIPE_TOKENS:
|
|
|
|
|
'my-component': {
|
|
|
|
|
// size tokens — scope 'root' (stable)
|
|
|
|
|
'height-md': '36px',
|
|
|
|
|
'padding-inline-md': 'var(--space-3)',
|
|
|
|
|
'gap': 'var(--space-2)',
|
|
|
|
|
'radius': 'var(--radius-md)',
|
|
|
|
|
|
|
|
|
|
// per-color literal definitions — scope 'root'
|
|
|
|
|
'primary-solid': 'var(--color-primary-solid)',
|
|
|
|
|
'affirm-solid': 'var(--color-affirm-solid)',
|
|
|
|
|
'threat-solid': 'var(--color-threat-solid)',
|
|
|
|
|
|
|
|
|
|
// dynamic palette — 'host' default + per-color overrides
|
|
|
|
|
'palette-solid': {
|
|
|
|
|
declarations: [
|
|
|
|
|
{ value: 'var(--my-component-primary-solid)', scope: 'host' },
|
|
|
|
|
{ value: 'var(--my-component-affirm-solid)', scope: 'color:affirm' },
|
|
|
|
|
{ value: 'var(--my-component-threat-solid)', scope: 'color:threat' }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// derived tokens — scope 'host' (deps inferred)
|
|
|
|
|
'solid-bg': {
|
|
|
|
|
value: 'var(--my-component-palette-solid)',
|
|
|
|
|
scope: 'host'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 3 — Regenerate
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm run generate:eidos-css
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If your config violates the TSC, the regeneration tells you:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Eidos recipe scope contract violations:
|
|
|
|
|
- my-component.solid-bg (scope root): dependency 'palette-solid' is only
|
|
|
|
|
declared at scopes [host], none of which is reachable from the
|
|
|
|
|
consumer's scope.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 4 — Write the recipe CSS
|
|
|
|
|
|
|
|
|
|
`src/uix/eidos/components/my-component/my-component.css`:
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
[data-my-component] {
|
|
|
|
|
/* Private tokens — only this recipe reads them */
|
|
|
|
|
--_my-component-bg: var(--my-component-solid-bg);
|
|
|
|
|
--_my-component-radius: var(--my-component-radius);
|
|
|
|
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding-inline: var(--my-component-padding-inline-md);
|
|
|
|
|
height: var(--my-component-height-md);
|
|
|
|
|
border-radius: var(--_my-component-radius);
|
|
|
|
|
background: var(--_my-component-bg);
|
|
|
|
|
gap: var(--my-component-gap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-my-component][data-disabled] {
|
|
|
|
|
opacity: var(--opacity-disabled);
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 5 — Wire the CSS
|
|
|
|
|
|
|
|
|
|
Code-split components import their own CSS from the wrapper `.svelte`;
|
|
|
|
|
layout primitives and shared visuals aggregate in `index.css`:
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
@import './components/my-component/my-component.css';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 6 — Verify
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm run generate:eidos-css
|
|
|
|
|
npm test -- src/uix/eidos
|
|
|
|
|
npm run morfo:check
|
|
|
|
|
node --import tsx/esm scripts/eidos-lint.ts my-component
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Common anti-pattern: declaring composed tokens at `:root`
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
// ❌ WRONG — the TSC will fail
|
|
|
|
|
'my-component': {
|
|
|
|
|
'palette-solid': { value: 'var(--color-neutral-solid)', scope: 'host' },
|
|
|
|
|
'solid-bg': 'var(--my-component-palette-solid)' // ← implicit 'root' scope, deps live in 'host'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ✓ CORRECT
|
|
|
|
|
'my-component': {
|
|
|
|
|
'palette-solid': { value: 'var(--color-neutral-solid)', scope: 'host' },
|
|
|
|
|
'solid-bg': {
|
|
|
|
|
value: 'var(--my-component-palette-solid)',
|
|
|
|
|
scope: 'host'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## How to define a theme
|
|
|
|
|
|
|
|
|
|
Eidos supports **3 modes** of theme definition:
|
|
|
|
|
|
|
|
|
|
### Mode 1 — A patch over the base theme (recommended)
|
|
|
|
|
|
|
|
|
|
Change only what you need; everything else follows the base:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { ActiveEidos } from '$uix/eidos';
|
|
|
|
|
|
|
|
|
|
ActiveEidos.create({
|
|
|
|
|
themeBase: {
|
|
|
|
|
semantics: {
|
|
|
|
|
color: {
|
|
|
|
|
roles: {
|
|
|
|
|
primary: 'blue', // primary uses the Radix blue scale
|
|
|
|
|
secondary: 'plum'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
primitives: {
|
|
|
|
|
typography: {
|
|
|
|
|
families: {
|
|
|
|
|
primary: { family: 'Inter' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
applyDom: true
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Mode 2 — A full config
|
|
|
|
|
|
|
|
|
|
For authoring from scratch:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { ActiveEidos, defineEidosConfig } from '$uix/eidos';
|
|
|
|
|
|
|
|
|
|
const config = defineEidosConfig({
|
|
|
|
|
primitives: { /* … */ },
|
|
|
|
|
semantics: { color: { /* … */ } },
|
|
|
|
|
themes: { /* … */ }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ActiveEidos.create({ config, applyDom: true });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Mode 3 — A CSS-only theme (no TypeScript)
|
|
|
|
|
|
|
|
|
|
Eidos publishes the contract as empty CSS for externals to fill:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const contract = activeEidos.renderContractCss({
|
|
|
|
|
themeSelector: "[data-theme='acme-light']"
|
|
|
|
|
});
|
|
|
|
|
// Output:
|
|
|
|
|
// [data-theme='acme-light'] {
|
|
|
|
|
// --scale-blue-9: ;
|
|
|
|
|
// --color-primary-solid: ;
|
|
|
|
|
// --size-md-control-height: ;
|
|
|
|
|
// ...
|
|
|
|
|
// }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The consumer fills in the values:
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
[data-theme='acme-light'] {
|
|
|
|
|
--scale-blue-9: #006adc;
|
|
|
|
|
--color-primary-solid: var(--scale-blue-9);
|
|
|
|
|
--size-md-control-height: 38px;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
And loads that CSS alongside Eidos's. With `themeSource: 'css'`,
|
|
|
|
|
`ActiveEidos` generates no theme of its own.
|
|
|
|
|
|
|
|
|
|
### Versioned persistence
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const document = activeEidos.toDocument();
|
|
|
|
|
// → { kind: 'uix.eidos-config', version: 1, options: {...} }
|
|
|
|
|
|
|
|
|
|
const json = activeEidos.serialize();
|
|
|
|
|
localStorage.setItem('user-theme', json);
|
|
|
|
|
|
|
|
|
|
// Later
|
|
|
|
|
const hydrated = createActiveEidos({
|
|
|
|
|
config: JSON.parse(localStorage.getItem('user-theme')!),
|
|
|
|
|
prefs, dom
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The document envelope carries a `version` for future migrations.
|