Skip to content

Light

One light.* entity: whether it’s on, how bright, and its warmth or colour when it has them.

import { useHaLight } from "@dashhaus/core";
export function LightButton({ entityId }: { entityId: string }) {
const { entity, actions } = useHaLight(entityId);
return (
<button type="button" aria-pressed={entity.state === "on"} onClick={actions.onToggle}>
{entity.name} {entity.state === "on" && entity.brightness != null ? `${entity.brightness}%` : entity.state}
</button>
);
}

The hook maps the raw light into a LightEntity and builds its actions. A tap calls light.toggle; the button changes when Home Assistant reports the new state.

<HaLight> is useHaLight 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: HaLightResult) => ReactNode
Draws the hook's result. The component itself draws nothing.
useHaLight(entityId: string, options?: HaLightOptions): HaLightResult

One live light for any UI: the mapped entity, today's history and its service actions.

It reads the light, its area from Home Assistant’s registries, and today’s history from the recorder.

name?string
Overrides Home Assistant's friendly name.
area?string
Overrides the area Home Assistant's registries resolve for the entity.
lastSeen?string
Overrides when the entity was last reachable, shown while it is unavailable.
history?LightHistory
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.
onActionError?(error: HaActionError) => void
Called with an HaActionError when an action is refused or fails.
entityLightEntity
historyLightHistory
actionsLightHandlers
entityIdstring
The Home Assistant entity id.
namestring
Its name: the `name` option, else Home Assistant's friendly name, else its entity id.
area?string
Area name, shown under the light's name.
stateLightState
supportsBrightnessboolean
True when the light supports brightness (HA color mode other than `onoff`).
brightness?number
Brightness in percent, 1-100 (HA `brightness` / 2.55). Absent when HA has not reported a reading.
supportsColorTemp?boolean
kelvin?number
Color temperature in Kelvin (HA `color_temp_kelvin`).
minKelvin?number
maxKelvin?number
supportsColor?boolean
True when the light takes a colour (an HA `hs`, `xy`, `rgb`, `rgbw` or `rgbww` colour mode).
hs?[number, number]
Its colour as HA `hs_color`: hue 0-360, saturation 0-100. Absent while off or showing a colour temperature.
lastChanged?string
Local time of the last state change, e.g. "10:31".
lastSeen?string
Local time the light was last reachable, shown while unavailable.

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

samplesHourSample[]
currentHournumber
Index of the current hour in `samples`.
onMinutesnumber
Minutes on today, including the current hour.
onToggle() => void
Calls `light.toggle`.
onBrightnessChange(percent: number) => void
Calls `light.turn_on` with `brightness_pct`, rounded and held between 1 and 100. A light without brightness reports `unsupported`.
onKelvinChange?(kelvin: number) => void
Only for a light that takes a colour temperature. Calls `light.turn_on` with `color_temp_kelvin`; a value outside `minKelvin` to `maxKelvin` reports `invalid_input`.
onColorChange?(hs: [number, number]) => void
Only for a colour light. Calls `light.turn_on` with `hs_color`: hue 0 to 360, saturation 0 to 100.

Every action on an unavailable entity is reported to onActionError as unsupported and never sent. A light Home Assistant reports as unknown is not unavailable: its state is "unknown", and every action is sent.

Hooks can’t be called in a loop, so a list renders one headless component per entity. The selector returns a string: useHaSelector re-renders only when its value changes, and a string of ids changes only when an entity comes or goes. Lists skip each item’s history read with loadHistory={false}.

import { HaLight, useHaSelector } from "@dashhaus/core";
import type { HaSnapshot } from "@dashhaus/core";
/** Every light in the house, as one string, so the selector's value only changes when a light comes or goes. */
const lightIds = (snapshot: HaSnapshot) =>
Object.keys(snapshot.entities).filter((id) => id.startsWith("light.")).sort().join(" ");
export function AllLights() {
const ids = useHaSelector(lightIds).split(" ").filter(Boolean);
return (
<ul>
{ids.map((id) => (
<li key={id}>
<HaLight entityId={id} loadHistory={false}>
{({ entity, actions }) => (
<button type="button" aria-pressed={entity.state === "on"} onClick={actions.onToggle}>
{entity.name}
</button>
)}
</HaLight>
</li>
))}
</ul>
);
}
import { useHaLight } from "@dashhaus/core";
/** A brightness slider, and a warmth slider when the light takes a colour temperature. */
export function Dimmer({ entityId }: { entityId: string }) {
const { entity, actions } = useHaLight(entityId, { loadHistory: false });
const unavailable = entity.state === "unavailable";
return (
<fieldset disabled={unavailable}>
<legend>{entity.name}</legend>
{entity.supportsBrightness && (
<label>
Brightness
<input
type="range"
min={1}
max={100}
value={entity.brightness ?? 1}
onChange={(event) => actions.onBrightnessChange(event.currentTarget.valueAsNumber)}
/>
</label>
)}
{actions.onKelvinChange && entity.minKelvin != null && entity.maxKelvin != null && (
<label>
Warmth
<input
type="range"
min={entity.minKelvin}
max={entity.maxKelvin}
step={100}
value={entity.kelvin ?? entity.minKelvin}
onChange={(event) => actions.onKelvinChange?.(event.currentTarget.valueAsNumber)}
/>
</label>
)}
</fieldset>
);
}

onKelvinChange and onColorChange are only there when the light can use them, so test the handler, not the light’s name or model.

import { useHaLight } from "@dashhaus/core";
/** How long a light has been on today, from the history the hook reads. */
export function OnToday({ entityId }: { entityId: string }) {
const { entity, history } = useHaLight(entityId);
const hours = Math.floor(history.onMinutes / 60);
const minutes = history.onMinutes % 60;
return (
<p>
{entity.name} has been on for {hours} h {minutes} min today.
</p>
);
}

history.onMinutes counts today’s minutes on, the current hour included. history.samples has one value per hour for a strip of your own.

Orb’s HaLightTile and Field’s HaLightModule are useHaLight above each skin’s card. They take the hook’s options as props, plus their own:

import { HaLightTile } from "@dashhaus/orb";
import { HaLightModule } from "@dashhaus/field";
export function OrbLight() {
return <HaLightTile entityId="light.living_room" footprint="featured" />;
}
export function FieldLight() {
return <HaLightModule entityId="light.living_room" footprint="compact" />;
}
entityIdstring
The Home Assistant entity id.
showControl?boolean
Compact only. false removes the inline control, shows the value in its place, and makes the row open the detail sheet. The orb keeps its tap and hold actions. 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.