Boilerplate Card

March 3, 2026 · View on GitHub

A community-driven boilerplate of best practices for Home Assistant Lovelace custom cards.

GitHub Release License hacs_badge GitHub Activity


Overview

This project is a fully-featured starting point for building your own custom Lovelace card. It is built on modern tooling (Lit 3, TypeScript 5, Rollup 4) and demonstrates real-world patterns used in production HA custom cards:

  • Visual editor with collapsible accordion sections
  • Multiple entity support
  • Conditional card visibility based on entity states
  • Custom attribute display with label and unit overrides
  • Multiple layout modes (vertical, horizontal) and display modes (card, badge)
  • Comprehensive tap / hold / double-tap action support with repeat and haptic feedback
  • Skeleton loading state while hass initialises
  • Responsive design and theme-aware CSS custom properties

Installation

  1. Open HACS in your Home Assistant instance.
  2. Go to Frontend+ Explore & Download Repositories.
  3. Search for Boilerplate Card and click Download.
  4. Refresh your browser.

Manual

  1. Download boilerplate-card.js from the latest release.
  2. Copy it to <config>/www/boilerplate-card.js.
  3. Add a resource entry in your dashboard settings:
resources:
  - url: /local/boilerplate-card.js
    type: module

Configuration

Minimal example

type: custom:boilerplate-card
entity: light.living_room

Full example

type: custom:boilerplate-card
entity: light.living_room
name: Living Room
icon: mdi:ceiling-light
layout: vertical
display_mode: card
card_style: default
accent_color: [255, 152, 0]
attribute_limit: 3
show_timestamps: true
tap_action:
  action: toggle
hold_action:
  action: more-info
double_tap_action:
  action: navigate
  navigation_path: /lovelace/lights

Options

Core

NameTypeRequiredDescriptionDefault
typestringRequiredcustom:boilerplate-card
entitystringOptionalPrimary HA entity IDnone
namestringOptionalCard title overrideEntity friendly name
iconstringOptionalMDI icon override (e.g. mdi:lightbulb)Domain default
areastringOptionalHA area to associate with the cardnone
show_errorbooleanOptionalRender the error card template (for testing)false
show_warningbooleanOptionalRender the warning banner template (for testing)false

Actions

NameTypeRequiredDescriptionDefault
tap_actionobjectOptionalAction on single tapaction: more-info
hold_actionobjectOptionalAction on 500 ms holdaction: none
double_tap_actionobjectOptionalAction on double tapaction: none

Action object

NameTypeRequiredDescriptionDefault
actionstringRequiredmore-info toggle navigate url call-service fire-dom-event nonemore-info
navigation_pathstringOptionalPath for navigate (e.g. /lovelace/0/)none
url_pathstringOptionalURL for url action — opens in a new tabnone
servicestringOptionalService for call-service (e.g. light.turn_on)none
service_dataobjectOptionalService data payload for call-servicenone
hapticstringOptionalHaptic feedback: success warning failure light medium heavy selectionnone
repeatnumberOptionalRepeat interval in ms while held (hold_action only)none
repeat_limitnumberOptionalMaximum number of repeats (hold_action only)none

Appearance

NameTypeRequiredDescriptionDefault
layoutstringOptionalvertical (stacked) or horizontal (icon + info + actions in one row)vertical
display_modestringOptionalcard (full ha-card) or badge (compact inline chip)card
card_stylestringOptionaldefault compact detailed minimaldefault
accent_color[r, g, b]OptionalRGB accent color applied via --card-accent-colorTheme primary color

Card styles

ValueEffect
defaultStandard padding and font sizes
compactReduced padding and smaller text
detailedEnlarged text, bigger icon, extra padding
minimalEntity row only — attributes and timestamps hidden

Display

NameTypeRequiredDescriptionDefault
attribute_limitnumberOptionalMaximum number of attributes to display (0 = none). Shown as a slider (0–10) in the visual editor.3
show_timestampsbooleanOptionalShow last changed / last updated timestampstrue

Developer Guide

Prerequisites

ToolMinimum versionNotes
Node.js24Required by custom-card-helpers@2
Yarn4Managed via Corepack

TypeScript, Rollup, ESLint, and all other build tools are installed locally via yarn install — no global installs needed.

The devcontainer gives you a full HA development environment in one click with no local setup required.

  1. Open the project in VS Code.
  2. When prompted, click Reopen in Container (or run Dev Containers: Rebuild Container).
  3. A local Home Assistant instance starts automatically at http://localhost:8123.
  4. Log in with dev / dev.
  5. The built card is served from the container and hot-reloads on every save (yarn start is launched automatically).

Requires: Dev Containers extension.

Quick start — local

# 1. Clone or use the GitHub "Use this template" button
git clone https://github.com/custom-cards/boilerplate-card.git my-card
cd my-card

# 2. Install dependencies
yarn install

