|
|
---
|
|
|
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¹ | ⚠️ 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 | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
|
|
|
|
¹ The `--size-*` bundle exists + is emitted, but components do NOT consume it
|
|
|
directly (they re-declare their size→font mapping). The 2026-06-15 audit
|
|
|
formalized the **3 archetypes** (`control · compact · dense`, THEMING §5) and
|
|
|
added a **coherence guard** (no px/rem literals in recipe
|
|
|
`font-size-*`/`icon-size-*`). The refactor to actually *consume* the bundle
|
|
|
is deferred; the guard prevents the drift. Hence ⚠️ (canon + enforcement),
|
|
|
not ✅ (full 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.
|