Skip to content

Numeric

One sensor.* entity with a number: CO₂ in ppm, power in W, energy in kWh, or anything else a sensor measures. One card for all of them: its device class and its unit choose its words, its icon and how its figure is written. Read-only.

import { useHaNumeric } from "@dashhaus/core";
import { numericFigure, withUnit } from "@dashhaus/core/entities/numeric/readings";
import { numericWords } from "@dashhaus/core/entities/numeric/words";
/** A room's CO₂ in its own words: what it measures, and the figure in its unit. */
export function LivingRoomCo2() {
const { entity } = useHaNumeric("sensor.living_room_co2", { loadHistory: false });
if (entity.state === "unavailable") return <p>{entity.name} is unavailable</p>;
if (entity.value == null) return <p>{entity.name}: no reading yet</p>;
const { label } = numericWords(entity.deviceClass);
return <p>{entity.name}{label && ` (${label})`}: {withUnit(numericFigure(entity.value), entity.unit)}</p>;
}

A sensor’s domain says nothing of what it measures, so the card reads its device_class. numericWords names it, short for a line (CO₂, Power) and long for small print (Carbon dioxide). numericFigure writes the figure in five places at most: 650, 21.4, 0.42, and beyond five, scaled (123k, 1.2M), so a card never cuts a digit. withUnit puts a symbol on the figure (41%) and a word after a space (650 ppm). A sensor Home Assistant can’t reach is unavailable, and a state that isn’t a number is no reading, never a made-up value.

The sensor card stays the default for a room’s temperature and humidity. On a dashboard, a sensor.* listed on its own draws this card when it says it measures something else: a device class such as CO₂ or power, a unit, or a state class. card: numeric picks it for any sensor, and card: sensor keeps the room sensor. Only what the sensor declares is read, never its value, so a sensor that goes offline keeps its card. A dashboard laid out for you uses it for the room’s air quality (CO₂, particles, VOC), one per device, and, when you set up Home Assistant’s Energy dashboard, for the sensors you chose there: the grid’s, solar’s and batteries’ power now, then their energy meters, in a last row called Energy. Add any other power, energy or reading yourself, since every smart plug has them.

The card never decides on its own that a number is too high: a CO₂ or power limit of its own would be a guess. Set warn_above or warn_below, in the sensor’s own unit, and the card says when the reading crosses it. Set both for a band, warn_below under warn_above. Orb and Field take the same keys.

type: custom:dashhaus-orb
cards:
- entity: sensor.bedroom_co2
card: numeric
warn_above: 1200 # warns while it reads over 1200 ppm
- entity: sensor.freezer_temperature
card: numeric
warn_below: -25
warn_above: -15 # warns outside the band, either way

Past a limit, the card says so in place of what it measures, behind a warning sign: High CO₂, Low voltage, Too cold for a temperature, and Too high or Too low where its kind has no short word. A screen reader hears the card’s name, then “Warning: Carbon dioxide above 1200 ppm”. A reading at the limit doesn’t warn. The details always say the limits you set (“Warns above 1200 ppm”), so you can check why it warns. In React they are useHaNumeric’s warnAbove and warnBelow, and numericWarning says whether a reading is past one.

It is a look-when-convenient warning, never an alarm: the figure keeps its colour, nothing glows and Field’s screen never turns light. The card shows it only while the reading is past the limit, with no delay or hysteresis, so a reading that wavers around it comes and goes. For a warning that must reach you (a phone notification, an automation, a freezer that has failed), make a Threshold helper in Home Assistant (Settings → Devices & services → Helpers) and automate on it. Its binary sensor can sit on the dashboard too, beside the reading.

<HaNumeric> is useHaNumeric as a component that draws nothing of its own. It takes every option of the hook as a prop, and hands the hook’s result to its child function. Use it where hooks can’t go, such as one per item of a list.

entityIdstring
The Home Assistant entity id.
children(live: HaNumericResult) => ReactNode
Draws the hook's result. The component itself draws nothing.
useHaNumeric(entityId: string, options?: HaNumericOptions): HaNumericResult