# 3. Verify the build works
yarn build

# 4. Start the development watcher
yarn start

Then add your local file as a Lovelace resource:

resources:
  - url: /local/boilerplate-card.js
    type: module

Copy or symlink dist/boilerplate-card.js into your HA www/ folder, or use the devcontainer where this is handled automatically.

Available scripts

CommandDescription
yarn buildLint + production bundle (minified, ES2022 output)
yarn rollupProduction bundle only (skips lint)
yarn startDevelopment watcher with hot reload (rollup --watch)
yarn lintESLint across all src/ files

Project structure

src/
├── boilerplate-card.ts          # Main card element — LitElement subclass
├── editor.ts                    # Visual editor — implements LovelaceCardEditor
├── types.ts                     # TypeScript interfaces for all config fields
├── const.ts                     # CARD_VERSION constant
├── action-handler-directive.ts  # Lit directive: tap / hold / double-tap gestures
└── localize/
    ├── localize.ts              # i18n helper
    └── languages/
        ├── en.json              # English strings
        └── nb.json              # Norwegian strings
dist/
└── boilerplate-card.js          # Build output — serve this to HA

Adapting the boilerplate for your own card

Search the codebase for TODO — every required change is marked. The key steps in order:

  1. Rename the element — change boilerplate-card everywhere: the @customElement decorator, the customCards.push entry, the editor tag name in getConfigElement, and your YAML type: field.
  2. Define config fields — add your options to BoilerplateCardConfig in types.ts.
  3. Validate and set defaults — update setConfig() in boilerplate-card.ts. Throw for missing required fields; spread sensible defaults for optional ones.
  4. Update getStubConfig — return a minimal valid config so the card picker renders something immediately without the editor.
  5. Build your render — replace _renderContent() and _renderAttributes() with your domain-specific templates.
  6. Update the visual editor — add your config fields to the relevant accordion section in editor.ts. Use ha-selector for type-safe inputs that match HA's UI conventions.
  7. Add user-facing strings — put labels and messages in src/localize/languages/en.json and reference them with localize('key').

Toolchain summary

ToolVersionRole
Lit3.2Web components framework
TypeScript5.6Type checking and compilation
Rollup4Bundler — single .js output with tree-shaking
@rollup/plugin-typescript11TypeScript integration
@rollup/plugin-terser0.4Minifier
ESLint8Linting with TypeScript and Prettier integration

Important: Rollup and Terser are both configured for ES2022 output (ecma: 2022, target: 'ES2022'). Do not lower these targets. Lit 3 uses native ES6 class syntax and the extends keyword; downgrading to ES5 causes a runtime TypeError: Class constructor cannot be invoked without 'new'.

Action handler directive

action-handler-directive.ts is a Lit directive that attaches gesture recognisers to any element. Wire it up with the actionHandler() function and listen for the @action event:

import { actionHandler } from './action-handler-directive';
import { handleAction, ActionHandlerEvent } from 'custom-card-helpers';

// In your render():
html`
  <div
    ${actionHandler({ hasHold: true, hasDoubleClick: true })}
    @action=${this._handleAction}
    tabindex="0"
  >...</div>
`

// Handler:
private _handleAction(ev: ActionHandlerEvent): void {
  handleAction(this, this.hass, this.config, ev.detail.action);
}
GestureActivated by
TapRelease before the hold threshold
HoldPress held for 500 ms
Hold + repeatFires every repeat ms while held
Double tapTwo taps within 250 ms
KeyboardEnter or Space triggers tap

Adding a new language

  1. Copy src/localize/languages/en.json to src/localize/languages/<lang>.json.
  2. Translate the values (keep all keys identical).
  3. Import and register the new translations in src/localize/localize.ts.

Contributing

  1. Fork the repository and create a feature branch from master.
  2. Run yarn build before opening a PR — all lint checks must pass.
  3. Keep PRs focused on a single change.
  4. Code style is enforced automatically by Prettier and ESLint on build.

Troubleshooting

Card not appearing after install Clear your browser cache or do a hard reload (Ctrl+Shift+R / Cmd+Shift+R).

TypeError: Class constructor cannot be invoked without 'new' Your bundler is transpiling Lit's class syntax down to ES5. Ensure rollup.config.js has terser({ ecma: 2020 }) and typescript({ compilerOptions: { target: 'ES2022' } }).

Visual editor not opening Check the browser console for import errors from the dynamic import('./editor') in getConfigElement. Also confirm the boilerplate-card-editor custom element tag matches what getConfigElement creates.

Card stuck on skeleton / not rendering shouldUpdate may be returning false before hass is ready. The safest implementation for a single entity is:

protected shouldUpdate(changedProps: PropertyValues): boolean {
  if (!this.config) return false;
  return changedProps.has('config') || changedProps.has('hass');
}

General Lovelace plugin troubleshooting See the thomasloven wiki.