Skip to main content
Build a new Wayland app on the Otter Shell stack.

1. Create the project

Initialize a Zig project and add Otter dependencies in build.zig.zon:
Outside the monorepo, fetch libraries by URL:

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 shared otter-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 via UiState and UiFrame. The otter-ui guide covers layout, nodes, input, and examples.
Use 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 with otter-conf:
Store config at ~/.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:
Widget config fields should use ?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:
Place in 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.