Skip to content

Toggle

One input_boolean.* or switch.*, used as a mode: Guest mode, Do Not Disturb, Away.

import { useHaToggle } from "@dashhaus/core";
/** A mode as a switch: Guest mode, Do Not Disturb, an input_boolean or a switch. */
export function ModeSwitch({ entityId }: { entityId: string }) {
const { entity, actions } = useHaToggle(entityId);
return (
<button
type="button"
role="switch"
aria-checked={entity.state === "on"}
disabled={entity.state === "unavailable"}
onClick={actions.onToggle}
>
{entity.name}
</button>
);
}

onToggle calls toggle on the entity’s own domain, input_boolean.toggle or switch.toggle.

<HaToggle> is useHaToggle 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: HaToggleResult) => ReactNode
Draws the hook's result. The component itself draws nothing.
useHaToggle(entityId: string, options?: HaToggleOptions): HaToggleResult

One live input_boolean or switch for any UI: the mapped entity, today's history and its toggle.

It reads the entity, its area and today’s history. icon and description are yours to give: Home Assistant has no such attributes for a mode.

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.
icon?ToggleIcon
description?string
history?ToggleHistory
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.
entityToggleEntity
historyToggleHistory
actionsToggleHandlers
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 applies: "Whole home", "Bedroom".
state"unavailable" | "off" | "on"
icon?ToggleIcon
until?string
Local time it switches itself off, when it was turned on for a while ("07:00"). The hook doesn't set it: Home Assistant has no timed-on service for these domains, so a host that times a mode supplies it.
duration?string
Which duration option it was turned on with; shown as selected in the sheet. Host-owned, like `until`.
description?string
One line on what it does, shown in the sheet.
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[]
1 for hours it was on, 0 off, "unavailable", null for hours ahead. Fractions for part of an hour.
currentHournumber
onMinutesnumber
Minutes on today, including the current hour.
onToggle() => void
Calls `input_boolean.toggle` or `switch.toggle`. An unavailable entity, or one from another domain, reports `unsupported`.

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 { HaToggle, useHaSelector } from "@dashhaus/core";
import type { HaSnapshot } from "@dashhaus/core";
/** Every input_boolean: the house's modes. Switches are often devices, so this list leaves them out. */
const modeIds = (snapshot: HaSnapshot) =>
Object.keys(snapshot.entities).filter((id) => id.startsWith("input_boolean.")).sort().join(" ");
export function AllModes() {
const ids = useHaSelector(modeIds).split(" ").filter(Boolean);
return (
<ul>
{ids.map((id) => (
<li key={id}>
<HaToggle entityId={id} loadHistory={false}>
{({ entity, actions }) => (
<button type="button" role="switch" aria-checked={entity.state === "on"} onClick={actions.onToggle}>
{entity.name}
</button>
)}
</HaToggle>
</li>
))}
</ul>
);
}
import { useEffect, useState } from "react";
import { useHaToggle } from "@dashhaus/core";
/**
* "On for an hour". Home Assistant has no timed-on service for an input_boolean or a switch, so the timer is yours:
* this one turns the mode off again from the page, and only while the page stays open.
*/
export function OnForAnHour({ entityId }: { entityId: string }) {
const { entity, actions } = useHaToggle(entityId, { loadHistory: false });
const [until, setUntil] = useState<number>();
const on = entity.state === "on";
useEffect(() => {
if (until == null || !on) return;
const timer = setTimeout(() => {
actions.onToggle();
setUntil(undefined);
}, until - Date.now());
return () => clearTimeout(timer);
}, [until, on, actions]);
return (
<button
type="button"
disabled={on || entity.state === "unavailable"}
onClick={() => {
actions.onToggle();
setUntil(Date.now() + 60 * 60 * 1000);
}}
>
{entity.name} for an hour
</button>
);
}

There is no onTurnOnFor in the actions on purpose: Home Assistant can’t turn an input_boolean or a switch on for a while, so the timer belongs to whoever runs it. Orb’s tile and Field’s module take your durations and onTurnOnFor and draw the choices. A timer in Home Assistant itself, an automation or a timer helper, outlives the page; this one doesn’t.

Orb’s HaToggleTile and Field’s HaToggleModule are useHaToggle above each skin’s card. They take the hook’s options as props, plus their own:

import { HaToggleTile } from "@dashhaus/orb";
import { HaToggleModule } from "@dashhaus/field";
export function OrbGuestMode() {
return <HaToggleTile entityId="input_boolean.guest_mode" icon="guest" footprint="compact" />;
}
export function FieldDoNotDisturb() {
return <HaToggleModule entityId="input_boolean.do_not_disturb" icon="bell-off" description="Silences the chimes." />;
}
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
durations?DurationOption[]
Sheet: ways to turn it on. Omit `onTurnOnFor` to hide them.
onTurnOnFor?(value: string) => void
Sheet: turn on (or keep on) with one of `durations`.

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