Repository navigation
v2.0.0
π Whats New: The Ultimate Upgrade
First release of the modernised Stack In Card β a complete rewrite of the 2020-vintage stack-in-card@0.2.0 on a current Home Assistant frontend stack (HA 2025.1+), with a HA-native visual editor and per-card custom CSS support.
Existing YAML configurations from
0.2.xcontinue to work unchanged. The only addition is the optionalstack_in_card_stylesfield β either at the top level (for the outer "stack card") or on each child card. The field is namespaced to avoid clashing with cards like bubble-cardandbutton-cardthat use a plainstyles:` key for their own styling system.
β¨ Highlights
Visual editor β matches HA's own stack-card editor 1:1
- Native
<ha-tab-group>tab strip for switching between child cards (falls back to styled buttons on HA versions without it) - Action row with GUI/YAML toggle, RTL-aware move-before / move-after, copy, cut, delete β same icons + localizations as HA's built-in editor
- Embedded
<hui-card-picker>for adding cards inline (no modal popup, noshow-create-card-dialoground-trip) - Inline Paste-from-clipboard entry above the picker β shares HA's
dashboardCardClipboardsessionStorage key, so cards copied from any HA editor (built-in stack, this card) can be pasted here - Embedded
<hui-card-element-editor>per child, re-mounted on every reorder/insert/delete via Lit'skeyed()directive β same mechanism HA itself uses; reorder actually persists in YAML - Empty stack starts in the picker β adding the card drops the user straight into card selection, no placeholder children to delete first
- Empty-state placeholder β when the stack has no children, the card renders a "Stack In Card β Add child cards from the editor" placeholder synchronously, so the picker preview tile never spins
Custom CSS support
stack_in_card_stylesat the top level β CSS applied to the outer stack card (theha-cardwrapper itself)stack_in_card_styleson each child card config β CSS injected into that child's shadow DOM only, doesn't leak to sibling cards- Namespaced field name avoids clashing with cards like
bubble-card/button-cardthat use a plainstyles:key for their own styling - The visual editor exposes both via
<ha-code-editor mode="yaml">withautocomplete-entities/autocomplete-icons - When a child is reordered, copied, or pasted, its
stack_in_card_stylesfield travels with it β no parallel index array to keep in sync
Add-card flow
+button opens the embedded<hui-card-picker>inline (HA's own pattern)- Full picker UX: built-in cards, custom cards, HACS cards β exactly what the main dashboard shows
π Modernised stack
| Concern | Before (0.2.0) | Now (2.0.0) |
|---|---|---|
| Framework | lit-element@^2.4 + lit-html@^1.3 |
lit@^3.2 |
| TypeScript | 4.0.5 |
5.7.x |
| Build target | ES2017 |
ES2021 (lib ES2022) |
| Bundler | rollup@^2 + rollup-plugin-typescript2 |
rollup@^4 + @rollup/plugin-typescript |
| Minifier | rollup-plugin-terser (deprecated) |
@rollup/plugin-terser |
| Transpiler | Babel + multiple plugins | Removed (TypeScript handles it) |
| HA bindings | custom-card-helpers@^1.6 (unmaintained) |
Local minimal types |
| Linter | ESLint 7 + airbnb + broken prettier plugin | Prettier 3 only |
| Icons | <ha-icon icon="mdi:..."> (legacy) |
<ha-icon-button .path=${mdiX}> with @mdi/js |
| CI | actions/checkout@v1, yarn |
actions/checkout@v4, Node 20, npm |
Build output dist/stack-in-card.js is a single ES module (~48 KB minified) β HACS install paths are unchanged.
π Bug fixes carried in from the modernisation
- Element double-registration guard β loading the bundle twice (HACS + manual resource) no longer throws
NotSupportedError - Style application via
MutationObserverβ replaces the fragilesetTimeout(500)of the original; late-mounted nested cards
(mushroom,button-card) get their styles correctly - Race-condition guard β monotonic generation counter on async stack creation; rapid config updates from the editor can no longer overwrite each other
- Brute-force per-child CSS injection β
walkShadowAndLightwrites into every shadow root + light-DOM node under a child, with retry, so cards that mount their internalha-cardasynchronously still get their CSS - No-op
config-changedevents filtered β HA's nested<hui-card-element-editor>re-firesconfig-changedon focus/blur
with an identical config._childCardConfigChangednow short-circuits when nothing changed β eliminates a full inner-stack rebuild per keystroke updated()gated on_card/_configchanges β future@stateadditions won't silently re-trigger full style passesMutationObserverdisconnected during DOM writes β the observer is paused around our own style/CSS injections and reconnected afterwards. Replaces an earlier reentrancy boolean that could block legitimate style passes during a config swapcards: []accepted β empty stack is a valid config (renders the empty-state placeholder)- Delete last child β falls back to the picker instead of throwing
- Validation of
modeandcardsshape insetConfig customCards.typewithoutcustom:prefix β HA's picker callsdocument.createElement(type)directly; the prefix would fail
silently and hang the preview tile- Cleanup on disconnect β animation frames, mutation observer, and card promise all torn down in
disconnectedCallback - No-op no longer disabled β Cut + Delete are no longer disabled when at one child, since
cards: []is now valid outer_paddingtautology cleanup βouter_padding ?? (margin ? true : false)βouter_padding ?? !!margin
π Performance & stability
- Mutation bursts debounced β
MutationObservercallbacks from live-updating children (history-graph,mini-graph-card,
animations) used to schedule a full style re-walk per mutation, with onlyrequestAnimationFramedebouncing. A history-graph
card refreshing several times a second could pin the main thread re-walking the whole subtree. Bursts are now collapsed via an
additional 150 mssetTimeoutdebounce; "real" updates from_card/_configchanges still go through rAF only for snappy
first paint. - Mutation filter on element nodes only β observer callback now short-circuits on the first added element node and ignores text /
comment / attribute mutations. Eliminates the long tail of "tens of mutations per second, none of them introduce a newha-card" wakeups. - CSS-injection retry cap tightened β the per-child CSS injection fallback used to retry up to 10 times at 500 ms (= 5 s of repeated
walkShadowAndLightcalls) when no shadow root had mounted yet. Capped to 3 Γ 200 ms and guarded withisConnected, so a stuck card can no longer spamwalkShadowAndLightlong after it gave up. - Picker null-deref fix β
_appendCardused to flip_showPicker = falsesynchronously inside the click handler, which
unmounted<hui-card-picker>while its ownupdated()pass was still running, triggering aCannot read properties of null (getElementById)deref athui-card-picker.ts:286. The unmount is now deferred viarequestAnimationFrameso the picker can
finish its own update cycle first.
π CI / packaging
- HACS-compliant repo layout β
dist/stack-in-card.jsis committed to master so HACS finds the built file when installing from the repo. No more "Repository structure for master is not compliant" error when adding the repo as a HACS custom plugin. - Auto-build workflow β
.github/workflows/build.ymlruns on every push to master: typecheck β build β commitdist/back. No manual rebuild step needed. - Tagged-release workflow β
.github/workflows/release.ymlruns onv*tag push, builds, and creates a GitHub Release with
stack-in-card.jsattached as an asset (softprops/action-gh-release). - HACS validation workflow β
.github/workflows/hacs.ymlruns the officialhacs/actionon every push to verify the repo stays
HACS-compliant. package-lock.jsonis committed (reproducible builds, npm best practice).
π§ Code quality
- Shared helpers in
helpers.tsβstripStackInCardFields,walkShadowAndLight,deepClone,createCardElement,
computeCardSize. The samewalkShadowAndLightwalker drives both per-child CSS injection and cleanup, replacing two near-identical recursive closures. _appendCardhelper β_handleCardPickedand_handlePasteClipboardwere line-for-line identical after card
extraction. Both route through a single helper now.- Removed all dead code from the 2020 base (
.devcontainer/,.vscode/,.npmrc,docs/Example.png,rollup-plugin-typescript2
cache directory).
π Backwards compatibility
All 0.2.x YAML configurations remain valid. New optional fields:
stack_in_card_stylesat the top of the config (string) β CSS for the outer stackstack_in_card_styleson individualcards[i](string) β CSS for that child card
The field name is namespaced (rather than plain styles:) to avoid clashing with cards that use styles: as their own configuration key β
notably bubble-card (string-style CSS) and button-card (object-style sections like card:, name:, icon:).