Skip to content

Cover

One cover.* entity: curtains, blinds, shades, shutters, awnings, windows, garage doors, doors and gates.

import { useHaCover } from "@dashhaus/core";
export function CoverButtons({ entityId }: { entityId: string }) {
const { entity, actions } = useHaCover(entityId);
return (
<div role="group" aria-label={entity.name}>
<button type="button" onClick={actions.onOpenCover}>
Open
</button>
{entity.supportsStop && (
<button type="button" onClick={actions.onStopCover}>
Stop
</button>
)}
<button type="button" onClick={actions.onCloseCover}>
Close
</button>
<span>{entity.position == null ? entity.state : `${entity.position}% open`}</span>
</div>
);
}

Open and close work on every cover. Stop, a position and a tilt depend on what the cover reports.

<HaCover> is useHaCover 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: HaCoverResult) => ReactNode
Draws the hook's result. The component itself draws nothing.
useHaCover(entityId: string, options?: HaCoverOptions): HaCoverResult

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

It reads the cover, its area and today’s history. deviceClass is Home Assistant’s device_class, so you can draw a garage door differently from a curtain.

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?CoverHistory
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.
entityCoverEntity
historyCoverHistory
actionsCoverHandlers
entityIdstring
The Home Assistant entity id.
namestring
Its name: the `name` option, else Home Assistant's friendly name, else its entity id.
area?string
Its area: the `area` option, else the one Home Assistant's registries resolve; none when neither has one.
state"unavailable" | "open" | "closed" | "opening" | "closing" | "unknown"
`unknown` is reachable but not reporting where it is: an RF blind or a garage door with no sensor, which Home Assistant drives on an assumed state. It can be opened, closed and stopped, and it is never drawn as closed or as unavailable.
deviceClassCoverClass
position?number
current_position, 0-100. Omit when the cover only opens and closes.
tilt?number
current_tilt_position, 0-100, for blinds with slats.
supportsPositionboolean
supportsStopboolean
supportsTilt?boolean
lastChanged?string
lastSeen?string
While it is unavailable, when it was last reachable: the `lastSeen` option, else what Home Assistant reports.

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[]
How open each hour was, 0-1; "unavailable"; null for hours ahead.
currentHournumber
onOpenCover() => void
Calls `cover.open_cover`.
onCloseCover() => void
Calls `cover.close_cover`.
onStopCover() => void
Calls `cover.stop_cover`. A cover that can't stop reports `unsupported`.
onSetPosition?(position: number) => void
Only for a cover that reports a position. Calls `cover.set_cover_position`, held between 0 (closed) and 100 (open).
onSetTilt?(tilt: number) => void
Only for a cover with slats. Calls `cover.set_cover_tilt_position`, held between 0 and 100.

Every action on an unavailable entity is reported to onActionError as unsupported and never sent. A cover 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 { HaCover, useHaSelector } from "@dashhaus/core";
import type { HaSnapshot } from "@dashhaus/core";
const coverIds = (snapshot: HaSnapshot) =>
Object.keys(snapshot.entities).filter((id) => id.startsWith("cover.")).sort().join(" ");
/** Every cover, and a button that closes whichever are open. */
export function AllCovers() {
const ids = useHaSelector(coverIds).split(" ").filter(Boolean);
return (
<ul>
{ids.map((id) => (
<li key={id}>
<HaCover entityId={id} loadHistory={false}>
{({ entity, actions }) => (
<>
{entity.name} ({entity.deviceClass}): {entity.state}
{entity.state === "open" && (
<button type="button" onClick={actions.onCloseCover}>
Close
</button>
)}
</>
)}
</HaCover>
</li>
))}
</ul>
);
}
import { useHaCover } from "@dashhaus/core";
/** A position slider for a cover that reports one: 0 is closed, 100 open. */
export function Position({ entityId }: { entityId: string }) {
const { entity, actions } = useHaCover(entityId, { loadHistory: false });
const { onSetPosition } = actions;
if (!onSetPosition) return <p>{entity.name} only opens and closes.</p>;
return (
<label>
{entity.name}
<input
type="range"
min={0}
max={100}
value={entity.position ?? 0}
disabled={entity.state === "unavailable"}
onChange={(event) => onSetPosition(event.currentTarget.valueAsNumber)}
/>
</label>
);
}

onSetPosition is absent for a cover that only opens and closes; say so rather than drawing a slider that does nothing.

Orb’s HaCoverTile and Field’s HaCoverModule are useHaCover above each skin’s card. They take the hook’s options as props, plus their own:

import { HaCoverTile } from "@dashhaus/orb";
import { HaCoverModule } from "@dashhaus/field";
export function OrbCover() {
return <HaCoverTile entityId="cover.living_room" footprint="compact" />;
}
export function FieldCover() {
return <HaCoverModule entityId="cover.living_room" />;
}
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.