diff --git a/src/panels/lovelace/card-features/hui-valve-open-close-card-feature.ts b/src/panels/lovelace/card-features/hui-valve-open-close-card-feature.ts
new file mode 100644
index 0000000000..0912dd54bb
--- /dev/null
+++ b/src/panels/lovelace/card-features/hui-valve-open-close-card-feature.ts
@@ -0,0 +1,225 @@
+import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js";
+import { html, LitElement, nothing, css } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { classMap } from "lit/directives/class-map";
+import { styleMap } from "lit/directives/style-map";
+import { computeDomain } from "../../../common/entity/compute_domain";
+import { supportsFeature } from "../../../common/entity/supports-feature";
+import { stateColorCss } from "../../../common/entity/state_color";
+import "../../../components/ha-control-button";
+import "../../../components/ha-control-button-group";
+import "../../../components/ha-svg-icon";
+import {
+ canClose,
+ canOpen,
+ canStop,
+ ValveEntityFeature,
+ type ValveEntity,
+} from "../../../data/valve";
+import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
+import type { HomeAssistant } from "../../../types";
+import type { LovelaceCardFeature } from "../types";
+import { cardFeatureStyles } from "./common/card-feature-styles";
+import type {
+ ValveOpenCloseCardFeatureConfig,
+ LovelaceCardFeatureContext,
+} from "./types";
+import "../../../components/ha-control-switch";
+
+export const supportsValveOpenCloseCardFeature = (
+ hass: HomeAssistant,
+ context: LovelaceCardFeatureContext
+) => {
+ const stateObj = context.entity_id
+ ? hass.states[context.entity_id]
+ : undefined;
+ if (!stateObj) return false;
+ const domain = computeDomain(stateObj.entity_id);
+ return (
+ domain === "valve" &&
+ (supportsFeature(stateObj, ValveEntityFeature.OPEN) ||
+ supportsFeature(stateObj, ValveEntityFeature.CLOSE))
+ );
+};
+
+@customElement("hui-valve-open-close-card-feature")
+class HuiValveOpenCloseCardFeature
+ extends LitElement
+ implements LovelaceCardFeature
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @property({ attribute: false }) public context?: LovelaceCardFeatureContext;
+
+ @state() private _config?: ValveOpenCloseCardFeatureConfig;
+
+ private get _stateObj() {
+ if (!this.hass || !this.context || !this.context.entity_id) {
+ return undefined;
+ }
+ return this.hass.states[this.context.entity_id!] as ValveEntity | undefined;
+ }
+
+ static getStubConfig(): ValveOpenCloseCardFeatureConfig {
+ return {
+ type: "valve-open-close",
+ };
+ }
+
+ public setConfig(config: ValveOpenCloseCardFeatureConfig): void {
+ if (!config) {
+ throw new Error("Invalid configuration");
+ }
+ this._config = config;
+ }
+
+ private _onOpenValve(): void {
+ this.hass!.callService("valve", "open_valve", {
+ entity_id: this._stateObj!.entity_id,
+ });
+ }
+
+ private _onCloseValve(): void {
+ this.hass!.callService("valve", "close_valve", {
+ entity_id: this._stateObj!.entity_id,
+ });
+ }
+
+ private _onOpenTap(ev): void {
+ ev.stopPropagation();
+ this._onOpenValve();
+ }
+
+ private _onCloseTap(ev): void {
+ ev.stopPropagation();
+ this._onCloseValve();
+ }
+
+ private _onStopTap(ev): void {
+ ev.stopPropagation();
+ this.hass!.callService("valve", "stop_valve", {
+ entity_id: this._stateObj!.entity_id,
+ });
+ }
+
+ private _valueChanged(ev): void {
+ ev.stopPropagation();
+ const checked = ev.target.checked as boolean;
+
+ if (checked) {
+ this._onOpenValve();
+ } else {
+ this._onCloseValve();
+ }
+ }
+
+ protected render() {
+ if (
+ !this._config ||
+ !this.hass ||
+ !this.context ||
+ !this._stateObj ||
+ !supportsValveOpenCloseCardFeature(this.hass, this.context)
+ ) {
+ return nothing;
+ }
+
+ // Determine colors and active states for toggle-style UI
+ const openColor = stateColorCss(this._stateObj, "open");
+ const closedColor = stateColorCss(this._stateObj, "closed");
+ const openIcon = mdiValveOpen;
+ const closedIcon = mdiValveClosed;
+
+ const isOpen =
+ this._stateObj.state === "open" ||
+ this._stateObj.state === "closing" ||
+ this._stateObj.state === "opening";
+ const isClosed = this._stateObj.state === "closed";
+
+ if (
+ this._stateObj.attributes.assumed_state ||
+ this._stateObj.state === UNKNOWN
+ ) {
+ return html`
+
+ ${supportsFeature(this._stateObj, ValveEntityFeature.CLOSE)
+ ? html`
+
+
+
+ `
+ : nothing}
+ ${supportsFeature(this._stateObj, ValveEntityFeature.STOP)
+ ? html`
+
+
+
+ `
+ : nothing}
+ ${supportsFeature(this._stateObj, ValveEntityFeature.OPEN)
+ ? html`
+
+
+
+ `
+ : nothing}
+
+ `;
+ }
+
+ return html`
+
+
+ `;
+ }
+
+ static get styles() {
+ return [
+ cardFeatureStyles,
+ css`
+ ha-control-button.active {
+ --control-button-icon-color: white;
+ --control-button-background-color: var(--color);
+ --control-button-background-opacity: 1;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-valve-open-close-card-feature": HuiValveOpenCloseCardFeature;
+ }
+}
diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts
index 82625917d8..d809946e90 100644
--- a/src/panels/lovelace/card-features/types.ts
+++ b/src/panels/lovelace/card-features/types.ts
@@ -153,6 +153,10 @@ export interface VacuumCommandsCardFeatureConfig {
commands?: VacuumCommand[];
}
+export interface ValveOpenCloseCardFeatureConfig {
+ type: "valve-open-close";
+}
+
export const LAWN_MOWER_COMMANDS = ["start_pause", "dock"] as const;
export type LawnMowerCommand = (typeof LAWN_MOWER_COMMANDS)[number];
@@ -223,6 +227,7 @@ export type LovelaceCardFeatureConfig =
| ToggleCardFeatureConfig
| UpdateActionsCardFeatureConfig
| VacuumCommandsCardFeatureConfig
+ | ValveOpenCloseCardFeatureConfig
| WaterHeaterOperationModesCardFeatureConfig
| AreaControlsCardFeatureConfig;
diff --git a/src/panels/lovelace/create-element/create-card-feature-element.ts b/src/panels/lovelace/create-element/create-card-feature-element.ts
index 4a696e4cef..8fac9cef0a 100644
--- a/src/panels/lovelace/create-element/create-card-feature-element.ts
+++ b/src/panels/lovelace/create-element/create-card-feature-element.ts
@@ -28,6 +28,7 @@ import "../card-features/hui-target-temperature-card-feature";
import "../card-features/hui-toggle-card-feature";
import "../card-features/hui-update-actions-card-feature";
import "../card-features/hui-vacuum-commands-card-feature";
+import "../card-features/hui-valve-open-close-card-feature";
import "../card-features/hui-water-heater-operation-modes-card-feature";
import "../card-features/hui-area-controls-card-feature";
@@ -69,6 +70,7 @@ const TYPES = new Set([
"toggle",
"update-actions",
"vacuum-commands",
+ "valve-open-close",
"water-heater-operation-modes",
]);
diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
index afeb3ce5f6..a63690777e 100644
--- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
@@ -48,6 +48,7 @@ import { supportsTargetTemperatureCardFeature } from "../../card-features/hui-ta
import { supportsToggleCardFeature } from "../../card-features/hui-toggle-card-feature";
import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature";
import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature";
+import { supportsValveOpenCloseCardFeature } from "../../card-features/hui-valve-open-close-card-feature";
import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature";
import type {
LovelaceCardFeatureConfig,
@@ -94,6 +95,7 @@ const UI_FEATURE_TYPES = [
"toggle",
"update-actions",
"vacuum-commands",
+ "valve-open-close",
"water-heater-operation-modes",
] as const satisfies readonly FeatureType[];
@@ -155,6 +157,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
toggle: supportsToggleCardFeature,
"update-actions": supportsUpdateActionsCardFeature,
"vacuum-commands": supportsVacuumCommandsCardFeature,
+ "valve-open-close": supportsValveOpenCloseCardFeature,
"water-heater-operation-modes": supportsWaterHeaterOperationModesCardFeature,
};
diff --git a/src/translations/en.json b/src/translations/en.json
index f3c0c6a626..19749bc8c0 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -7885,6 +7885,9 @@
"return_home": "[%key:ui::dialogs::more_info_control::vacuum::return_home%]"
}
},
+ "valve-open-close": {
+ "label": "Valve open/close"
+ },
"climate-fan-modes": {
"label": "Climate fan modes",
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",