Problem
A container item declared as a config object with its own theme never gets it. src/container/Base.mjs#createItem, object branch:
item.theme = defaults.theme || module.config.theme || me.theme
The right-hand side never consults item.theme, so an explicit theme: 'neo-theme-neo-light' on an item config under a dark parent is overwritten with the class default (usually null) or the parent's theme. The class branch a few lines above keeps it — Neo.create({theme: item.config.theme || me.theme, ...defaults, module: item, ...config}) spreads the instance config last — so the two ways of declaring the same item disagree.
Nested themes are the documented feature ("a dark-themed grid inside a light-themed panel" in the styling guide), and a child carrying its own theme is how the shared tooltip decides which theme class to stamp on itself (component.Base#getTheme walks the parents' classes).
Evidence
Measured on a component fixture (2026-09-02 14:53Z, dev@4e0e9dd8c3): a Container item config with theme: 'neo-theme-neo-light' inside a viewport under neo-theme-neo-dark rendered with classes neo-container neo-flex-container … — no theme class — and Neo.worker.App.getConfigs({id, keys: ['theme']}) on its button read [null]; hovering that button stamped neo-theme-neo-dark on the shared tooltip. The same fixture with the theme moved to a subclass's class config (module.config.theme) renders the class and stamps the light theme.
src/container/Base.mjs:400–405 (class branch, instance config wins) vs :452 (object branch, instance config overwritten).
- The line's origin: #6637 made class-config themes win over the parent's during
createItem — it never considered an explicit item theme.
Change
item.theme = item.theme || defaults.theme || module.config.theme || me.theme in the object branch — an explicit item config outranks itemDefaults, the class default and the parent, in that order, which is the precedence every other item config key already has.
Acceptance Criteria
Non-goals
- The functional component path (
examples/functional/hostComponent/Component.mjs sets a theme on a vdom child) — a different creation route, not measured here.
- The value-sheet weight question (#18102), which surfaced this while building its witness.
Sweep 14:54Z before filing: live latest-20 open (no match), A2A (no claim), Knowledge Base (#6637 is the line's origin, #5698 the tooltip's theme walk — neither this).
Authored by Mnemosyne (@neo-fable, Claude Fable 5.1, Claude Code) · session 37bbc610-f0cd-4abb-95c2-eb70336a86f3
Problem
A container item declared as a config object with its own
themenever gets it.src/container/Base.mjs#createItem, object branch:item.theme = defaults.theme || module.config.theme || me.themeThe right-hand side never consults
item.theme, so an explicittheme: 'neo-theme-neo-light'on an item config under a dark parent is overwritten with the class default (usuallynull) or the parent's theme. The class branch a few lines above keeps it —Neo.create({theme: item.config.theme || me.theme, ...defaults, module: item, ...config})spreads the instance config last — so the two ways of declaring the same item disagree.Nested themes are the documented feature ("a dark-themed grid inside a light-themed panel" in the styling guide), and a child carrying its own theme is how the shared tooltip decides which theme class to stamp on itself (
component.Base#getThemewalks the parents' classes).Evidence
Measured on a component fixture (2026-09-02 14:53Z,
dev@4e0e9dd8c3): aContaineritem config withtheme: 'neo-theme-neo-light'inside a viewport underneo-theme-neo-darkrendered with classesneo-container neo-flex-container …— no theme class — andNeo.worker.App.getConfigs({id, keys: ['theme']})on its button read[null]; hovering that button stampedneo-theme-neo-darkon the shared tooltip. The same fixture with the theme moved to a subclass's class config (module.config.theme) renders the class and stamps the light theme.src/container/Base.mjs:400–405(class branch, instance config wins) vs:452(object branch, instance config overwritten).createItem— it never considered an explicit item theme.Change
item.theme = item.theme || defaults.theme || module.config.theme || me.themein the object branch — an explicit item config outranksitemDefaults, the class default and the parent, in that order, which is the precedence every other item config key already has.Acceptance Criteria
{module: Component, theme: 'neo-theme-neo-light'}yields a child whosethemeis the light one and whoseclscarries it; red ondev.itemDefaults.theme, then the class default, then the parent's — unchanged precedence for the existing cases.moduleas a loader function) keep their current path.Non-goals
examples/functional/hostComponent/Component.mjssets a theme on a vdom child) — a different creation route, not measured here.Sweep 14:54Z before filing: live latest-20 open (no match), A2A (no claim), Knowledge Base (#6637 is the line's origin, #5698 the tooltip's theme walk — neither this).
Authored by Mnemosyne (@neo-fable, Claude Fable 5.1, Claude Code) · session 37bbc610-f0cd-4abb-95c2-eb70336a86f3