One live, read-only numeric reading (CO₂, power, energy) for any UI: the mapped entity and today's history. It sends no services.

It reads the sensor, its area, and today’s readings by hour.

name?string
area?string
Overrides the area Home Assistant's registries resolve.
unit?string
Use when the raw entity omits its unit.
lastSeen?string
warnAbove?number
The limits it warns past, in its own unit (YAML `warn_above`, `warn_below`): over `warnAbove`, or under `warnBelow`, the card warns, advisory (`numericWarning`). None set, it never warns.
warnBelow?number
history?NumericHistory
Host-owned history override; when omitted, today's history is read from Home Assistant.
loadHistory?boolean
Set false to skip the Home Assistant history read.
entityNumericEntity
historyNumericHistory
entityIdstring
The Home Assistant entity id.
namestring
Its name: the `name` option, else Home Assistant's friendly name, else its entity id.
area?string
Where it is: "Living room".
state"unavailable" | "ok"
"unavailable" while the device can't be reached; "ok" otherwise, even before a first reading.
value?number
The measured value; omitted while there is no reading.
unit?string
As Home Assistant reports it: "ppm", "W", "kWh", "%"; none for a bare count.
deviceClass?NumericClass
What it measures (HA `device_class`); none is a plain reading.
stateClass?NumericStateClass
A measurement now, or a running total (HA `state_class`).
lastUpdated?string
When it last reported (HA `last_updated`), formatted.
lastSeen?string
While it is unavailable, when it was last reachable.
warnAbove?number
The limits it warns past, in its own unit, as the person set them (YAML `warn_above` and `warn_below`, the hook's `warnAbove` and `warnBelow`): over `warnAbove`, or under `warnBelow`, it warns (`numericWarning`). With none set it never warns: a card never invents a threshold.
warnBelow?number

Today, from Home Assistant’s recorder, kept up to date by every change since: one reading per hour, carried forward. Pass history to supply your own, or loadHistory={false} to skip the read.

readingsReading[]
One reading per hour, carried forward; "unavailable", or null for hours ahead and before the first reading.
currentHournumber

numericToday says the day the same way in every skin. A measurement gives its low and high. A meter that only climbs (state_class: total_increasing, such as energy or water) gives how far it rose since the day’s first reading, since its low and high would only be midnight and now.

import { useHaNumeric } from "@dashhaus/core";
import { numericToday } from "@dashhaus/core/entities/numeric/readings";
/**
* Today in one line: a measurement's low and high ("Low 470 ppm · High 940 ppm"), or how far a meter that only climbs
* rose since the day's first reading ("+8.6 kWh today").
*/
export function EnergyToday() {
const { entity, history } = useHaNumeric("sensor.house_energy");
return <p>{entity.name}: {numericToday(entity, history).long}</p>;
}

Orb’s HaNumericTile and Field’s HaNumericModule are useHaNumeric above each skin’s card. Each draws the picture its device class names (air, power, a meter or a gauge), the figure in its unit, and today. Neither warns unless you set a limit (warnAbove, warnBelow). They take the hook’s options as props, plus their own:

import { HaNumericTile } from "@dashhaus/orb";
import { HaNumericModule } from "@dashhaus/field";
/** The living room's CO₂: the cloud icon, the figure in ppm, today's low and high, and a warning over 1200 ppm. */
export function OrbCo2() {
return <HaNumericTile entityId="sensor.living_room_co2" warnAbove={1200} />;
}
/** The house's energy meter as a compact row, its unit given where the entity doesn't say it. */
export function FieldEnergy() {
return <HaNumericModule entityId="sensor.house_energy" unit="kWh" footprint="compact" />;
}
entityIdstring
showControl?boolean
Compact only. false removes the inline control, shows the value in its place, and makes the row open the details. Default true.
footprint?CardFootprint
Card footprint. The skin's stylesheet sets the heights; the three names are the shared vocabulary.
defaultOpen?boolean
defaultAction?undefined

Every state of both is in Storybook: Orb and Field.