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

9.3 KiB

title type audience status source
Eidos Theming — Comparison + decisions notes human + agent current 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 (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 §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 §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?

SUPERSEDED (2026-07-11, DOC-4). The motion REDESIGN resolved this question differently: event-driven visuals are motion SIGNATURES (EidosConfig.motion.signatures — generic per family/intent/event, generated CSS; see theming/motion.md and reference §13/§14). The scope: 'event:*' TSC route described below was never exercised and is no longer the plan. Kept as the rationale record of the moment.

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 — superseded by motion signatures (see the note above).
  • Decide whether to collapse layer 4 (component-color) — deferred until a consumer asks for that extension point (on the F4-D decision menu of the clean-room plan, 2026-07-11).

Powered by TurnKey Linux.