Design e-paper screens inside Home Assistant. Drag shapes, text, icons and live widgets onto a canvas that is exactly the size of your display, bind any property to Home Assistant data, and send the result to an OpenDisplay device, or serve it as an image that any display can fetch.
- A real WYSIWYG editor. The picture on the canvas is rendered by the same engine that produces the final image, so what you see is what the display shows: same fonts, same palette, same pixels.
- Every OpenDisplay drawing type: text, multiline text, lines, rectangles, patterns, polygons, circles, ellipses, arcs, icons, icon rows, images, QR codes, progress bars, history plots and a debug grid.
- Turn anything. Drag the round handle above any element to rotate it, or type an angle; the preview follows at once.
- Your own fonts. Open Settings in the list of dashboards, drop in any
.ttfor.otfand pick it for any text or widget, previewed in its own letters. Removing a font that dashboards use asks first, and nothing breaks if it is gone. - Live widgets: Sensor card, Sensor list, Sensor chart, Gauge, Thermometer,
Calendar, Agenda, School timetable, Waste collection, Battery and Weather read your entities, calendars and forecasts. Write
your own widget as a small Python package (see
docs/widget-sdk.md). Every widget is described, with its options and pictures, indocs/widgets. - Your data in any property. Switch the
{}toggle next to a field and type a Home Assistant template. The panel re-renders when the entities it reads change. - Made for e-paper. Pick the colours of your display (black/white, red, yellow, Spectra 6, grayscale…); the colour pickers only offer what the panel can show.
Create a dashboard from a display Home Assistant already knows, from a list of common panels, or at any custom size. Rotation is part of the dashboard, so a portrait display just works.
All your dashboards live in one list, with their size, colours and state (Draft or Ready). Ready dashboards get a stable image address; send them to a device with one click.
Select a polygon and drag its points. Click the + on an edge to add a point, double-click a point to remove it. A line has a handle on each end. Everything snaps to the grid and to the edges and centres of its neighbours, and guides with equal-gap badges show you when things line up. Hold Ctrl to turn snapping off, press Esc to cancel a drag.
Every element except the debug grid can be turned. Select one and drag the round handle above its frame, or type an angle under Layout → Rotation. The frame turns with the element, about the point the renderer turns it about, so what you see is what the display shows. Hold Shift to turn in steps of 15°, Esc to cancel; the whole turn is one step of the history. Text, icons, lines, shapes, QR codes and charts all turn, and a design made before keeps its look until you turn something. While an element is turned its frame has no resize handles; set the rotation back to 0 to resize it.
The more advanced drawing types are as easy to use as a rectangle. A history plot draws the recorded history of up to four entities, each with its own colour, line width and smoothing, optional axes and labels, and a multiplier for values that need scaling. Pick the entity of every series from the usual Home Assistant entity picker. Progress bars, arcs, QR codes, repeating patterns, rows of icons and multiline text sit next to them in the library.
Primitives, widgets and containers are edited with the same compact controls: numbers with units, segmented choices, colour swatches limited to your display's palette, a searchable picker for every Material Design icon, an image picker, and an entity picker for each series of a history plot. Sections show a dot when something differs from the defaults and a reset link puts it back in one step; rarely needed fields wait under Advanced.
Widgets decide how to lay out your data; you decide what to show. Pick the entities, calendars or weather source, choose the options, and resize the frame. The layout adapts to the size you give it. Every widget can be drawn without its frame and background, or in white on black. The widgets page lists each widget with its sources, options and pictures.
The widgets that come with Studio:
Every change is one undo step: moving, resizing, typing a value, ordering, grouping, pasting. Drag and nudge bursts are grouped sensibly, and Esc abandons a gesture without leaving a trace.
The Code view shows the clean, flat ODL YAML that is generated from the dashboard: absolute coordinates, only valid fields, nothing else. Copy it into an automation if you prefer to drive a display yourself.
Export saves the open dashboard as a JSON file; Import replaces the elements of the open dashboard with those of such a file, keeping its name, id and display settings. If the file was made for a display with more colours than yours, the import lists the colours your palette lacks and lets you pick a colour of your own for each; colours both displays have are kept. An import is one undo step and is not saved until you save.
| Do this | To get this |
|---|---|
| Mouse wheel | Pan |
| Ctrl / ⌘ + wheel | Zoom toward the pointer |
| Middle button, or Space + drag | Move the view |
| Shift or Ctrl / ⌘ + click | Add to the selection; click an element again to take it out |
| Arrow keys, Shift + arrow | Nudge by 1 px, or by the grid size |
| Ctrl + G | Group the selection |
| Ctrl + D, Ctrl + C / V | Duplicate, copy and paste |
| Ctrl + Z / Y | Undo and redo |
| Drag the round handle above a selection, with Shift | Turn it, in steps of 15° |
Every action is one command, and the toolbar buttons, the keyboard, the two context menus and the shortcut list (?) all use the same ones.
| Command | Shortcut | What it does |
|---|---|---|
| Undo, Redo | Ctrl + Z, Ctrl + Shift + Z or Ctrl + Y | Step through the history; every change is one step |
| Copy, Cut, Paste | Ctrl + C / X / V | Use the clipboard of the panel |
| Paste here | context menu of the empty canvas | Paste at the place that was clicked |
| Duplicate | Ctrl + D | Copy the selection 8 px to the side |
| Delete | Del | Remove the selection, after asking |
| Rename | F2 | Rename a layer |
| Hide, Lock | tree buttons, context menu of the tree | Hide an element from the picture, or keep it from being moved |
| Make group, Ungroup | Ctrl + G, Ctrl + Shift + G | Turn the selection into a group, or dissolve one |
| Enter group, Exit group | Enter or double-click, Esc | Select the members of a group one by one |
| Bring to front, Move up, Move down, Send to back | arrange bar, context menus | Change the drawing order; the last element is on top |
| Align | arrange bar | Put the edges or the middles of the selection on one line |
| Space out evenly | arrange bar | Make the gaps between the elements equal |
| Nudge | arrow keys, Shift + arrow keys | Move by 1 px, or by the snap size |
| Save | Ctrl + S | Save the dashboard |
| Design / Code | Ctrl + E | Switch between the canvas and the generated YAML |
| Zoom in, out, reset | Ctrl + = / - / 0 | Change the view |
| Keyboard shortcuts | ? | List every shortcut |
Right-click an element on the canvas or a layer in the tree. The two menus offer what suits where you are: the canvas menu has Duplicate and the drawing order, the tree menu has Move up and down, Hide, Lock and Rename. Commands that cannot run now are dimmed.
The bar under the canvas toolbar is always there, and a button is dimmed until enough is selected. Select elements with Shift or Ctrl / ⌘ and a click (the second click takes an element out again), or drag a box around them.
| Buttons | Need | Result |
|---|---|---|
| Align left edges, centers horizontally, right edges | 2 elements | The edges or the middles sit on one vertical line, that of the selection's box |
| Align top edges, centers vertically, bottom edges | 2 elements | The same on a horizontal line |
| Space out evenly, left to right / top to bottom | 3 elements | The outer two stay; the ones between are moved so that every gap is equal |
| Bring to front, Move up, Move down, Send to back | 1 element | Change the drawing order |
Two buttons beside them choose what the others measure against. Align to the selection is the default. Align to the container or the working area puts each element against the box it lives in, so one element is enough to align, and two are enough to space out: the first goes to one edge of the box, the last to the other.
For three entity widgets of one size: select them, centre them vertically, then space them out left to right. For a grid of nine, do that for each row, then align and space out each column. Locked elements and elements whose position comes from an expression are left where they are, and an element inside a selected container moves with it. Gaps that cannot be equal in whole pixels differ by at most one. Every press is one undo step, and a press that would change nothing does not add one.
Press ? in the editor for the complete list. Elements may hang out of the canvas; the renderer simply draws what is in view.
Home Assistant data
↓
dashboard (a tree of shapes, containers and widgets)
↓
ODL elements with absolute coordinates
↓
odl-renderer
↓
exact-size PNG
↓
editor preview, Media Source, or your device
The integration embeds odl-renderer:
no add-on, no browser, no extra service. The editor preview and the image your
display receives come from the same code path.
Every Ready dashboard has a stable address:
media-source://opendisplay_studio/<dashboard-id>
Text uses the two fonts that ship with the renderer. To use your own, put
.ttf or .otf files in /config/opendisplay_studio/fonts/; they appear in the
font lists of the editor.
- Click Open this integration in HACS, download OpenDisplay Studio, and restart Home Assistant when requested.
- Click Add OpenDisplay Studio to Home Assistant.
- Open OpenDisplay Studio in the Home Assistant sidebar.
It works on Home Assistant OS, Supervised, Container and Core.
Studio and the OpenDisplay integration from HACS both use the odl-renderer package, and
Home Assistant keeps only one copy of it. Studio needs 0.5.13 or newer, while the released
OpenDisplay 3.1.0 and older pin 0.5.12. (The OpenDisplay integration that ships with Home
Assistant does not use odl-renderer.) Until the HACS OpenDisplay is released with
odl-renderer 0.5.13 or newer, which its development branch already has, the two cannot
be installed together, and the log says so. Update OpenDisplay to such a version, then
restart Home Assistant. A restart is always needed after a package changes.
python -m pip install -r requirements_test.txt
python -m ruff check .
python -m ruff format --check .
python -m mypy custom_components/opendisplay_studio scripts
python -m pytest
cd frontend-src
npm ci
npm test
npm run build
npm run test:e2eSee ARCHITECTURE.md for the render boundary, WIDGET_CONTRACT.md for the widget contract and ROADMAP.md for what is done and what is next.
MIT
























