battery-state-card: a Lovelace card for the batteries in your house
Battery state card for Home Assistant
At a glance
- What is it?
- A Home Assistant custom card that lists battery levels across your devices, with filtering, bulk renaming, sorting, collapse groups, colour thresholds and a configurable secondary line. The v4 release renames a data field, makes one range function inclusive and changes how defaults merge with your config, all of which will need attention in an existing dashboard.
- Who is it for?
- battery-state-card suits a Home Assistant user with enough battery-powered sensors that the default battery card is not enough, and who wants one card that filters, renames, sorts and collapses them.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A card that started as a fork of someone else's
battery-state-card is a card for Home Assistant that shows battery levels from connected devices, that is, from entities.
The origin story is in the README and it explains the design. The card was inspired by another card, the lovelace-battery-entity by cbulock, which showed battery states. The author decided to implement his own because there was no response to pull requests from the original author, and he wanted to fix a few things and add a couple of new features.
That is worth keeping in mind when you compare the two. The features that differentiate this card are the ones the author wanted, which turns out to be filtering, bulk renaming, sorting, collapse groups and colours.
The card is a Lovelace custom card, distributed through HACS, which is the community repository manager for Home Assistant custom components. The README carries a HACS badge, a GitHub releases shield, a coveralls coverage badge for the master branch, a community forum link and a donation link.
It is a front end card in TypeScript, and the repository is a small TypeScript project: `src/`, `test/`, `scripts/`, a `rollup.config.js`, a `hacs.json`, a `coverage-jest.json` and a `web-test-runner.config.mjs`. The project is MIT licensed, the default branch is master, and the last push was on 2026-10-14.
The release history is a steady line of minor versions with a major one every few months: v4.2.0 from 2026-04-02, v4.1.0 from 2026-03-28 and v4.0.1 from 2026-03-16.
The v4 breaking changes that will rewrite your config
The README keeps a list of breaking changes per major version, and the v4 list is the one that will bite an existing user.
First, the `display` entity data field has been renamed to `entity`. If you use the `display.` prefix in filters, for example `name: "display.platform"`, you have to change it to `entity.`, and the same applies to KString references, so `{display.name}` becomes `{entity.name}`.
Second, the KString `between` function is now inclusive. Previously `between(2,6,30)` matched values strictly between 2 and 6, and now it matches from 2 to 6 inclusive. The README says that if you relied on the exclusive behaviour you should adjust your thresholds accordingly, which is the kind of change that produces a card that looks identical until a value lands exactly on a boundary.
Third, `{charging}` is now an object with `text`, a string, and `is_active`, a boolean. If you used `{charging}` in `secondary_info` or another KString, you now need `{charging.text}`, and you can use `{charging.is_active}` to get the boolean charging state.
Fourth, and the largest, the default configuration is now shallow-merged with your custom config. Previously, specifying any custom config discarded all the defaults. Now default values such as `sort`, `filter`, `collapse`, `bulk_rename`, `colors` and `secondary_info` are applied for any property you do not explicitly set. The README notes that if you relied on the old behaviour where defaults were fully replaced, you may need to explicitly override specific properties, for example `filter: {}` to disable the default filter.
That last change is the reason the config also grew a `default_config_base` option, which the option table dates to v4.1.0. It is a boolean, defaults to true, and it decides whether the default config values are used as a base for your configuration. When set to false, only user-specified settings are used, which the README says is useful when entities are managed externally, for example through the auto-entities card.
The default config, and how to reset it
The built-in defaults are shown in the README as a complete YAML block, and they are a reasonable starting point for a new dashboard.
type: "custom:battery-state-card"
secondary_info: "{last_changed}"
filter:
include:
- name: "attributes.device_class"
value: "battery"
exclude:
- name: "entity_id"
value: "binary_sensor.*"
sort:
by: "state"
collapse: 8
bulk_rename:
- from: " Battery"
- from: " level"
colors:
steps:
- '#ff0000'
- '#ffff00'
- '#00ff00'
gradient: trueReading that tells you most of the behaviour. The default filter includes entities whose `device_class` attribute is `battery`, and excludes everything matching `binary_sensor.*`, which is what stops binary sensors from appearing as rows. The default sort is by state, so the emptiest batteries come first. The default collapse is 8, so only eight rows show and the rest sit in an expandable section. The default bulk rename strips the words ` Battery` and ` level` from entity names, which is the usual outcome when a device is named `Phone Battery` or `Sensor level`. The default colours are three steps, red through yellow to green, with a gradient between them.
The README also gives the opposite configuration, the one that resets all the defaults and can be used as a base if you want to configure the card entirely yourself. It sets `secondary_info` to null, the filter, sort and colours to empty objects, `collapse` to 0, and `bulk_rename` to an empty list.
The difference between the two is now one property, because of the shallow merge. Setting `default_config_base: false` gets you the second configuration's effect.
The options table, and what each knob does
The card configuration table lists every option with its type, its default, the version it appeared in and a description, which makes it easy to see the shape of the card's history.
The `type` property is required and the table says it must be `custom:battery-state-entity`, which looks like a typo against the examples that use `custom:battery-state-card`. It is one of the two details in the documentation that are inconsistent, and the v2 breaking change explains the other one: when you want to use the card as an entity, for example inside an entities card, you need a different type, `custom:battery-state-entity`, instead of `custom:battery-state-card`.
So the table's entry is describing the entity usage, and the field names the two variants in one row.
The rest of the options are `entities`, a list of entity or group IDs which can be strings rather than entity objects, `title`, `sort`, `group` for how many entities to show before collapsing, `filter` for automatic include and exclude rules, `bulk_rename` for the rename rules, and `theme` for the name of a theme installed in Home Assistant.
Two of the date stamps are informative. `sort` is marked v3.0.0, and the v3 breaking change says the sorting setting is now called `sort` where it was previously `sort_by_level`, and that it can define multiple levels of sorting. And `theme` is v3.3.0, so theme support arrived after the sorting rework.
The group option is either a number of entities to show or a list of entity and battery groups, so the same card can either collapse a long list or split your batteries into named groups.
The entity object, and where the battery value comes from
The entity object has its own options table, and the value lookup is the part that determines whether a device appears correctly.
The `attribute` option overrides the name of the attribute to extract the value from, and the default behaviour is documented precisely: by default the card looks for values in the `attributes.battery_level` and `attributes.battery` attributes, and if they are not present it takes the entity state.
So a device that reports its level under a different attribute name, which is common across different manufacturers, needs the attribute set explicitly. The card does not guess beyond those two names.
The `multiplier` option handles the other common case, where the value is not in the 0 to 100 range. The default is 1, and the README's example is a device reporting 0 to 10, which you scale by putting 10 as the multiplier.
The `icon` option takes an icon override, so you can set a custom icon such as `mdi:battery`, use an entity attribute such as `attributes.battery_icon`, or set it to null to use the entity's own default icon. The `name` option is an entity name override, and it accepts a structured name on Home Assistant 2026.4 and later.
That structured name support is a small sign of how the card tracks the platform. A card that understands a name format introduced in a 2026 Home Assistant release is being maintained against a current platform rather than a frozen one.
Testing, coverage and the build pipeline
The `package.json` shows a two-layer test setup, and it is more thorough than most card projects.
Unit tests run under Jest with the ts-jest preset and a jsdom environment, matching the files under `test/unit`. Integration tests run under the web test runner, which loads the card in a real browser, and the `test` script runs the unit tests and then the integration tests in sequence.
Coverage is handled separately for each layer. The unit coverage writes to `coverage/unit`, the integration coverage is collected by the web test runner, and a script at `scripts/merge-coverage.mjs` merges the two into one report, with a `test:coverage` script that chains all three. The `coverage-jest.json` file at the repository root and the coveralls badge in the README are part of the same coverage setup.
The build is Rollup, with a `build` script, a `watch` script, and a separate `release` script that passes a RELEASE environment to the same config, which is how the distributable file is produced. The Rollup plugins in the dev dependencies are the node resolver, the TypeScript plugin and terser for minification.
There is one inconsistency worth knowing about. The `version` field in `package.json` is `1.0.0` while the published releases are in the v4 series, with v4.2.0 the most recent. The package version is therefore not the release version, and anyone automating an update check against the package metadata will be reading the wrong number.
Where battery-state-card is the wrong choice
The first wrong case is a dashboard that needs per-device history. The card is a list of current states with a secondary line. The `secondary_info` field takes a KString, and the v3 breaking change shows the kind of value that goes there, such as `{last_updated}` or `{last_changed}`, which is a timestamp rather than a graph. A device-by-device consumption history needs the standard history card or a sensor card, not this one.
The second is a battery forecast. There is no configuration for an estimated time remaining, a charge rate, or a time to empty, and the option table does not mention one. A card that showed a projected time remaining would be a different and much more opinionated piece of software.
The third is a heterogeneous set of devices. The default attribute lookup covers only `battery_level` and `battery`, so a mixed household with devices using different conventions needs an explicit attribute per entity. That is per-row configuration, and it does not scale to fifty devices.
The fourth is an existing dashboard that has been tuned against v3. The v4 changes are all silent in the sense that the card still renders. A renamed field, an inclusive range and a changed merge behaviour all produce a card that looks right and shows different rows. The README's own list of breaking changes is the thing to read, and then check the card rather than trusting it.
The fifth is someone who wants the card to manage their entity list. That is what `default_config_base: false` is for, and the README marks it as useful when entities are managed externally, but it is an option rather than the default.
Editorial conclusion
battery-state-card suits a Home Assistant user with enough battery-powered sensors that the default battery card is not enough, and who wants one card that filters, renames, sorts and collapses them. It does not suit a dashboard that needs a history graph per device, a battery forecast, or a charge-time estimate, none of which the configuration exposes, and it does not suit someone who expects the card to work with an arbitrary attribute naming, since the default lookup covers only `battery_level` and `battery`. Before adopting it, read the v4 breaking changes list, because the `display` to `entity` rename and the now-inclusive `between` range will silently change thresholds you wrote against the older behaviour, and set `default_config_base: false` if you manage your entity list from another card.
Frequently asked questions
How do I install the battery-state-card in Home Assistant?
It is distributed as a Lovelace custom card through HACS, and the README links the HACS badge for the repository. The card is then referenced in a dashboard with the type `custom:battery-state-card`.
What changed in battery-state-card v4?
The `display` entity data field was renamed to `entity`, the KString `between` function became inclusive, `{charging}` became an object with `text` and `is_active` properties, and the default configuration is now shallow-merged with your custom config rather than replacing it entirely.
How does the card find a device's battery level?
By default it looks for the `battery_level` and `battery` attributes, and falls back to the entity state if neither is present. The `attribute` option overrides which attribute is read, and `multiplier` scales a value that is not on a 0 to 100 scale.
How do I stop the card from showing a default filter?
Set `filter: {}` explicitly, or set `default_config_base: false` to stop using the default config as a base. The README notes the second is useful when entities are managed externally, for example by the auto-entities card.
How are the battery-state-card tests run?
Unit tests run under Jest with ts-jest and jsdom, and integration tests run under the web test runner. Coverage is collected separately for each and merged by a script in `scripts/merge-coverage.mjs`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/maxwroc-battery-state-card)Was this useful?