otter-examples/otr/. The Zig build files and .so plugin ABI are unsupported.
Choose a starter
The native and Lua-UI templates share a demo card and native Wayland shells. Both support an XDG toplevel window and a layer-shell overlay. The standalone
otter-lua run app host uses a headless backend; use the Lua-UI starter for a visible Wayland app.
For plugins, continue with Writing plugins.
1. Prepare the workspace
You need the otr toolchain onPATH. The examples expect sibling library checkouts with their otr/ trees:
otr/otr.toml declares local path dependencies on otter_geo, otter_utils, otter_theme, otter_render, otter_wayland, and otter_ui. Preserve that layout while learning. If you move the starter, update its manifest paths.
The build wrapper fetches required C dependencies before its steps. The example README describes toolchain and system dependencies. Running a window requires a Wayland session; the layer variant also requires zwlr_layer_shell_v1. Tests and fixtures can run without your desktop session.
2. Build a release app
From theotter-examples repository root:
app builds .build/otter-examples and .build/otter-examples-lua with --release-fast. check checks the package roots and formatting.
To try the native window or overlay:
examples.lua into your user data directory first.
3. Keep the shell separate from the view
Choose the lifecycle you need:
- XDG toplevel for a normal window, such as an editor or Settings.
- Layer shell for edge-anchored panels, popups, and overlays, when supported by the compositor.
- Session lock for lock surfaces; use the lock app and shared Wayland lock wrapper as references, not an ordinary overlay.
- No surface for a service or CLI.
4. Change the UI
Native view
The native starter describes its card throughotter-ui Surface Description. Start in otr/ui/demo.otr:
- Add a stable id in
idsfor each new interactive element. - Add its node to the view tree, following neighboring controls’ theme styles.
- Handle its press in
Demo.on_press. - Return damage for content that changed, or the appropriate existing input/full result.
Lua view
The script returns{ frame = function(state), action = function(id) }. It records sd.* nodes; the native LuaView owns replay and routes tagged actions.
view.otr’s push_state defines the script’s available state: card geometry, compact mode, title, hover/active ids, and resolved colors and metrics. The starter-specific otter.examples binding exposes native text measurement and baseline placement.
Add a hit id, draw its control, then handle the id in action. Script globals reset on reload. A failed candidate script retains the last valid card. See UI scripts for the complete contract, paths, and policy.
5. Apply shared theme and configuration
Use semantic theme colors, dimensions, and control styles rather than checking profile names. Both templates already integrate the shared theme and imported-file watcher. Keep their transactional reload: a broken theme edit must retain the previous valid theme and font. Application configuration belongs under$XDG_CONFIG_HOME/otter-shell/, defaulting to ~/.config/otter-shell/. Use otter-conf for the shared flat key-value format. If Settings should expose a feature, add its configuration and controls there too; study otter-settings/otr/tabs.otr and its editor modules.
Lua code belongs in data directories, not config directories:
- Plugins:
$XDG_DATA_HOME/otter-shell/plugins/<id>/. - UI scripts:
$XDG_DATA_HOME/otter-shell/ui/. - Plugin policy:
$XDG_CONFIG_HOME/otter-shell/plugins.conf. - Native/Lua UI choice:
$XDG_CONFIG_HOME/otter-shell/ui.conf.
~/.local/share. Native hosts choose which bindings and capabilities to expose to Lua. See Bindings and capabilities.
6. Verify without touching your desktop
The starter provides static checks, tests, direct-render fixtures, and isolated headless integration tests:test runs both debug and release-fast tests. Release applications and visual fixtures use --release-fast. The E2E starts private headless Sway sessions with isolated runtime, home, config, and D-Bus state. It compares native and Lua templates and checks input, damage, and reload behavior. Do not point capture tools at your real Wayland session.
Review generated visual evidence in otr/out/visual/ and otr/out/headless/. Golden fixtures live in otr/testdata/visual/. Review changed pixels before updating baselines.
For native window debugging, pass --inspect or --metrics after window, or press Ctrl+Shift+I to cycle overlays. These overlays intentionally force full-surface repaint on input.
7. Install the application
The starter’s install step writes release-fast binaries underotr/out/bin/ and examples.lua under otr/out/share/otter-shell/ui/:
icons/hicolor/. Rename the starter’s binary, application identifiers, UI script, and package metadata consistently before distribution.

