Lock
One lock.* entity, with the battery sensor on its device: whether it is locked, on its way or jammed, who or what
changed it last, whether it wants a code, and whether it can unlatch the door too.
Quick example
Section titled “Quick example”import { useState } from "react";import { useHaLock } from "@dashhaus/core";
/** * Lock on one press; unlock only after asking, with the code when the lock wants one. The handlers never reject: they * answer "done", "wrong_code" or "failed", so each outcome gets its own words. */export function FrontDoor({ entityId }: { entityId: string }) { const { entity, actions } = useHaLock(entityId); const [asking, setAsking] = useState(false); const [code, setCode] = useState(""); const [answer, setAnswer] = useState("");
async function unlock() { const outcome = await actions.onUnlock(entity.codeFormat ? code : undefined); setAnswer(outcome === "wrong_code" ? "Wrong code." : outcome === "failed" ? "Home Assistant didn't take it. Try again." : ""); if (outcome === "done") setAsking(false); setCode(""); }
if (entity.state === "unavailable") return <p>{entity.name} is unavailable</p>; if (entity.state !== "locked") return <button type="button" onClick={() => void actions.onLock()}>Lock {entity.name}</button>; if (!asking) return <button type="button" onClick={() => setAsking(true)}>Unlock {entity.name}…</button>; return ( <form onSubmit={(event) => { event.preventDefault(); void unlock(); }}> <p>Unlock {entity.name}? Anyone at the door can open it.</p> {entity.codeFormat && ( <label> Code <input type="password" inputMode={entity.codeFormat === "number" ? "numeric" : "text"} autoComplete="off" value={code} onChange={(event) => setCode(event.currentTarget.value)} /> </label> )} <button type="button" onClick={() => setAsking(false)}>Cancel</button> <button type="submit">Unlock</button> <p role="status">{answer}</p> </form> );}Locking is the safe direction, so it can take one press. Unlocking never should: a stray tap, a child or a pet must not open a door, so both skins ask first and take the code when the lock wants one. Build your own controls the same way.
The handlers never reject. Each answers "done" when Home Assistant took the call, "wrong_code" when the code was
refused or can’t fit the lock’s pattern, and "failed" when the call didn’t go through; a failure also goes to
onActionError.
Component
Section titled “Component”<HaLock> is useHaLock 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: HaLockResult) => ReactNode- Draws the hook's result. The component itself draws nothing.
useHaLock(entityId: string, options?: HaLockOptions): HaLockResultOne live lock for any UI: the mapped entity composed with its device's battery, and its lock, unlock and open actions. A lock shows its last change only, so there is no history read.
It reads the lock, its area and, given batteryEntityId, the battery sensor on the same device. A lock shows its last
change, not a day’s history, so there is no history read and no history in the result.
Options
Section titled “Options”batteryEntityId?string- Optional companion on the same device, `device_class: battery` (the automatic layout finds it).
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.
onActionError?(error: HaActionError) => void- Called with an HaActionError when an action is refused or fails.
Returns
Section titled “Returns”entityLockEntityactionsLockHandlers
entity: LockEntity
Section titled “entity: LockEntity”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: "Front door".
stateLockStatesupportsOpenboolean- It can unlatch the door too (`lock.open`, `LockEntityFeature.OPEN`).
codeFormat?"number" | "text" | null- Whether a code is asked for, and what kind. Home Assistant's lock `code_format` is a pattern: one that only matches digits is "number" (a keypad), any other "text". Null or omitted: no code.
codePattern?string- Home Assistant's `code_format` pattern, so a code that can't fit is answered before it is sent.
changedBy?string- HA `changed_by`: who or what last locked or unlocked it, where the integration says.
lastChanged?string- When it last changed state (HA `last_changed`), formatted.
battery?number- The same device's battery, 0–100, where it has one.
lastSeen?string- While it is unavailable, when it was last reachable: the `lastSeen` option, else what Home Assistant reports.
Home Assistant’s code_format for a lock is a pattern, such as ^\d{4}$. codeFormat says which keyboard it wants:
number when the pattern only takes digits, text otherwise.
actions: LockHandlers
Section titled “actions: LockHandlers”onLock(code?: string | undefined) => Promise<LockOutcome>- Calls `lock.lock`, with the code when given. A tap may do this: locking is the safe direction.
onUnlock(code?: string | undefined) => Promise<LockOutcome>- Calls `lock.unlock`, with the code when given. Ask before you call it: only after a deliberate confirmation, with the code where the lock asks for one.
onOpen(code?: string | undefined) => Promise<LockOutcome>- Calls `lock.open` to unlatch the door, only where `supportsOpen`; otherwise answers `failed` with an `unsupported` error and sends nothing. Ask first, as for an unlock.
Every lock
Section titled “Every lock”Hooks can’t be called in a loop, so a list renders one headless component per entity.
import { HaLock, useHaSelector } from "@dashhaus/core";import type { HaSnapshot } from "@dashhaus/core";
const lockIds = (snapshot: HaSnapshot) => Object.keys(snapshot.entities).filter((id) => id.startsWith("lock.")).sort().join(" ");
/** Every lock in the house and how it was left: a lock has no history read, so a list costs nothing extra. */export function AllLocks() { const ids = useHaSelector(lockIds).split(" ").filter(Boolean); return ( <ul> {ids.map((id) => ( <li key={id}> <HaLock entityId={id}> {({ entity }) => ( <> {entity.name}: {entity.state} {entity.changedBy ? ` by ${entity.changedBy}` : ""} {entity.lastChanged ? `, ${entity.lastChanged}` : ""} </> )} </HaLock> </li> ))} </ul> );}In a skin
Section titled “In a skin”Orb’s HaLockTile and Field’s HaLockModule are useHaLock above each skin’s card. A press on a locked lock opens
its details on the question, or on the keypad; Cancel takes the focus, so Enter never unlocks. A jam is marked as a
warning, never a glow. They take the hook’s options as props, plus their own:
import { HaLockTile } from "@dashhaus/orb";import { HaLockModule } from "@dashhaus/field";
/** The front door with the battery sensor on its device. */export function OrbLock() { return <HaLockTile entityId="lock.front_door" batteryEntityId="sensor.front_door_battery" />;}
/** A lock Home Assistant can read but you shouldn't press from this panel: its state and last change, nothing to unlock. */export function FieldLock() { return <HaLockModule entityId="lock.back_door" readOnly />;}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 details. Default true.
readOnly?boolean- View only: the lock's state and last change with nothing to press. Default: on when neither onLock nor onUnlock is given.
footprint?CardFootprint- Card footprint. The skin's stylesheet sets the heights; the three names are the shared vocabulary.
defaultOpen?booleandefaultAction?NonNullable<"open" | "lock" | "unlock">
entityIdstring- The Home Assistant entity id.
readOnly?boolean- View only: the lock's state and last change, nothing pressed. Default: on when neither onLock nor onUnlock is given.
footprint?FieldFootprintdefaultOpen?boolean- Open the detail panel on first render (stories).