Skip to main content

ESPHome, Composed

Build touchscreen UIs and device configs with TSX — reusable components, real data binding, compiled to standard YAML.

Read the Documentation

Why ESPCompose?

Component-Based LVGL UIs

Build touchscreen interfaces from reusable components — Screen, Card, HStack, Button, and more.

Hot Reload, No Flashing

Iterate on your UI instantly — changes hot-reload in seconds without waiting for C++ compilation or device flashing.

Home Assistant Data Binding

Bind HA entities to UI with useHAEntity. Toggle lights, read state, control devices — no lambdas.

Type-Safe Everything

Auto-generated types for every ESPHome platform and UI component. Your IDE catches errors at build time.

Theming & Layout

Built-in dark and light themes, flex layouts, and a design system you can customize or extend.

Live Home Assistant Data

Preview your UI locally with `--host` and test with real Home Assistant entities, real state, real interactions.

Targets ESPHome Directly

Generates standard ESPHome YAML and triggers the compiler automatically — seamless from code to device.

Built on Node.js

Tap into TypeScript and the entire NPM ecosystem — use any package or tooling to power your build pipeline.

Open Source

Fully open source and community-driven. Inspect the code, contribute features, and shape the project's future.

Before & After

ESPHome YAML
lvgl:
widgets:
- obj:
layout: flex
flex_flow: COLUMN
children:
- label:
text: "Dashboard"
- obj:
layout: flex
children:
- button:
id: office_btn
on_press:
- homeassistant.action:
action: light.toggle
data:
entity_id: light.office
children:
- label:
id: office_btn_label
text: "Office"
- button:
id: gym_btn
on_press:
- homeassistant.action:
action: light.toggle
data:
entity_id: light.gym
children:
- label:
id: gym_btn_label
text: "Gym"
binary_sensor:
- platform: homeassistant
entity_id: light.office
on_state:
- lvgl.label.update:
id: office_btn_label
text: !lambda |-
if (x) return "Office On";
return "Office Off";
- platform: homeassistant
entity_id: light.gym
on_state:
- lvgl.label.update:
id: gym_btn_label
text: !lambda |-
if (x) return "Gym On";
return "Gym Off";
ESPCompose TSX
import { useHAEntity } from "@espcompose/core";
import { Screen, VStack, Card, HStack, Button, Text } from "@espcompose/ui";
const HALight = ({ entity, text }) => {
const light = useHAEntity(entity);
return (
<Button
text={light.isOn ? `${text} On` : `${text} Off`}
onPress={() => { light.toggle(); }}
/>
);
};
export const Dashboard = ({ display }) => (
<lvgl displays={[display]}>
<Screen>
<VStack>
<Text variant="title" text="Dashboard" />
<Card>
<HStack>
<HALight entity="light.office" text="Office" />
<HALight entity="light.gym" text="Gym" />
</HStack>
</Card>
</VStack>
</Screen>
</lvgl>
);

A New System, Get Involved

ESPCompose is a new system that is evolving quickly and shaped by the community. Report bugs, suggest features, contribute code, and help guide what comes next.

Support ESPCompose

If ESPCompose saves you time or helps your projects ship faster, a small coffee keeps development moving. Your support funds new features, documentation, and maintenance.