You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/theming/notes.md

172 lines
7.3 KiB

---
title: Eidos Theming — Comparison + decisions
type: notes
audience: human + agent
status: current
source: migrated from src/uix/eidos/THEMING_NOTES.md (2026-07-02, docs-book F7.3; originally extracted from THEMING §15 + §17)
---
# Eidos Theming — Comparison + decisions
> The theming's E3 material: how eidos compares with the reference libraries,
> and the FAQ of the controversial decisions (the *why* behind the choices).
> Extracted from [`THEMING.md`](./reference.md) (the E1
> reference).
---
## Comparison with reference libraries
### Bundle size (a typical 10-component app)
| System | Raw | Gzip | Strategy |
|---|---|---|---|
| Tailwind v4 | ~30 KB | ~10 KB | JIT atomic classes |
| **Eidos + purge** | **116 KB** | **13.6 KB** | **JIT custom-property purge** |
| Chakra Panda v3 | ~30 KB | ~12 KB | Build-time JIT |
| Mantine | ~60 KB | ~18 KB | No purge |
| Radix Themes | ~80 KB | ~22 KB | No purge |
| shadcn/ui | varies | varies | Copy-paste, not centralized |
| **Eidos without purge** | **218 KB** | **25 KB** | Single CSS |
### Theming features
| Feature | Eidos | Radix Themes | Chakra Panda | Mantine | shadcn | Tailwind v4 |
|---|---|---|---|---|---|---|
| **Token scope as data** | ✅ TSC | ❌ implicit | ⚠️ build-time | ❌ runtime | ❌ N/A | ❌ N/A |
| **Auto-inferred deps** | ✅ `var()` parse | ❌ | ✅ types | ❌ | N/A | N/A |
| **Cross-axis collision** | ✅ explicit | ❌ | ⚠️ partial | ❌ | N/A | N/A |
| **Composite scopes** | ✅ `[axis:v, …]` | ❌ | ✅ conditional pairs | ❌ | N/A | N/A |
| **9 canonical color roles** | ✅ the book | ⚠️ 6 accents | ❌ open | ❌ open | ⚠️ 4 roles | ❌ open |
| **Coordinated size canon** | ✅ canon + guard + consumed¹ | ⚠️ 1-3 | ⚠️ 5 | ⚠️ 5 | ❌ | N/A |
| **Runtime density** | ✅ 3 levels | ❌ | ❌ | ⚠️ partial | ❌ | ❌ |
| **Contract introspection** | ✅ typed | ⚠️ docs | ✅ Panda | ⚠️ docs | ❌ | ❌ |
| **Runtime override** | ✅ contract-aware | ⚠️ via CSS vars | ❌ | ✅ CSSVarsProvider | ⚠️ via CSS | ❌ |
| **Versioned persistence** | ✅ envelope | ❌ | ❌ | ❌ | ❌ | N/A |
| **Perceptual layer (sema)** | ✅ unique | ❌ | ❌ | ❌ | ❌ | ❌ |
¹ Full coordination: the `--size-{k}-*` bundle is emitted, guarded (no px/rem
literals in recipe `font-size-*`/`icon-size-*`), **and consumed** — the C7
sweep (2026-07-03) pointed all 34 size-bearing recipes at the bundle
coordinate (`--size-{k}-control-height` / `-font-size` / `-icon-size`)
instead of the raw primitive of the same coordinate, and
`recipe-css-contract` forbids the raw primitive so the drift cannot return.
The size→font mapping is the universal **1:1** of the size canon (§5; the
2026-06-15 audit's `control · compact · dense` archetypes were superseded by
the 2026-06-17 override — control text follows the typographic scale 1:1).
Hence ✅ (canon + enforcement + consumption).
### Mental model
| System | Token philosophy |
|---|---|
| Eidos | 7 layers of indirection, scope-as-contract, perceptual integration |
| Radix Themes | 3 layers, runtime accent swap, no scope contract |
| Chakra Panda | Build-time conditional values, a recipe system |
| Mantine | Runtime theme provider, string interpolation |
| shadcn | Flat `--primary` + `.dark`, copy-paste components |
| Tailwind v4 | The `@theme` directive, atomic utilities, no composed tokens |
**Critical read**: Eidos is NOT simpler than Tailwind nor more ergonomic than
shadcn. It is **more expressive** on the dimension "what a component can
communicate". If your app only needs a primary color and a dark mode, shadcn
is the answer. If your app needs to perceptually differentiate "save draft"
(affirm) from "delete permanently" (loss) with distinct tokens and
animations, Eidos is the system.
---
## FAQ — controversial decisions
### Why doesn't theming live in Morfo? Shouldn't Morfo be the source of truth for everything?
A CRITICAL question — the detailed answer is
[`THEMING.md`](./reference.md) §1.bis.
Summary: morfo is the source of truth of the **cross-layer contract** (parts,
events, attrs, archetypes, attr values). Tokens/themes/recipes belong to
Eidos by explicit architectural design
([`architecture/active-architecture.md`](../architecture/active-architecture.md)
§9). The 2-of-3 rule derives it: visual tokens are consumed by eidos alone →
1-of-3 → they don't enter morfo. The eidos↔morfo integration happens
**through the DOM** (recipes target the attrs morfo declares), NOT by
importing morfo objects in TS (rule #6 forbids it explicitly).
### Why 7 layers of indirection? It looks excessive
Each layer serves a real override point:
- Without layer 1, you can't bring a custom Radix palette.
- Without layer 2, you can't remap roles.
- Without layer 3, you can't tune slots per role.
- Without layer 4, you can't override a color for ONE component only.
- Without layer 5, you can't have a per-instance dynamic palette.
- Without layer 6, you can't combine variant × palette.
- Without layer 7, recipes would mix external and internal tokens.
Layer 4 is the most redundancy-suspect. It is a deprecation candidate if
after 6 months no consumer uses it.
### Why the TSC and not just convention?
**Convention fails silently.** The pre-TSC Toggle bug would have stayed
hidden for years. With the TSC, the build fails. It is the difference
between "you should do it right" and "you cannot do it wrong".
### Why not use Tailwind since it's smaller?
Tailwind:
- Has no canonical semantic roles (success/danger/warning exist but they are
Bootstrap-world).
- Has no perceptual layer (sema).
- Has no runtime density.
- Has no programmatic contract introspection.
But if your app is simple, **use it**. Eidos justifies its complexity only
when the app needs the dimensions Eidos covers.
### Why not atomic classes like Tailwind?
Custom properties enable:
- **Dynamic cascade** (runtime palette overrides).
- **Theme switching** without a recompile.
- **Persistence** of the user's theme.
- **Composability** with sema (the `event:*` scope).
Atomic classes compress better but are static. It is impossible to make
`--palette-solid` change with `data-color='affirm'` from atomic classes
without generating ×N variants at build time.
### Why invent the "TSC" instead of using CSS's native @scope?
`@scope` (CSS Cascading Modules L6) is bleeding-edge: Chrome 118+, Firefox
128+, Safari not yet. Not production-ready in 2026.
When @scope is universal, the TSC could be re-implemented on top of it. The
config surface (declarations[] + scope) would stay the same; only the emitted
CSS would change.
### Why `event:` in the TSC instead of the motion doc's `data-motion-ref`?
Three reasons:
1. **The TSC already exists and works.** `data-motion-ref` would require a
new DOM attribute, a runtime to inject it, a separate registry.
2. **Sema already emits `data-event`.** Reusing it costs zero architecture.
3. **Composable**: `scope: ['event:announce', 'color:affirm']` lets the
animation differ by valence. `data-motion-ref` would lose that or need
more complex keys.
### What's next?
Deferred pendings:
- Migrate the 5 palette consumers (button, checkbox, switch, radio-group,
toggle-group) to `declarations[]` for uniformity.
- Implement the first real `scope: 'event:*'` case (e.g. a toast
bg-during-announce).
- Decide whether to collapse layer 4 (component-color) — deferred until a
consumer asks for that extension point.

Powered by TurnKey Linux.