A modular component library for building touchscreen smart home control panels using ESPHome + LVGL on cheap ESP32 displays.
See ARCHITECTURE.md for the full design rationale.
Each entity type lives in ui/<type>/ and provides:
ui/<type>/local.yaml — tile for an ESPHome component on the same device
ui/<type>/remote.yaml — tile for a Home Assistant entity
ui/<type>/detail.yaml — full-screen detail page (complex types only)
Your device YAML composes a panel by including one hardware file, the shared infrastructure, and one !include per tile. Everything internal (globals, scripts, detail page) is wired automatically.
ESPHome 2026.7.0 or later (the image: component uses the new platform: file form introduced in 2026.7.0). For SVG image support (required by solar/tides modules):
pip install cairosvgcd /config # or wherever your ESPHome configs live
git clone https://github.com/agillis/esphome-modular-lvgl-buttons.gitCreate secrets.yaml in your ESPHome config root (one level above this repo):
wifi_ssid: "your-ssid"
wifi_password: "your-wifi-password"
ap_password: "your-fallback-ap-password"
latitude: 0.0000
longitude: 0.0000Copy the closest example from example_code/ and adapt it, or start from scratch:
esphome run my-panel.yaml| Type | local | remote | Detail page | Notes |
|---|---|---|---|---|
light |
✅ | ✅ | ✅ | RGB / CCT / brightness, capability auto-detected |
switch |
✅ | ✅ | — | Works with any toggleable HA entity |
sensor |
✅ | ✅ | — | Configurable unit and decimal precision |
binary_sensor |
✅ | ✅ | — | Read-only — door, motion, leak |
text_sensor |
✅ | ✅ | — | Display any string state or attribute |
button |
✅ | ✅ | — | Momentary press — works with script.*, scene.* too |
climate |
✅ | ✅ | ✅ | Arc setpoint, mode + fan + swing dropdowns, capability auto-detected |
cover |
🔜 | 🔜 | 🔜 | Blinds, shutters, garage doors |
fan |
🔜 | 🔜 | 🔜 | — |
number |
🔜 | 🔜 | 🔜 | Setpoints, PID targets |
select |
🔜 | 🔜 | 🔜 | Operating modes, option lists |
media_player |
🔜 | 🔜 | 🔜 | — |
lock |
🔜 | 🔜 | 🔜 | With PIN pad detail page |
Click any type name in the table above for its full variable reference and usage examples.
| Variable | Required | Description |
|---|---|---|
uid |
✅ | Unique identifier — must be unique across your entire config |
entity_id |
✅ | ESPHome component ID (local) or HA entity e.g. "light.foo" (remote) |
row |
✅ | Grid row (0-based) |
column |
✅ | Grid column (0-based) |
text |
✅ | Tile label |
icon |
✅ | MDI icon glyph e.g. $mdi_lightbulb |
row_span |
— | Rows to span (default: 1) |
column_span |
— | Columns to span (default: 1) |
page_id |
— | Parent page ID (default: main_page) |
Pages use LVGL's grid layout. layout: NxM creates N rows × M columns.
| Layout | Tiles | Good for |
|---|---|---|
2x2 |
4 | Small displays |
2x3 |
6 | Portrait or compact |
3x3 |
9 | 480×480 square displays |
4x4 |
16 | Large landscape displays |
Tiles are placed with row and column (0-based). Use row_span / column_span to make a tile span multiple cells.
Add more pages to the lvgl.pages list, include swipe_navigation.yaml on each, and set page_id on tiles to route them to the right page:
lvgl:
pages:
- id: main_page
layout: 3x3
styles: page_style
<<: !include esphome-modular-lvgl-buttons/common/swipe_navigation.yaml
- id: lights_page
layout: 2x3
styles: page_style
<<: !include esphome-modular-lvgl-buttons/common/swipe_navigation.yamlIcons use Material Design Icons via substitution variables. Usage: icon: $mdi_lightbulb.
The icon name must also be listed as a glyph in your device font: block — otherwise it will render as a blank square:
font:
- file: 'https://github.com/Templarian/MaterialDesign-Webfont/raw/v7.4.47/fonts/materialdesignicons-webfont.ttf'
id: mdi_icons_40
size: 40
bpp: 8
glyphs:
- $mdi_lightbulb
- $mdi_ceiling_light
- $mdi_thermostat
# ... add every icon you useEach detail page type also requires specific glyphs — see the type's README.md.
The theme lives in common/theme/ and is a self-contained bundle — one include pulls in colors, fonts, MDI glyph substitutions, and LVGL styles. See the theme README for the full color palette, font sizes, and customization reference.
The common/theme/index.yaml bundle includes colors, fonts, MDI glyph substitutions, and LVGL styles in one include.
To debug layout, swap to the debug variant which adds red outlines to all widgets:
# theme: !include esphome-modular-lvgl-buttons/common/theme/index.yaml
theme: !include esphome-modular-lvgl-buttons/common/theme/index_debug.yamlTheme appearance is controlled via substitution variables:
| Variable | Default | Description |
|---|---|---|
button_on_color |
ep_orange |
Tile background when active/on |
button_off_color |
very_dark_gray |
Tile background when inactive/off |
icon_on_color |
yellow |
Icon color when active |
icon_off_color |
gray |
Icon color when inactive |
label_on_color |
white |
Label color when active |
label_off_color |
gray |
Label color when inactive |
icon_font |
mdi_icons_40 |
Font ID used for icons |
text_font |
nunito_20 |
Font ID used for labels |
Available named colors: ep_orange, ep_blue, ep_green, steel_blue, misty_blue, very_dark_gray, gray800, gray900, and all standard CSS colors.
Test your UI on macOS or Linux without flashing hardware:
# macOS
brew install sdl2
# Ubuntu/Debian
sudo apt install libsdl2-devUse the SDL hardware config instead of a real device:
packages:
hardware: !include esphome-modular-lvgl-buttons/hardware/SDL-lvgl.yaml
sensors: !include esphome-modular-lvgl-buttons/common/sensors_base_sdl.yamlThen esphome run your-config.yaml — a window opens simulating the display. See example_code/SDL-lvgl-display_modular_480px.yaml for a full working SDL config.
One hardware file per display, in hardware/. Include the one matching your
device (hardware: !include esphome-modular-lvgl-buttons/hardware/<model>.yaml). The
Driver column is the ESPHome display platform each file uses.
| Model | Size | Resolution | Driver |
|---|---|---|---|
waveshare-esp32-s3-touch-lcd-2.8c |
2.8" round | 480×480 | st7701 |
waveshare-esp32-s3-touch-lcd-3.5 |
3.5" | 320×480 | mipi_spi |
waveshare-esp32-s3-touch-lcd-3.5b |
3.5" | 480×320 | mipi_spi |
waveshare-esp32-s3-touch-lcd-4 |
4.0" | 480×480 | st7701 |
waveshare-esp32-s3-touch-lcd-4-v4 |
4.0" | 480×480 | mipi_rgb |
waveshare-esp32-s3-touch-lcd-4.3 |
4.3" | 800×480 | rpi_dpi_rgb |
waveshare-esp32-s3-touch-lcd-7 |
7.0" | 800×480 | mipi_rgb |
waveshare-esp32-s3-touch-lcd-7B |
7.0" | 1024×600 | mipi_rgb |
waveshare-esp32-p4-86-panel |
86mm | 720×720 | mipi_dsi |
waveshare-esp32-p4-wifi6-touch-lcd-3.4c |
3.4" | 800×800 | mipi_dsi |
waveshare-esp32-p4-wifi6-touch-lcd-4b |
4.0" | 720×720 | mipi_dsi |
waveshare-esp32-p4-wifi6-touch-lcd-7 |
7.0" | 720×1280 | mipi_dsi |
waveshare-esp32-p4-wifi6-touch-lcd-7b |
7.0" | 1024×600 | mipi_dsi |
waveshare-esp32-p4-wifi6-touch-lcd-10.1 |
10.1" | 800×1280 | mipi_dsi |
| Model | Size | Resolution | Driver |
|---|---|---|---|
guition-esp32-s3-4848s040 |
4.0" | 480×480 | mipi_rgb |
guition-esp32-jc4827w543 |
4.3" | 480×272 | qspi_dbi |
guition-esp32-jc8048w535 |
3.5" | 480×320 | axs15231 |
guition-esp32-jc8048w550 |
5.0" | 800×480 | mipi_rgb |
guition-esp32-p4-jc1060p470 |
7.0" | 1024×600 | mipi_dsi |
guition-esp32-p4-jc1060p470c |
7.0" | 1024×600 | mipi_dsi |
guition-esp32-p4-jc4880p443 |
4.3" | 480×800 | mipi_dsi |
guition-esp32-p4-jc8012p4a1 |
10.1" | 800×1280 | mipi_dsi |
| Model | Size | Resolution | Driver |
|---|---|---|---|
sunton-esp32-2432s028 |
2.8" | 240×320 | mipi_spi |
sunton-esp32-2432s028R |
2.8" | 320×240 | ili9xxx |
sunton-esp32-4827s032R |
3.2" | 480×320 | rpi_dpi_rgb |
sunton-esp32-8048s050 |
5.0" | 800×480 | rpi_dpi_rgb |
sunton-esp32-8048s070 |
7.0" | 800×480 | mipi_rgb |
| Model | Size | Resolution | Driver |
|---|---|---|---|
esp32-s3-box-3 |
2.4" | 320×240 | ili9xxx |
lilygo-tdisplays3 |
1.9" | 170×320 | ili9xxx |
elecrow-esp32-7inch |
7.0" | 800×480 | rpi_dpi_rgb |
SDL-lvgl |
desktop | variable | host (SDL) |
hardware/generic.yaml is a starting template for boards not listed above.
Audio/voice add-ons (include alongside a screen, not standalone):
hardware/media_player-voice_assistant.yaml and hardware/guition-esp32-jc4827w543-audio.yaml.
Additional UI modules under ui/ for specific integrations:
| Module | Description |
|---|---|
ui/clock/flip_clock.yaml |
Gluqlo-style flip clock widget |
ui/weather/today.yaml |
Current weather tile from HA weather entity |
ui/weather/forecast.yaml |
4-day forecast widget via weather.get_forecasts |
ui/solar/ |
Enphase / solar production and consumption monitoring |
ui/tides/tide_update.yaml |
NOAA tide clock with gauge display |
ui/tides/NOAA_tide_update.yaml |
NOAA CO-OPS API tide data with HA sensors (level, percentage, high/low times). Vars: noaa_station_id, noaa_unit_system (english/metric), noaa_unit_of_measurement (ft/m). |
See example_code/advanced/ for full working configs using these modules.
MIT — see LICENSE.