Overview
If you’ve searched for “ha style” and landed in a maze of forum snippets, this guide is your map. Home Assistant card-mod styling lets you customize Lovelace dashboards beyond the theme system—tuning spacing, borders, fonts, and state-driven highlights at fine-grained scope.
You’ll learn when to use card-mod versus themes or custom cards, how to install it correctly, which selectors are reliable, and how to keep your dashboards responsive, accessible, and maintainable.
We’ll proceed definition-first, then go end-to-end: setup via HACS, where styles belong, selector discovery in Shadow DOM, mobile/dark mode tactics, an accessibility/performance checklist, and risk-aware maintenance. If you’re comfortable with YAML and light CSS, you’ll leave ready to implement and troubleshoot.
For reference, see Lovelace dashboards and the card-mod repository.
The Home Assistant styling stack: ha-card, themes, CSS variables, and Shadow DOM
Before you write a single selector, understand the parts at play. Most Lovelace cards render inside a host element with an ha-card container. That’s your primary surface for consistent spacing, borders, and backgrounds.
Themes define CSS custom properties (variables) that cards consume. Card-mod bridges you into web components and Shadow DOM so you can target deeper elements when needed.
This stack matters because it determines what is safe and what is brittle. Theme variables travel far with minimal breakage. Deep selectors can snap during frontend updates.
Your strategy is to prefer variables for color and typography. Use ha-card for layout surfaces. Go deep only when a design truly requires it.
Where styles live: theme-level vs dashboard/view/card scope
The biggest win is placing styles at the right scope so you don’t repeat yourself. Theme-level styles apply across all dashboards and users when a theme is active. They are ideal for color tokens, fonts, and default radii.
Dashboard-level resources let you ship shared snippets to one dashboard without touching others. View-level overrides adapt a single screen (e.g., kiosk view) while keeping global rules intact.
Card-level styles are your escape hatch for special cases and state-driven tweaks.
Pick the highest scope that still meets the requirement. If you paste the same ha-card border into multiple cards, move it up to theme variables or a small shared snippet.
If one view needs denser spacing for a wall panel, scope that override to the view and leave other views untouched.
Factual baseline: CSS variables cascade and Shadow DOM boundaries
CSS variables are first-class citizens in Home Assistant. Per MDN, CSS custom properties inherit like normal properties and can be read inside components through the host. That’s why theme variables are so effective for Lovelace (MDN CSS custom properties).
Shadow DOM encapsulates styles by default. Normal CSS selectors outside a component can’t style inside it unless the component exposes parts or you inject styles via a mechanism like card-mod (MDN Shadow DOM guide).
In practice, encode color, spacing, and typography as variables at the theme or host level. Rely on card-mod only when you need to cross the Shadow DOM boundary.
Install and enable card-mod (HACS, resources, YAML vs UI)
Getting card-mod right on day one prevents the “styles not applying” spiral. Install via HACS, add it as a Lovelace resource, and verify it loads in both YAML and UI editor modes before styling anything.
- In HACS, search for “card-mod” and install the frontend resource (see HACS documentation).
- In Settings → Dashboards → Resources, add a resource pointing to /hacsfiles/lovelace-card-mod/card-mod.js as type JavaScript Module.
- If you use YAML mode for Lovelace, ensure the resource entry exists under lovelace: resources: in configuration.yaml pointing to the same path.
- Restart Home Assistant or reload resources (Settings → Dashboards → Three-dot menu → Manage resources).
- Create a quick test card with a tiny inline style (for example, card_mod: style: "ha-card { outline: 2px solid lime; }"). If you see the outline, card-mod is active.
Confirm this works in the GUI editor and survives a reload. If not, revisit the resource path and caching (browser hard refresh) before proceeding.
YAML mode and UI editor: where style: is available and how to edit safely
Card-mod works in both YAML mode and the UI editor, but you need to respect how each saves data. YAML mode centralizes configuration in files. The UI editor stores JSON under .storage.
In the UI editor, add a card, open Edit, switch to the YAML view, and insert the card_mod block. In YAML mode, keep your styles alongside card definitions or within !include files for reuse.
To avoid the UI overwriting your styles, don’t switch back and forth between visual and YAML modes on heavily customized cards. Prefer duplicating complex cards and editing the copy in YAML-only view.
Keep a backup of snippets in a dedicated folder so you can reapply them if a UI edit accidentally strips unknown keys.
Styling scope without duplication: global, dashboard, view, and card-level strategies
The difference between a tidy setup and a tangle of paste is scope. Use theme variables for design tokens and defaults.
Use dashboard-level includes for reusable structural tweaks. Reserve card-level styles for exceptions and dynamic state rules that can’t reasonably be moved up.
A practical pattern is to define tokens like --ha-card-border-radius, --ha-card-padding, and --state-critical-color in your theme. Then, in card-mod, refer to these variables instead of hard-coded values using var(--token, fallback).
This keeps color and spacing consistent and makes dark mode or brand refreshes trivial. When a kiosk view needs larger touch targets, apply a view-scoped card_mod style that scales font-size and --mdc-icon-size using relative units.
Governance tips: naming, variables, and change control
Treat your dashboard like a product, not a sandbox. Pick a short, consistent variable prefix (for example, --app-). Group tokens by purpose: color, spacing, typography, elevation.
Favor neutral names (--app-surface-1) over role names you’ll outgrow (--blue-200). Document your defaults in a single theme file and import it across dashboards.
For change control, keep styles versioned in Git with a simple branching model. Ship changes behind user-selectable themes (e.g., “Dashboard v2 Beta”) so you can test without disrupting family members.
Note breaking changes in a CHANGELOG and record the Home Assistant and card-mod versions you tested against. This basic hygiene eliminates most surprises when the frontend updates.
Selectors that work: cheat-sheet and DevTools discovery workflow
Card-mod inserts your CSS into the right place so you can target Shadow DOM safely. Selectors still matter.
The most reliable hooks are :host for the card element itself and ha-card for the visual container. Go deeper only when necessary, and validate selectors with DevTools before committing.
Use a repeatable DevTools workflow. In Chrome or Edge, open a dashboard, right-click the element you want to style, and Inspect.
Find the component (for example, hui-entities-card) and its shadow root. Validate a selector by adding it in the Styles pane via the element’s class toggle.
Once you see the effect, transpose that selector into a card_mod: style: rule. Re-test after reload to ensure load order doesn’t mask the result.
Built-in cards: entities, glance, button, tile, gauge, graph
Start with conservative, stable hooks. These patterns are less likely to break across updates and cover most “ha-card CSS” needs.
- Entities card: ha-card for padding, background, border radius; :host to adjust card-level fonts; state-based row coloring via variables (for example, --state-icon-color).
- Glance card: ha-card for layout and background; .entities is often present but can change—prefer adjusting icon/text size through variables like --mdc-icon-size or host font-size.
- Button card (built-in): ha-card handles shape and elevation; adjust label/icon size with --mdc-icon-size and font-size on :host.
- Tile card: treat ha-card as the surface; many tile internals are web components—prefer variable-based color/size changes to avoid brittle deep selectors.
- Gauge card: ha-card for container; use theme variables for primary/secondary colors; apply subtle shadows to ha-card rather than targeting the canvas internals.
- Graph card: ha-card and padding/border; leave the chart internals alone when possible; adjust the card surface and title typography via host-level styles.
Use these to cover 80% of needs. If you must go deeper—for example, targeting a specific row in entities—use attribute selectors that are resilient (target an entity-id attribute) and confirm in DevTools before relying on them.
Custom cards and rows: common patterns and pitfalls
Custom rows and cards introduce different tag names and deeper Shadow DOM. The safe pattern is the same: start with :host and ha-card.
When targeting a child element, prefer component-provided classes, CSS parts, or attributes over nth-child indices. Avoid relying on internal tag names that could change without notice.
Load order can delay style application on heavy dashboards. If a deep selector seems flaky, verify that card-mod loads as a Lovelace resource once and that the custom card has finished rendering before you judge the selector.
If a custom card exposes its own theming variables, use them first. Combine card-mod only where those variables fall short.
Variables and responsive design: var(), rem, and clamp() for fluid dashboards
Your most maintainable “ha style” uses variables and relative units. Encode design tokens as theme variables and reference them with var(--token, fallback).
Use rem to scale typography and spacing with a single root change. Use clamp() to make sizes fluid between device widths without media queries.
A practical recipe is to define --app-font-size-base on :root. Express card padding as calc(1.25rem) or clamp(0.875rem, 1vw + 0.5rem, 1.25rem).
For icons, set --mdc-icon-size at the host and let child components inherit. This keeps phones readable without bloating TV dashboards, and the same tokens carry through light/dark themes.
Device targets: phone, tablet, TV, and wall panels
Different devices drive density and touch-target decisions. Phones need larger targets and compact layouts. Tablets can show more columns.
TVs and wall panels benefit from high-contrast, low-motion designs that survive glare and distance. Start by scaling root font-size per device with a view-scoped override.
Use clamp() for tiles and icons so they grow gracefully on large screens. Kiosk browsers may have slightly different CSS and JS timing, so prefer variable-driven changes over deep selectors.
Avoid heavy animations that can stutter on embedded devices. Test on the actual hardware you’ll mount on the wall, not just your laptop.
Dark mode and theme switching via automations
Dark mode shouldn’t require duplicating every style. Put your colors in theme variables, not hard-coded values in card-mod.
Then switch themes via automations based on time, sun elevation, or user profile. The theme system is first-class in Home Assistant—follow the documented approach for reliability (Home Assistant themes documentation).
A clean pattern is to define paired tokens, like --app-surface-1 for light and a different value in the dark theme, while keeping the same variable names. Card-mod rules reference those tokens once.
Your automation simply toggles the theme. Every card inherits the correct palette without additional YAML edits.
Accessibility and performance: contrast, focus, motion, and effect costs
A beautiful dashboard that’s hard to read or slow to scroll defeats its purpose. Build an accessibility-first palette with sufficient contrast and always preserve focus visibility on interactive elements.
Keep motion optional for users who prefer reduced animations. Budget visual effects like heavy blurs and multiple box-shadows because they’re expensive on weaker devices.
Validate contrast for text on surfaces you style and ensure keyboard focus is visible on buttons and tiles. If you add micro-interactions, wrap them in a reduced-motion check so they don’t distract or drain panel CPUs.
For fundamentals and trade-offs between visual fidelity and responsiveness, follow widely accepted performance guidance.
Factual baseline: WCAG AA 4.5:1 contrast and prefers-reduced-motion
WCAG AA recommends a minimum contrast ratio of 4.5:1 for normal text to remain legible against its background (WCAG contrast minimum). Modern CSS supports the user’s system preference to reduce motion via the prefers-reduced-motion media query.
Honor it to limit non-essential animations and parallax (web.dev performance fundamentals).
Security, supportability, and maintenance
Card-mod is a community project, and deep selectors override internals the frontend doesn’t guarantee stable. That doesn’t mean “don’t use it”—it means use it strategically.
Prefer variables and ha-card-level surfaces. Track your customizations, and be ready to adjust after frontend updates. The project’s support scope is documented in the card-mod repository, so align your expectations accordingly.
Future-proof your styles by minimizing assumptions about internal markup. When you must target internals, prefer attributes or exposed CSS parts.
Test in staging after Home Assistant updates and keep a quick rollback plan. It’s safer to layer style enhancements than to make your dashboard depend on a fragile selector chain.
Versioning, rollbacks, and multi-user change management
Treat styles as code. Store themes, shared snippets, and critical dashboards in a Git repository.
Organize them with a clear directory structure (themes/, dashboards/, snippets/). Use YAML anchors and !include to reuse snippets across dashboards without duplication, and tag releases when you ship bigger changes.
For rollbacks, branch before upgrading Home Assistant. Apply the update in a test environment or a separate profile, and only merge when your selectors and variables hold up.
If multiple people use the dashboard, introduce change windows and versioned themes. You can switch users back instantly if something breaks.
Reusable style snippet library
A small library of snippets saves hours. Keep a README of what each does and the variables it relies on.
Good candidates include card radii and padding, focus styles, severity color ramps, glow/shadow presets, and subtle gradients.
Examples to consider:
- Card surface: ha-card { border-radius: var(--app-radius-md); padding: var(--app-space-3); box-shadow: var(--app-elevation-1); }
- Focus ring: :host { outline: 0; } :host(:focus-within) ha-card { box-shadow: 0 0 0 3px var(--app-focus); }
- Severity ramp: define --app-ok, --app-warn, --app-crit, then map state templates to color: var(--app-crit) to flag critical entities.
- Subtle glow: ha-card { box-shadow: 0 0 0 1px var(--app-surface-2), 0 8px 24px rgba(0,0,0,.12); }
- Rounded images/icons: ha-card img, ha-card ha-icon { border-radius: var(--app-radius-sm); }
Keep these snippets variable-driven so they adapt automatically to dark mode and different dashboards.
Troubleshooting card-mod: caching, theme reloads, and update regressions
When card-mod “does nothing,” it’s usually a resource path, caching, or scope issue. Work through a short diagnostic flow before changing selectors.
Verify the resource, bust caches, and confirm your style is attached to the card you think it is.
A quick sequence:
- Confirm card-mod is listed under Settings → Dashboards → Resources as a JavaScript Module and the path is /hacsfiles/lovelace-card-mod/card-mod.js.
- Clear the browser cache or hard refresh (Shift+Reload) and reload Lovelace resources.
- Temporarily add a loud style like ha-card { outline: 2px solid magenta; } to any card to verify global load.
- Verify your theme is active if your rules rely on variables; reselect the theme to force a reload.
- Inspect the element in DevTools and confirm your rule appears in the Styles pane. If it’s crossed out, specificity or a later rule is winning—raise specificity or move the rule to a closer scope.
Selector stopped working after update: a methodical fix checklist
- Reconfirm card-mod is loading by applying an obvious ha-card outline; if not, fix resources/caching first.
- Inspect the card in DevTools; check if the internal tag/class names changed. Rebuild the selector to target attributes or stable parts instead of tag names.
- Lower your ambition: move from deep internals back to ha-card or variable-based overrides where possible.
- Check theme variables; a renamed or removed variable can nullify your color/spacing without visible errors.
- Review release notes for the Home Assistant frontend and the card-mod project; adjust to any noted breaking changes.
- If only some cards fail, confirm that the failing ones are the same type or custom variant and that the style is applied at the correct scope (card vs view vs dashboard).
- Keep a minimal reproduction card and test it in a fresh dashboard; if it works there, a conflicting rule in your main dashboard is likely the culprit.
Decision guide: card-mod vs themes vs custom cards vs layout helpers
Use the simplest tool that achieves the design safely. Themes are your foundation for color, typography, and spacing tokens across the app.
Card-mod handles surgical tweaks to card surfaces and the occasional deep override that themes can’t reach. Custom cards provide new functionality or built-in styling controls not available in core cards.
Layout helpers arrange cards responsively without changing their internals. Choose them deliberately, not by default.
Choose themes when you need consistent brand and dark mode. Choose card-mod when a core card almost fits but needs a nudge.
Choose a custom card (e.g., Mushroom) when the component itself provides better UX and built-in options. Choose layout helpers to change density and grid behavior without touching card styles.
Combine them carefully to avoid conflicts.
Alternatives and integrations: Mushroom, Minimalist, layout-card, and mod-card
Mushroom and the Minimalist dashboard are opinionated kits that offer elegant defaults and extensive variables. They reduce your need for deep overrides.
They’re great for fast, consistent “responsive Lovelace dashboard” builds, and they expose clear theming hooks to align with your brand. Layout-card and other layout helpers handle complex grids, media queries, and density shifts at the container level—excellent complements to theme tokens.
The mod-card wrapper lets you style wrappers around cards. It’s useful for spacing between groups and view-level theming without editing each child.
Think of these as layers: themes for tokens, a design system like Mushroom for component UX, layout helpers for structure, and card-mod for final-mile polish. Avoid mixing multiple design systems unless you intentionally reconcile their variables first.
Advanced state-driven patterns and mini case studies
State-driven styles let the dashboard call attention to what matters without noise. Start with a severity map in variables—define OK, warning, and critical colors.
Map entity states to those tokens in card-level rules. Add thresholds for metrics like temperature or humidity, and include string templates for names or unit changes so users understand context at a glance.
For a climate view, scale tile size and color intensity based on temperature deviation from target. Neutral within 1°C, warm tint above, cool tint below.
For energy, highlight tiles when consumption breaches a --app-energy-peak-threshold. Tone down historical graphs for nighttime readability.
For security, apply a bright --app-crit border and focus ring to doors/windows open after a curfew time. Pair it with a strong, accessible label color.
Keep all thresholds and colors as variables so you can tune behavior globally.
Migrating from card-modder and validating changes
If you’re coming from card-modder, plan an automated pass and a manual check. Card-modder used different keys; search your repository for old keys and replace them with card_mod: and style: in one sweep.
Where you once targeted internals directly, prefer :host and ha-card first. Confirm any deep selectors in DevTools.
Validate in three steps. First, apply a global outline to ensure card-mod loads. Second, spot-check each card type—entities, glance, tile, gauge, graph—to ensure surfaces look right, and that Mushroom or other custom cards still respect your variables.
Third, remove fallback hard-coded colors if the same result is possible via theme variables. Switch to var() usage to consolidate your dark/light logic.
This ensures you land on a cleaner, more maintainable base than you started with.
By grounding your Home Assistant card-mod styling in themes, ha-card surfaces, and carefully validated selectors, you get the flexibility you want without the fragility you fear. Install cleanly through HACS, prefer variable-driven tokens, keep a small snippet library, and test changes with DevTools and version control.
With that discipline, your dashboards will remain responsive, accessible, and resilient across frontend updates. For deeper reading, consult the official docs for Lovelace, themes, card-mod, and foundational CSS resources as referenced above.