Skip to content

Add a card or a skin

Three generators do the registering, so a new card or skin passes npm run check and npm run test-storybook before you change a line of it. Add --dry-run to any of them to see what it would write.

Terminal window
npm run new-card -- fan --code fn --domain fan # a control: on and off, over fan.toggle
npm run new-card -- co2 --code co --domain sensor --kind reading --unit ppm # a reading: a read-only number

A control is an orb and a pill that switch the entity; a reading is a bare icon and a figure, with no services. The generator writes the whole slice, the Home Assistant side included, and registers it everywhere:

  • packages/core/src/entities/fan/ for the model, packages/core/src/ha/ for the mapper, the actions, the useHaFan hook and the HaFan headless component, and packages/core/src/seeds/fan.ts for the demo seeds every skin’s stories draw on;
  • src/skins/orb/cards/fan/ for the card, its CSS and the standard stories, and src/skins/orb/ha/HaFanTile.tsx;
  • Orb’s Lovelace card (src/skins/orb/lovelace/), its exports, and the spec’s card and deploy pages.

Then make it the real thing: the entity’s fields, the card’s drawing, the mapper’s attributes and the actions’ services, its section in spec/cards.md, and its page on this site, docs/src/content/docs/entities/fan.mdx. The page’s kind frontmatter (its sidebar group and order, a summary for the home page, the companions it reads with) is all the site needs: the home page, the Entities overview, the Hooks reference and the YAML guide list it from there and from the code, and the site’s build fails until the page exists. verify/orb-conformance.test.ts says which other step is missing.

Terminal window
npm run new-card -- <name> --code <xx>

Without --domain, for a domain whose model and hook already exist, it writes only Orb’s side: the card drawn in the card frame, its tile, stories on the domain’s seeds in core, src/skins/orb/ha/Ha<Name>Tile.tsx, and its place in Orb’s Lovelace card. Both generators read the domain from one description, the same one the standalone scaffolder’s add command reads.

Terminal window
npm run new-card -- <name> --skin field

For a domain Orb draws and Field doesn’t yet. Its model and hook already exist, so this writes only Field’s drawing and adapters: the module, its CSS, icons, tests and stories on the domain’s seeds in core, src/skins/field/ha/Ha<Name>Module.tsx, and the module’s place in Field’s Lovelace card. A domain with an action a press can give starts as a control, one key per action, where an action that asks first opens the panel on its question instead; one without starts as a reading, a glyph and today. What a domain declares it sets, a level, a choice or an on-off switch, is drawn too: a level as a fader, a choice or a switch as keys in the panel; in Orb, a level as the card’s groove and the rest as chips in its sheet. The generator refuses, saying why, a domain whose hook needs an option it can’t know. verify/field-conformance.test.ts holds the result to the same steps.

Terminal window
npm run new-skin -- canvas

It creates src/skins/canvas/, a workspace package named @dashhaus/canvas, with neutral tokens, its parts, fixed footprints, dashboard furniture, its Storybook groups, a custom:dashhaus-canvas Lovelace card, tests and spec/canvas.md, and registers it with Storybook, the bundle and the deploy config. The empty dashboard is a valid start. Add domains with the card generator:

Terminal window
npm run new-card -- light --skin canvas

A generated skin’s cards start read-only, or with the hook’s zero-argument actions, rather than inventing behaviour. Give the skin its look by replacing its tokens and parts.

The page around the cards comes with it. Its tokens key dark mode on data-canvas-theme, the skin’s own attribute, which the Lovelace card sets from Home Assistant and the Theme toolbar sets in Storybook, so another skin’s theme never leaks into it. shared/surface.ts names its web font once: the Lovelace card links it on Home Assistant’s page and Storybook links it for the skin’s stories. And CanvasActionStatus shows a command Home Assistant refused, with Retry and Dismiss, so a failed tap never goes unnoticed.

A study is not made with new-skin: a study run has a fresh agent build a skin the way someone outside the repository would, from the release tarballs, a picture or a description, and a simulated Home Assistant.

Terminal window
npm run study -- canvas --image ./reference.png # or --describe "warm paper, ink and brass"
npm run study:keep -- canvas # once the agent is done

The first packs the release, sets up a sandbox and prints the task to hand a fresh agent; the second brings the skin it built into src/skins/ as a study, with the same Storybook groups as every skin, its design rules, the agent’s report of every guess and dead end, and screenshots. Studies lists every one.

For a skin outside the repository, with no Storybook or workspace, use the published scaffolder instead: Build a skin.

Every skin’s section has the same groups in the same order: Cards, Bundles (Orb only), Dashboards, Foundations, Connected. Every card has the same stories under the same names: its own states, then Unavailable, Worst case, All states, Sizes, Footprints, Compact, Details, Details, unavailable and Details, phone. verify/storybook.test.ts checks both.