Minimal • Lightweight • Offline-first • Cross-compositor
Development Guide • Theme Authoring Guide
smplOS is a minimal Arch Linux distro built around one idea: simplicity.
It started as an attempt to build a lighter version of Omarchy - same keybindings, same themes, but without the bloat. That contribution was rejected, so we forked and built our own distro. Along the way we rewrote most of the stack: a suite of lightweight GUI apps in Rust, a patched suckless terminal that renders inline images at a fraction of Kitty's footprint, a cross-compositor architecture, and a theme system that touches every app on the desktop. What came out the other side isn't Omarchy lite - it's a different OS.
- Lightweight. Under 800 MB of RAM on a cold boot (Omarchy idles at 1.7 GB). Every package earns its place.
- Fast installs. Fully offline - no internet required. A fresh install completes in under 2 minutes.
- Cross-compositor. Built from the ground up to support multiple compositors. Hyprland (Wayland) ships first, DWM (X11) is next. Shared configs, shared themes, shared keybindings - the compositor is just a thin layer.
- One UI toolkit. EWW powers the bar, widgets, and dialogs. It runs on both X11 and Wayland. No waybar, no polybar, no redundant tools.
- 14 built-in themes. One command switches colors across the entire system - terminal, bar, notifications, borders, lock screen, and editor.
A native start menu built with Rust and Slint, themed to match the active system colors. Tap Super to open it - browse apps by category, search across all installed apps (including Flatpak, AppImage, and web apps), or jump to Settings. Full keyboard navigation with Tab, Shift+Tab, arrow keys, and Enter. No dock, no taskbar, no wasted pixels.
A cold boot sits under 800 MB of RAM with the full desktop running - bar, notifications, compositor, and all background services. Unlike other lightweight distros that sacrifice usability to hit low numbers, smplOS keeps quality-of-life features like auto-mount, theme switching, a notification center, and a full app launcher. Light enough for a 2 GB VM, comfortable enough for daily driving.
st is our terminal of choice - a suckless terminal patched for the features that matter. It starts in milliseconds and idles at around 25 MB of RAM. We added SIXEL image support, scrollback, clipboard integration, Page Up/Down, and alpha transparency. The result is a terminal that can display inline images just like Kitty (~350 MB), but at a fraction of the footprint. Every fix was made in config.def.h following the suckless philosophy: if you don't need it, it doesn't exist.
We also fixed several upstream bugs in the suckless st codebase:
- History buffer allocation - the default scrollback patch pre-allocates the entire buffer (HISTSIZE x columns) at startup, wasting tens of MB. We rewrote it to use page-based lazy allocation (256-line pages, allocated on demand).
- Page Up/Down not working - the scrollback patch only bound Shift+PageUp/Down but left plain PageUp/Down doing nothing. Added
MOD_MASK_NONEbindings so both work. - Unnecessary X11 libraries on Wayland - the SIXEL patch links against imlib2, which drags in the entire X11 library chain (libX11, libxcb, libXext, etc.) into a Wayland-only binary. Stripped the dependency tree from 29 to 21 libraries, cutting ~5 MB of RSS.
- X11-only patches breaking Wayland - several patches (e.g.
sixelbyteorder = LSBFirst, boxdraw, openurlonclick) use X11 macros and structs that don't exist in the Wayland build. Identified and disabled them. - Font fallback crash - st-wl crashed on launch when no
-fflag was given. Fixed the default font fallback path. - SIXEL linker errors - enabling the SIXEL patch in
patches.def.halone wasn't enough; the SIXEL source files and imlib2 libs also need uncommenting inconfig.mk.
smplOS ships with 14 themes inherited and expanded from the Omarchy project. A single theme-set command applies colors system-wide - terminal, EWW bar, notifications, Hyprland borders, lock screen, btop, neovim, and VS Code. Every theme includes matching wallpapers and is generated from a single colors.toml source of truth.
smplOS uses the same keybindings as Omarchy, so migrating from that project is seamless - your muscle memory carries over. Press Super+K to open the keybinding cheatsheet overlay at any time. The overlay is dynamically generated by parsing bindings.conf at runtime, so any keybinding you add or change shows up in the help automatically - no manual docs to maintain.
Linux has no shortage of CLI tools, but when it comes to graphical settings panels that are lightweight, Wayland-native, and theme-aware, the options are slim. Most existing tools are either GTK/Qt monoliths that pull in hundreds of megabytes, Electron wrappers, or they just don't exist for tiling Wayland compositors. So we wrote our own.
The smplOS app suite is a set of purpose-built GUI apps written in Rust with the Slint UI framework. Each app is a single static binary under 5 MB, starts in milliseconds, integrates with the smplOS theme system, and works on both X11 and Wayland. They replace functionality that mainstream desktops take for granted but that tiling WM users have historically gone without.
A notification hub that collects and groups desktop notifications by app. Supports dismiss, clear-all, and scrollable history. Integrates with Dunst over D-Bus. We built this because every existing notification center was either Electron-based (heavy), GNOME-only, or lacked grouping. Ours idles at under 10 MB of RAM.
A graphical display configuration panel. Detects connected monitors via Hyprland IPC, shows resolution, refresh rate, scale, and position for each display, and lets you apply changes live. No external dependencies - just hyprctl under the hood. Replaces the need for wlr-randr CLI or a full KDE/GNOME settings app just to rearrange monitors.
A keyboard layout and input configuration panel. Shows active layouts, lets you add/remove languages, toggle NumLock behavior, and adjust repeat rate and delay - all applied live via hyprctl keyword. Previously this required hand-editing input.conf and reloading the compositor. Now it's a click.
Every tool in smplOS was chosen to work across compositors (Wayland and X11) so the OS feels identical regardless of which one you run.
| Component | Choice | Why |
|---|---|---|
| Bar & widgets | EWW | GTK3-based, runs natively on both X11 and Wayland. One codebase for bar, widgets, theme picker, and keybind help. Replaces waybar and polybar. |
| Start Menu | Rust + Slint | Native GPU-rendered app launcher with categories, search, source badges (AUR/Flatpak/AppImage/Web App), and Settings tab. Theme-aware, keyboard-driven, under 5 MB. |
| Terminal | st / st-wl | Suckless st has an X11 build and a Wayland port (marchaesen/st-wl). Same config.h, same patches, same look. Starts in ~5ms and uses ~4 MB of RAM - critical for staying under the 850 MB cold-boot target. |
| Notifications | Dunst | Works on both X11 and Wayland with the same config. Lightweight, themeable, no dependencies on a specific compositor. |
The rule is simple: if a tool only works on one display server, it doesn't ship in src/shared/. Compositor-specific code stays in src/compositors/<name>/ and is kept as thin as possible.
smplOS ships in focused editions that stack on top of each other. Pick the ones you need - they all merge cleanly:
| Flag | Edition | Focus | Example apps |
|---|---|---|---|
-p |
Productivity | Office & workflow | Logseq, LibreOffice, KeePassXC |
-c |
Creators | Design & media | OBS, Kdenlive, GIMP |
-m |
Communication | Chat & calls | Discord, Signal, Slack |
-d |
Development | Developer tools | VSCode, LazyVim (neovim), lazygit |
-a |
AI | AI tools | Ollama, open-webui |
Build with any combination:
./build-iso.sh -p -d # Productivity + Development
./build-iso.sh -p -d -c -m # Stack all four
./build-iso.sh # Base only (browser, terminal, file manager)Every edition installs offline, in under 2 minutes, from the same ISO.
On first boot, a notification shows the essential keybindings. Here they are for reference:
| Shortcut | Action |
|---|---|
| Super (tap) | Start menu |
| Super+Enter | Terminal |
| Super+A | App Center |
| Super+W | Close window |
| Super+F | Fullscreen |
| Super+T | Toggle floating |
| Super+1-9 | Switch workspace |
| Super+K | Keybinding cheatsheet |
| Super+Shift+F | File manager |
| Super+Shift+B | Web browser |
| Screenshot | |
| Super+Escape | Power menu |
Press Super+K anytime to see all bindings in an overlay.
Switch the system theme (terminal, bar, borders, lock screen, editor) with a single command:
theme-set catppuccin # or: dracula, nord, gruvbox, rose-pine, ...Or open the theme picker from the start menu's Settings tab.
smplOS separates shared infrastructure from compositor-specific config. The goal is maximum code reuse - compositors are a thin layer on top of a shared foundation.
src/
build-iso.sh Entry point — detects Docker, launches builder
bootstrap.sh One-shot host bootstrap (installs Docker if absent)
generate-theme-configs.sh Re-generates pre-baked theme configs from colors.toml templates
shared/ Everything here works on ALL compositors
bin/ User-facing scripts (installed to /usr/local/bin/)
eww/ EWW bar and widgets (GTK3 — works on X11 + Wayland)
configs/
smplos/ Cross-compositor configs (bindings.conf, messengers.conf, branding)
<app>/ Per-app default configs (btop, dunst, fish, foot, nvim, …)
themes/ 14 themes — each a self-contained directory with pre-baked configs
icons/ SVG status icon templates (baked with accent colors by theme-set)
applications/ Shared web-app .desktop entries and hicolor icons
skel/ Default user home skeleton (copied to /etc/skel in the ISO)
system/ System-level files (os-release, …)
start-menu/ Start menu launcher (Rust + Slint)
app-center/ App center — install/manage packages (Rust + Slint)
notif-center/ Notification center — dunst history viewer (Rust + Slint)
disp-center/ Display manager — monitor layout/resolution (Rust + Slint)
kb-center/ Keyboard manager — layouts, repeat rate (Rust + Slint)
webapp-center/ Web app manager — sandboxed browser shortcuts (Rust + Slint)
packages.txt Shared package list (all compositors)
packages-aur.txt AUR packages (prebuilt, injected into offline mirror)
packages-flatpak.txt Flatpak apps (installed on first boot)
packages-appimage.txt AppImages (bundled into the ISO)
compositors/
hyprland/ Hyprland-specific config
hypr/ hyprland.conf (sources shared bindings.conf)
configs/ Hyprland-only app configs (hyprlock, hyprpaper, …)
st/ st-wl patched terminal (config.def.h, patches.def.h)
packages.txt Wayland-specific packages
postinstall.sh Hyprland post-install steps
dwm/ DWM-specific config (X11, planned)
st/ st patched terminal
packages.txt X11-specific packages
postinstall.sh DWM post-install steps
editions/ Optional edition overlays (stack on top of base)
lite/ Lite edition — reduced package set
productivity/ Productivity — Logseq, LibreOffice, KeePassXC
creators/ Creators — OBS, Kdenlive, GIMP
communication/ Communication — Discord, Signal, Slack
development/ Development — VSCode, LazyVim, lazygit
ai/ AI — Ollama, open-webui
installer/ smplOS interactive installer (smplos-install)
builder/ ISO build pipeline (runs inside Docker/Podman)
custom-pkgbuilds/ In-tree PKGBUILDs for packages not in AUR
release/ VM testing tools (dev-push.sh, dev-apply.sh, test-iso.sh, QEMU scripts)
build/
prebuilt/ Pre-compiled AUR packages (.pkg.tar.zst) bundled into the ISO
- Simple over opinionated. Provide good defaults, not forced workflows.
- Cross-compositor first. Every feature must work across Hyprland (Wayland) and DWM (X11). Compositor-specific code stays in
src/compositors/<name>/. - EWW is the UI layer. Bar, widgets, dialogs - all EWW. It runs on both GTK3/X11 and GTK3/Wayland.
- One theme system.
theme-setapplies colors to EWW, terminals, btop, notifications, compositor borders, lock screen, and neovim. - bindings.conf is the single source of truth for keybindings across all compositors.
- Minimal packages. One terminal, one launcher, one bar. No redundant tools.
- Offline-first. The ISO carries everything needed. No downloads during install.
| Compositor | Display Server | Terminal | Status |
|---|---|---|---|
| Hyprland | Wayland | st-wl | Active |
| DWM | X11 | st | Planned |
A native start menu built with Rust and Slint. It appears at the bottom-left of the screen (like a Plasma/Windows start menu) with a slide-left animation, blur, and theme-aware colors.
Open it: Press Super (tap and release) or click the logo in the EWW bar.
The left sidebar shows category tabs with Nerd Font icons:
| Category | Icon | Contents |
|---|---|---|
| All Apps | | Everything |
| Internet | | Browsers, email, chat, web apps |
| Development | | IDEs, editors, git tools |
| Multimedia | | Media players, recorders |
| Graphics | | Image editors, viewers |
| Office | | Documents, spreadsheets |
| Settings | | System settings, App Center, Web Apps |
Click a category or use Tab / Shift+Tab to move between the sidebar, search, and app list. Arrow keys navigate within each area.
Each app shows a source badge indicating where it came from:
| Badge | Meaning |
|---|---|
| AUR | Installed from official repos or AUR |
| Flatpak | Installed via Flatpak |
| AppImage | Portable AppImage |
| Web App | Sandboxed web app created via Web App Center |
src/shared/start-menu/- Rust + Slint application. Reads the app index, resolves icons (SVG/PNG from hicolor, Flatpak exports, and user icon dirs), renders a GPU-accelerated UI.toggle-start-menu- wrapper script that toggles the menu open/closed. Manages focus capture (stay_focused,pin, temporaryfollow_mouse=3).- Hyprland window rules -
float,move 2 (monitor_h-window_h-37),animation slide left,opacity 1.0 override.
The start menu reads from a pre-built app index at ~/.cache/smplos/app_index. This is automatically rebuilt by a systemd path unit (smplos-app-cache.path) whenever .desktop files, Flatpak apps, or AppImages change. You can also rebuild manually:
rebuild-app-cachesmplOS configures the system so GTK dialogs (file pickers, VS Code popups, etc.) follow the dark/light theme automatically:
- GTK settings.ini - written during install for X11 fallback
- dconf/GSettings - written during install via
dbus-run-sessionfor Wayland (GTK on Wayland ignoressettings.ini) theme-set- updates bothgsettings color-schemeandgtk-themeon every theme switch
Credential storage (for VS Code, Brave, git, etc.) is fully configured:
- gnome-keyring - installed, started via Hyprland autostart, PAM-integrated for auto-unlock at login
- VS Code argv.json - pre-configured with
"password-store": "gnome-libsecret"to eliminate the keyring detection dialog
A custom notification center built with Rust and Slint, accessible from the bell icon in the EWW bar. It reads dunst's notification history and displays cards with dynamic heights - long messages word-wrap naturally instead of being truncated.
Open it: Click the bell icon in the bar, or press Super + N.
- Dynamic card layout - each notification card grows to fit its content. No fixed heights, no wasted space.
- Word-wrapped body text - long notification bodies wrap cleanly instead of being clipped with an ellipsis.
- Double-click to open - double-click any notification to launch the associated app. Uses the desktop entry from dunst metadata, with a fallback to the app name.
- Actionable notifications - well-known notifications (like "System Update") map to specific commands. Double-click the System Update notification to open a full system update in your terminal.
- Dismiss - click the X button on any card to dismiss it.
- Scrollable - notifications overflow into a smooth-scrolling list.
dunst (notification daemon)
+-- dunstctl history --> notif-center (Rust + Slint)
|-- Parses JSON history
|-- Maps app names to nerd font icons
|-- Maps summaries to actions (e.g. "System Update" -> smplos-update)
+-- Renders scrollable card list via Slint UI
smplOS includes a built-in update system. On first boot, a persistent "System Update" notification appears. Double-click it in the notification center to run a full update, or run it manually:
smplos-updateThe update script opens in your terminal and runs through:
- Pacman - official repo packages (
pacman -Syu --noconfirm) - AUR - if paru is installed, AUR packages (
paru -Sua --noconfirm) - Flatpak - if Flatpak apps are installed (
flatpak update -y --noninteractive) - AppImages - reminds you to check for updates manually
Terminal auto-detection makes sure it works regardless of which terminal is installed: xdg-terminal-exec -> st-wl (Wayland) -> st (X11) -> foot -> xterm.
The build system is designed to work on first run, on any Linux distro. It runs inside an Arch Linux container for reproducibility. The only host requirement is a container runtime — Podman (preferred, daemonless) or Docker.
cd src && ./build-iso.shThis produces a bootable Arch Linux ISO in release/. First build takes ~15-20 minutes (downloads packages); subsequent same-day builds reuse the package cache and finish much faster.
Podman is the preferred container runtime — it's daemonless and needs no background service. Docker works too and is auto-detected as a fallback.
If neither is installed, the build script will offer to install Podman automatically:
[WARN] No container runtime found (podman or docker)
Install Podman automatically? [Y/n]
To install Podman manually:
Arch / EndeavourOS / Manjaro / Garuda / CachyOS
sudo pacman -S --needed podmanUbuntu / Debian / Pop!_OS / Linux Mint / Zorin
sudo apt-get update && sudo apt-get install -y podmanFedora / Nobara
sudo dnf install -y podmanopenSUSE
sudo zypper install -y podmanVoid Linux
sudo xbps-install -y podmanNo group setup needed. The build script runs
sudo podmanautomatically —mkarchisorequires real root for loop devices and mounts, so there's no rootless option regardless of runtime.
You also need ~10 GB of free disk space. The script checks this and warns you if you're low.
Usage: build-iso.sh [EDITIONS...] [OPTIONS]
Editions (stackable):
-p, --productivity Office & workflow (Logseq, LibreOffice, KeePassXC)
-c, --creators Design & media (OBS, Kdenlive, GIMP)
-m, --communication Chat & calls (Discord, Signal, Slack)
-d, --development Developer tools (VSCode, LazyVim, lazygit)
-a, --ai AI tools (Ollama, open-webui)
--all All editions (equivalent to -p -c -m -d -a)
Options:
--compositor NAME Compositor to build (hyprland, dwm) [default: hyprland]
-r, --release Release build: max xz compression (slow, smallest ISO)
-n, --no-cache Force fresh package downloads
-v, --verbose Verbose output
--skip-aur Skip AUR packages (faster, no Rust compilation)
--skip-flatpak Skip Flatpak packages
--skip-appimage Skip AppImages
-h, --help Show this help
# Base build (Hyprland, browser, terminal, file manager)
./build-iso.sh
# Productivity + Development
./build-iso.sh -p -d
# All editions
./build-iso.sh --all
# Fast iteration (skip AUR packages like EWW that take ages to compile)
./build-iso.sh --skip-aur
# Release build with max compression
./build-iso.sh --all --release
# Full verbose output for debugging
./build-iso.sh -v- Checks prerequisites - detects your distro, ensures Podman (or Docker) is installed, checks disk space, pre-authenticates
sudo. - Builds AUR packages (unless
--skip-aur) - compiles packages like EWW in a temporary container. Results are cached inbuild/prebuilt/so they only build once. - Pulls
archlinux:latest- the build runs in a fresh Arch container for reproducibility. - Downloads packages - pacman downloads all packages into a dated local mirror. On Arch-based hosts, your system's pacman cache is mounted read-only for instant hits.
- Builds the ISO - copies configs, themes, scripts, and the offline package mirror into an Arch ISO profile, then runs
mkarchiso. - Outputs - the final
.isolands inrelease/. The full build log is saved to.cache/logs/build-TIMESTAMP.log.
Builds use a dated cache (build_YYYY-MM-DD/) under .cache/. Same-day rebuilds reuse downloaded packages. Old caches are automatically pruned (keeps the last 3 days). To force a completely fresh build:
./build-iso.sh --no-cache| Problem | Solution |
|---|---|
sudo password prompt mid-build |
Expected — mkarchiso needs real root for loop devices and mounts. The script pre-authenticates sudo at startup and keeps it alive throughout the build. |
| DNS errors inside container | The build uses --network=host, so the container shares your host's network stack. If you still get DNS errors, check that your host can reach archlinux.org and that your firewall allows container traffic. |
no space left on device |
Need ~10 GB free. Run podman system prune (or docker system prune) to reclaim container disk space. |
| AUR build fails | Try --skip-aur to skip it. Pre-built AUR packages are cached in build/prebuilt/ and reused on the next run. |
| Slow builds | First build downloads ~2 GB of packages. After that, the dated cache makes same-day rebuilds much faster. On Arch hosts, your system pacman cache is reused automatically. |
| Build log | All output is automatically saved to .cache/logs/build-TIMESTAMP.log for post-mortem inspection. |
See the Development Guide for hot-reload iteration, VM testing with QEMU, and how to extend smplOS (add settings entries, etc.).
14 built-in themes. Press Super + Shift + T to open the theme picker and switch instantly.
Catppuccin Mocha, Catppuccin Latte, Ethereal, Everforest, Flexoki Light, Gruvbox, Hackerman, Kanagawa, Matte Black, Nord, Osaka Jade, Ristretto, Rose Pine, Tokyo Night.
One command - theme-set <name> - applies colors across the entire system: terminal, bar, notifications, compositor borders, lock screen, launcher, system monitor, editor, fish shell, Logseq, and browser chrome.
For a full step-by-step authoring workflow, see CREATING_MODIFYING_A_THEME.md.
The theme system is a build-time template pipeline plus a runtime switcher:
colors.toml --> generate-theme-configs.sh --> 9 pre-baked configs per theme
(sed templates)
theme-set copies them to
their target locations and
restarts/reloads each app
Each theme is a directory under src/shared/themes/<name>/ containing:
| File | Source | Purpose |
|---|---|---|
colors.toml |
Hand-authored | Single source of truth - all colors and decoration variables |
btop.theme |
Generated | btop color scheme |
dunstrc.theme |
Generated | Dunst notification colors |
eww-colors.scss |
Generated | EWW bar/widget SCSS variables |
eww-colors.yuck |
Generated | EWW yuck variables (for SVG fills) |
fish.theme |
Generated | Fish shell syntax highlighting and pager colors |
foot.ini |
Generated | Foot terminal colors |
hyprland.conf |
Generated | Hyprland border colors, rounding, blur, opacity |
hyprlock.conf |
Generated | Lock screen colors |
logseq-custom.css |
Generated | Logseq editor colors (backgrounds, text, links, highlights) |
neovim.lua |
Hand-authored | Lazy.nvim colorscheme spec |
vscode.json |
Hand-authored | VS Code/Codium/Cursor theme name + extension ID |
icons.theme |
Hand-authored | GTK icon theme name |
light.mode |
Hand-authored (optional) | Marker file - if present, GTK + browser use light mode |
tide.theme |
Hand-authored | Tide prompt colors (git, pwd, vi-mode segments) |
backgrounds/ |
Hand-authored | Wallpapers bundled with the theme |
preview.png |
Hand-authored | Theme preview screenshot for the picker |
Every theme defines all its values in a single colors.toml file. Here's the full set of variables:
| Variable | Description | Example |
|---|---|---|
accent |
Primary accent color (bar icons, active borders, highlights) | "#89b4fa" |
cursor |
Terminal cursor color | "#f5e0dc" |
foreground |
Default text color | "#cdd6f4" |
background |
Window/terminal background | "#1e1e2e" |
selection_foreground |
Text color in selections | "#1e1e2e" |
selection_background |
Background color of selections | "#f5e0dc" |
color0 - color15 |
Standard 16-color terminal palette | "#45475a" |
Note:
color7andcolor15are the colors terminals actually display for normal text in most shells. If terminal text looks dim, brighten these to matchforeground.
| Variable | Default | Description |
|---|---|---|
rounding |
"10" |
Window corner radius in pixels |
blur_size |
"6" |
Background blur kernel size |
blur_passes |
"3" |
Number of blur passes (higher = smoother, more GPU) |
opacity_active |
"0.92" |
Opacity of focused windows (all regular apps) |
opacity_inactive |
"0.85" |
Opacity of unfocused windows |
term_opacity_active |
"0.85" |
st-wl background-only alpha. Text is always 100% opaque — only the background pixels carry this alpha in the ARGB surface. |
browser_opacity |
"1.0" |
Opacity of browsers (Brave, Firefox, Chrome, etc.) |
messenger_opacity |
"0.85" |
Opacity of messengers (Signal, Telegram, Slack, Discord, Teams, WhatsApp) |
popup_opacity |
"0.85" |
Opacity of smplOS Rust popup apps (start-menu, notif-center, kb-center, disp-center) |
Each opacity class is owned exactly once — no value is applied twice. See Opacity Architecture for details.
Each colors.toml can also choose which app-specific preset to use:
| Variable | Description | Example |
|---|---|---|
app_theme_nvim |
Which theme's neovim.lua preset to apply |
"tokyo-night" |
app_theme_vscode |
Which theme's vscode.json preset to apply |
"tokyo-night" |
app_theme_logseq |
Which Logseq mapping preset to apply | "tokyo-night" |
By default, each built-in theme points these to itself. You can mix and match (e.g. keep colors.toml from one theme but reuse VS Code preset from another).
accent = "#89b4fa"
cursor = "#f5e0dc"
foreground = "#cdd6f4"
background = "#1e1e2e"
selection_foreground = "#1e1e2e"
selection_background = "#f5e0dc"
color0 = "#45475a"
color1 = "#f38ba8"
color2 = "#a6e3a1"
color3 = "#f9e2af"
color4 = "#89b4fa"
color5 = "#f5c2e7"
color6 = "#94e2d5"
color7 = "#cdd6f4"
color8 = "#585b70"
color9 = "#f38ba8"
color10 = "#a6e3a1"
color11 = "#f9e2af"
color12 = "#89b4fa"
color13 = "#f5c2e7"
color14 = "#94e2d5"
color15 = "#cdd6f4"
rounding = "12"
blur_size = "14"
blur_passes = "3"
opacity_active = "0.60"
opacity_inactive = "0.50"
browser_opacity = "1.0"
messenger_opacity = "0.85"
popup_opacity = "0.85"Templates live in src/shared/themes/_templates/ and use {{ variable }} placeholders.
The generator provides three variants of each color variable:
| Variant | Example input | Output | Use case |
|---|---|---|---|
{{ accent }} |
"#89b4fa" |
#89b4fa |
CSS, config files |
{{ accent_strip }} |
"#89b4fa" |
89b4fa |
Hyprland rgb(), btop, foot |
{{ accent_rgb }} |
"#89b4fa" |
137,180,250 |
Hyprlock rgba() |
-
Create the directory:
mkdir src/shared/themes/my-theme
-
Write
colors.tomlwith all color and decoration values. Copy an existing theme as a starting point:cp src/shared/themes/catppuccin/colors.toml src/shared/themes/my-theme/
-
Add optional hand-authored files:
neovim.lua- Lazy.nvim colorscheme plugin specvscode.json-{"name": "Theme Name", "extension": "publisher.extension-id"}icons.theme- GTK icon theme name (e.g.,Papirus-Dark)light.mode- Create this empty file if the theme is lightbackgrounds/- Add wallpapers (named1-name.png,2-name.png, etc.)preview.png- Screenshot for the theme picker
-
Generate configs:
cd src && bash generate-theme-configs.sh
This reads your
colors.toml, expands all 9 templates, and writes the results into your theme directory. -
Test it:
theme-set my-theme
When you run theme-set <name>, it:
- Resolves the theme (user themes in
~/.config/smplos/themes/take precedence over stock themes) - Atomically swaps the active theme directory at
~/.config/smplos/current/theme/ - Copies pre-baked configs to their target locations:
eww-colors.scss->~/.config/eww/theme-colors.scsshyprland.conf->~/.config/hypr/theme.confhyprlock.conf->~/.config/hypr/hyprlock-theme.conffoot.ini->~/.config/foot/theme.inibtop.theme->~/.config/btop/themes/current.themefish.theme->~/.config/fish/theme.fishtide.theme-> applied viafish -c "source ...; tide reload"logseq-custom.css->~/.logseq/config/custom.css+ plugin theme viapreferences.jsondunstrc.theme-> appended to~/.config/dunst/dunstrc.activeneovim.lua->~/.config/nvim/lua/plugins/colorscheme.lua
- Bakes accent/fg colors into SVG icon templates for the EWW bar
- Sets the wallpaper from
backgrounds/ - Restarts/reloads all running apps:
- EWW bar: kill + restart (re-compiles SCSS)
- Hyprland:
hyprctl reload - st/st-wl: OSC escape sequences (live, no restart)
- Foot:
SIGUSR1 - Fish: sources
theme.fishin all running sessions - Tide prompt:
tide reload(updates git, pwd, vi-mode segment colors) - Dunst:
dunstctl reload - btop:
SIGUSR2 - Logseq: writes
preferences.json(Logseq watches this file) - GTK:
gsettings(dark/light mode) - Brave/Chromium: managed policy + flags file
Every window belongs to exactly one opacity class. The value for each class lives in colors.toml and is never applied in more than one place — no compounding.
| Class | Tag | Who controls opacity | colors.toml key |
|---|---|---|---|
| Regular apps | (untagged) | Hyprland compositor | opacity_active / opacity_inactive |
| Browsers | chromium-based-browser / firefox-based-browser |
Hyprland compositor | browser_opacity |
| Messengers | messenger |
Hyprland compositor | messenger_opacity |
| smplOS Rust popups | self-managed-alpha |
App itself (Slint ARGB surface) | popup_opacity |
| Terminals | self-managed-alpha |
App itself (st ALPHA_PATCH per-pixel) | term_opacity_active |
| Media / fullscreen | compositor-opaque |
Hyprland compositor (forced 1.0) | — |
Slint (smplOS Rust apps) and st with ALPHA_PATCH render their own semi-transparent pixels directly into an ARGB Wayland surface. If the compositor also applies opacity, the two multiply together:
0.85 (app alpha) × 0.85 (compositor) ≈ 0.72 opaque → only 28% see-through
To prevent this, all self-managed-alpha windows receive opacity 1.0 override from Hyprland, so the compositor passes pixels through untouched and the app's ARGB alpha is the sole controller.
All messengers default to messenger_opacity from the active theme, but individual apps can be pinned to a specific value by uncommenting the override lines in windows.conf:
# windowrule = opacity 0.70 override 0.70 override, match:class ^(signal)$
# windowrule = opacity 0.80 override 0.80 override, match:class ^(brave-teams\.microsoft\.com)(.*)$These lines come after the tag-level rule, so they win (Hyprland last-match-wins). No theme rebuild needed — reload Hyprland config with Super+R.
If you add a new Rust/Slint app, add one line to windows.conf in the self-managed-alpha tag block:
windowrule = tag +self-managed-alpha, match:class ^(my-new-app)$The opacity 1.0 override rule fires automatically from the tag. Nothing else to change.
MIT License. See LICENSE for details.
Terminal emulators (st, st-wl) are under their own licenses - see their respective directories.
Thanks to everyone who has contributed to smplOS!








