1. Create the project
Initialize a Zig project and add Otter dependencies inbuild.zig.zon:
2. Wire imports in build.zig
3. Choose your app pattern
Otter apps differ in two ways: which Wayland surface you create, and how long the process runs. A daemon might use layer shell, an XDG toplevel, or no window at all. Do not treat “daemon” as a synonym for layer shell.Layer-shell surfaces (zwlr_layer_shell_v1)
Use otter-wayland layer shell when the compositor should anchor your surface to an output edge or layer, not as a normal floating window.
Common overlay pattern:
DimOverlay (fullscreen dim on .overlay) plus a second layer surface for the panel. See otter-launcher and otter-logout. Overlay sessions that exit after one action often use ArenaAllocator.
Layer-shell daemons typically poll Wayland plus D-Bus, inotify config watchers, and timer FDs. Use GeneralPurposeAllocator and otter-conf.Watcher for hot reload.
XDG toplevel windows (xdg_toplevel)
Use otter-wayland XdgToplevel for regular desktop windows with a title bar and workspace placement.
Describe UI with Surface Description (
otter-ui UiFrame). Poll Wayland plus app-specific FDs (PTY for otter-term, config inotify, keyboard repeat).
Session lock (ext_session_lock_v1)
otter-lock draws on lock surfaces from ext_session_lock_v1, not layer shell. The process stays up while the session is locked.
Capture and selection overlays
Region pickers and frozen-capture UIs use sharedotter-wayland helpers (selection_overlay, capture_overlay, dim_overlay). They attach layer .overlay surfaces but are not full apps.
No Wayland UI
CLI tools can use minimal deps (
otter-tools-core + otter-conf) with no Wayland connection unless you add a popup mode.
Quick reference
4. Build the UI with Surface Description
Use Surface Description viaUiState and UiFrame. The otter-ui guide covers layout, nodes, input, and examples.
state.hitTest(point) for pointer input. No parallel hit arrays needed.
Skip text_system and text_scratch for ASCII-only labels. Pass both when the UI might show RTL, CJK, or other user-typed text (see otter-lock/src/lock_render_loop.zig).
5. Add configuration
Define a config struct and parse it withotter-conf:
~/.config/otter-shell/my-app.conf. On first run, normalize the file (add missing defaults, strip unknown keys).
For typed configs shared with otter-settings, add structs to otter-config-types and register a tab in otter-settings/src/tabs.zig.
6. Apply theming
Load the shared theme:?Color = null and resolve via config.field orelse theme.token. Precedence: widget config, then theme.conf, then compiled defaults.
7. Install a .desktop file
For launcher integration, install a freedesktop.desktop entry:
data/applications/ and install icons under data/icons/hicolor/.
Reference implementations
See the Libraries overview and per-library pages in the sidebar for API details.

