Look and theme
Pick a look
Section titled “Pick a look”look names the declared default: one entry of programs.
(default: every shipped look; list paths to your own source look
directories beside the names to add them). Each look renders at build
time into the catalog (~/. sources beside
~/. renders); each activation runs
scoot look sync, which starts a fresh state on the declared
default, switches when it changed, falls back when the live choice
left the catalog, and otherwise keeps the live choice. A swap
(scoot look apply, the look-cycle key, the IPC request) repoints
the state link and reloads each running app’s look layer at once; the
declaration only wins when it changes. The default is ginger-night
(the cat-peeking look): a profile with no look choice renders it.
programs. is the scoot-look binary that renders
the catalog at build time: a package or null (default:
pkgs., which the flake’s overlay and module wrappers
provide; without them, point it at
inputs.). Null installs no
catalog: no sources, no renders, no declared default, no sync step.
It takes effect on the next build.
| Look | Compositor ring / background | Bar | Wallpaper |
|---|---|---|---|
ginger-night (default) |
ginger ring #FF9A30, near-black #0E0E0E |
black, cream and ginger | ships (copied to the store; fit on the look’s black, so the cat is never cropped) |
music-desk |
blue ring #3D579A, paper #FCFBFB |
paper, ink and blue | ships (copied to the store) |
radial-burst |
blue ring #31a9e5, plum #241721 |
plum, yellow and blue | ships (copied to the store) |
moonrise |
amber ring #FF9A49, slate navy #2B3648 |
navy, cream and amber | ships (copied to the store) |
vinyl-sunset |
orange ring #FE9949, espresso #270E0A |
espresso, cream and orange | ships (copied to the store) |
With the profile on and no look set, the look is ginger-night.
null themes nothing and is only reached by setting look = null
explicitly (the opt-out), or by not enabling the profile at all. A look
is presentation only (colors, fonts, opacity, shape, cursor, the
wallpaper): layout stays yours (gaps, column widths, module lists,
binds). The shipped looks carry their shape (rounded windows, ring
widths, bar padding and spacing; the bar stays opaque). To keep
square windows, set settings.: your
file wins per key.
Precedence, highest first, per key: a value you set in settings,
then the active look’s layer, then the app default. So
settings. beside
look = "music-desk" replaces that one color and keeps the rest of the
look. What Stylix sets in settings (where present) sits above the
layer, so a live swap cannot change those keys. One combination is
invalid the same way the Stylix one is: a wallpaper. you set
yourself beside a look’s shipped image (scoot refuses the two
together and the session carries on with background_color).
Set your own image instead (it wins per key: a [wallpaper] key of
yours keeps your whole section), or drop to look = null.
Every look is previewed in Theming, which also covers making your own.
Look targets (any app)
Section titled “Look targets (any app)”programs. themes an app scoot never
hard-codes, from a template you give: each entry links as a directory
symlink scoot/look-targets/<name> to a store directory of regular
files (target. plus the template). It reads exactly like a
hand-written target (see Look
targets) and needs no catalog and no
profile. The declared look still wins: targets only add rendered
files, never look tokens.
| Option | Type / default | Target reloads |
|---|---|---|
lookTargets. |
path or text, required | the template, rendered with the active look’s tokens on every apply |
lookTargets. |
string, required | where the rendered file lands, relative to the config home (kitty/colors.); ASCII only, no .., no absolute path, no hidden segments, no control characters (anything else fails the evaluation); staying under the config home through symlinks stays checked by scoot look validate |
lookTargets. |
list of string, default null (no reload) |
the program plus its arguments, run directly after a successful swap, never through a shell; a bare name on PATH or an absolute path (checked by scoot look validate, not at evaluation) |
lookTargets. |
1 to 300, default null (the tool’s 30 s) |
how long to wait before the hung child is killed; a failing reload is a note, never a rollback |
programs.scoot.lookTargets.kitty-colors = { template = ./kitty-colors.tmpl; output = "kitty/colors.conf"; reload.argv = [ "kitty" "@" "set-colors" "--all" ]; reload.timeout_secs = 10;};A name outside [a-z0-9-]{1,32} or an output outside the grammar fails
the evaluation naming the entry; staying under the config home through
symlinks (and the reload program shape) stays checked by
scoot look validate. A sync with any target configured (declared or
hand-written) renders fresh instead of pointing at the catalog render,
because the catalog is built without the templates (templates render
per user at apply time).
App theme
Section titled “App theme”One look themes every app together: picking music-desk sets the
GTK theme, the Qt style, the cursor, the fonts and the dark-mode
preference from that look in one pass. Every
value is an option, applied on rebuild/switch (newly started apps
pick the theme up; running apps re-read GTK’s settings. where
the toolkit watches it, the rest on restart; the greeter takes it
at the next login). The theme is files, packages and session variables
only, with no daemon, so it costs no wakeups.
| Piece | Option | Type | Default | Notes |
|---|---|---|---|---|
| GTK apps | desktop. |
bool | true |
settings. (GTK 3 + 4): Adwaita, or Adwaita-dark for a dark look, with the look’s proportional sans, cursor and dark preference, plus gtk. carrying the look’s accent and surfaces (named colors and the :root vars libadwaita paints with); the dark-mode signal is color-scheme, written by scoot look sync from the active look (no GTK_THEME force: it would make libadwaita skip its own providers) |
| Qt apps | desktop. |
bool | true |
qt6ct. through QT_QPA_PLATFORMTHEME=qt6ct (plus QT_PLUGIN_PATH for its platformtheme and style): the Adwaita Qt style in the look’s polarity, Adwaita icons, and a generated color scheme from the look’s palette — dark looks get a dark palette, the light look a light one |
| Cursor | desktop. |
bool | true |
Mocu at its native 24 px: the look layer names it for the compositor plus XCURSOR_THEME/XCURSOR_SIZE for X11 apps — the same art as the built-in fallback, as real theme files; the managed sync renders no cursor keys while the target is off |
| Fonts | desktop. |
bool | true |
DejaVu Sans for sans-serif and system-ui (proportional UI text in GTK/Qt apps and the greeter), FiraCode Nerd Font for monospace (DejaVu Sans Mono for radial-burst), through fontconfig, and the look layer carries the bar’s DroidSansM Nerd Font Propo file, whose glyphs carry the module icons; the managed sync renders no bar font while the target is off. The profile installs all three families (NixOS system-wide, home-manager per user with fonts. on by default) so scoot look apply resolves them; a family still missing falls back with a note instead of refusing |
| Web-page fonts | desktop. |
list of package | [ pkgs. (about 9 MB; [ ] off Linux) |
Installed with the fonts target. Liberation answers Arial, Times New Roman and Courier New, TeX Gyre answers Helvetica, Times and Courier, through fontconfig’s metric aliases, so a page that names them keeps the sizes and line breaks it was written for. Set [ ] to skip them; every such name then falls back to fontconfig’s default |
| Login screen | desktop. |
bool | true |
the session wallpaper behind the login card, the dark setting, the look’s ReGreet CSS and font — needs greeter. too |
| Terminal | desktop. |
bool | true |
the look’s foot. from the flake |
| Prompt | desktop. |
bool | true |
the look’s starship. (radial-burst ships none: inert there) |
| Editor | desktop. |
bool | true |
the look’s Helix config beside its theme (radial-burst ships none: inert there), plus the look’s Neovim colorscheme, linked under its fixed name and loaded on demand, never selected |
| Editor select | desktop. |
bool | false |
select the linked Neovim colorscheme at startup (a plugin/ file running colorscheme scoot-look); off leaves the scheme loaded but unselected, and home-manager’s programs. skips the selection (select the scheme in your own init) |
| Monitor | desktop. |
bool | true |
the look’s btop config beside its theme (radial-burst ships none, music-desk ships no theme: inert there) |
| Input method | desktop. |
bool | true |
a generated scoot-look classic-UI theme (background and text, selection and border from the look’s palette) plus the classicui. naming it — needs inputMethod. too |
| Extra GTK keys | desktop. |
attrs of str | { } |
merged over the generated settings.: a value here wins per key |
The look’s fontconfig rules apply only when a client asks for the
generic family first (sans-serif, system-ui or monospace ahead of
any named family), which is what toolkits and terminals do. A web page’s
font-family: Helvetica, sans-serif, or an app that names “Arial” or
“DejaVu Sans”, gets that family, or fontconfig’s metric substitute for
it, not the look’s face. A request that names a family the system lacks
before the generic (Foo, sans-serif or Foo, monospace) gets the
system’s default for that generic, not the look’s face.
On NixOS the profile also turns on programs. (as a
default, so your own false wins): scoot look sync writes the
dark-mode signal into dconf after the links land, and its activation
needs the system’s dconf service to do that. With the profile on only
in your home config, set programs. in the
system config yourself.
Set any target to false to keep that piece’s own style while the
rest follows the look, for example to leave alone an app you already
themed:
programs.scoot.desktop.theme.targets.gtk.enable = false; # my GTK is hand-tunedThe cursor is Mocu: the profile
installs it as real XCursor files (Mocu, white at its native 24 px, with
Mocu-Black beside it in the same package), the look layer names it for
the compositor, and the theme exports it for X11/XWayland apps through
XCURSOR_THEME/XCURSOR_SIZE. It is the same art scoot draws as its
built-in fallback cursors (see
Appearance). The busy
cursors are static first frames in both, since Mocu’s animation frames
are not vendored. To use
another theme, point desktop. at it and name it in
settings. (a value there wins per key); set
targets. to leave the cursor alone entirely.
If you theme an app through its own home-manager module instead
(programs., programs., programs.,
programs.), that module owns the same file and wins: the
look’s file stays out, so the two never conflict. The same holds for
GTK and Qt, whose modules live at the top level rather than under
programs: gtk. owns both settings. files, and only
what it actually writes besides them (its extraCss or theme owns
gtk., its colorScheme the dconf color-scheme leaf), while
qt. with qt6ctSettings owns qt6ct.. The look’s file
stays out of each owned path while the rest stays themed, so enabling
the upstream module beside the profile evaluates cleanly with your file
winning.
Precedence, highest first, per key: a value you set (in
settings, in theme., or in the app’s own module), then the
active look’s layer, then the toolkit default. What Stylix sets in
settings (where present) sits above the layer, so a live swap cannot
change those keys. So theme. beside look = "music-desk" replaces that one key
and keeps the rest of the look.
QT_PLUGIN_PATH composes instead of replacing: the systemd user
session (environment.) holds one string per variable, so a value
you set there wins whole, as with home-manager’s own Qt module for its
profile dirs. Shells get both: the theme
publishes its plugin dirs through home.,
which home-manager prepends before your own $QT_PLUGIN_PATH at
login, so an input-method path you export keeps working beside the
theme’s qt6ct and style dirs. Put extra plugin dirs in
home. to compose everywhere shells reach.
Symptom: a Qt app ignores the theme (default fusion look, wrong colors).
Check
QT_QPA_PLATFORMTHEMEin the app’s environment (systemctl --user show-environment | grep QT_QPA) and thatqt6ctplusadwaita-qt6are installed (nix-store -q --requisiteswill not list them iftargets.is off or its packages are null). A light palette inside a dark look means the scheme did not apply: checkqt custom_palette=trueandcolor_scheme_pathin~/.point at the generatedconfig/qt6ct/qt6ct. conf colors/scoot-look.. If you export your ownconf QT_PLUGIN_PATH(an input method, say) and Qt apps lose the theme in some launcher but not in a terminal, your manager-level value is winning whole over the theme’s dirs there. Move it tohome.so shells compose both. Apps started before the switch keep the old theme; restart them.sessionSearchVariables
Symptom: a libadwaita app stays light under a dark look (or dark under the light one).
The look’s dark mode reaches libadwaita through dconf (
org.), not throughgnome. desktop. interface color-scheme settings.alone. Read it back withini dconf read /org/gnome/desktop/interface/color-scheme(dark looks say'prefer-dark', the light look'prefer-light'). If the key is right and the app is still wrong, it started before the switch; restart it.gtk-application-prefer-dark-themeinsettings.stays for plain GTK and older libadwaita; the pinned libadwaita only warns on it.ini
Symptom: a Flatpak app ignores the theme.
The sandbox hides your config: give it the files with
flatpak override --filesystem=xdg-config/gtk-3.(and the theme packages with0:ro --filesystem=xdg-config/gtk-4. 0:ro <app-id> --filesystem=/nix/store:roon NixOS), or install the matchingorg.extension. The look’s fontconfig and cursor need the same treatment.gtk. Gtk3theme. *
X11 apps (XWayland) read the same settings. and qt6ct.; no
settings daemon runs for them. The theme sets no Xft., because the
compositor publishes the output scale and XWayland follows it.
Look-independent effects
Section titled “Look-independent effects”Effects (shadow, fill, motion, card style, title weight) belong to the look,
and the daemons also take them from their own files, where they win per key
over any look. desktop. writes those file tables for you, so
one setting applies to every look, shipped or your own:
programs.scoot.desktop.theme.overrides = { effects = { shadow = "soft"; motion = "spring"; }; card.mark = "stripe"; title.weight = 600;};| Option | Type / default | Written to |
|---|---|---|
desktop. |
attrs of strings, numbers and booleans, { } |
[effects] of notify. and launch. |
desktop. |
same | [card] of notify. |
desktop. |
same | [title] of notify. |
The keys and values are the ones in the notifier’s
Effects over any look
and the launcher’s
Effects over any look; the
daemons check them, and a bad value is ignored with a log line. A table left
empty writes nothing, so a profile that sets none renders the same files as
without this option. Each key is set at mkDefault, so programs.
(or programs.) still wins for that key. Changing an
option reloads the running notifier in place; the launcher reads it at its
next open.
The launcher’s file is written only when programs. is imported
(homeModules. or nixosModules.) and enabled; the
profile does not import it. card and title never reach launch..
Symptom: the overrides are set and the launcher ignores them.
programs.is off, or its module is not imported, so noscootlaunch. enable launch.is generated. Import the module and enable it (the profile enables it for you whentoml desktop.).launcher. daemon = "scootlaunch"
Wallpaper from a link
Section titled “Wallpaper from a link”The session wallpaper (scootbg) can be a link:
point set (or scoot’s [wallpaper] image) at an http(s) URL and
the daemon downloads it once, caches it under ~/., and
shows it like any other image. Pin it with sha256 so a changed byte
fails loudly instead of landing on screen:
programs.scoot.settings.wallpaper = { image = "https://example.com/hills.jpg"; sha256 = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"; mode = "fill";};Until the download lands, and when it fails, the compositor’s background shows and the error says why. Nothing blocks and nothing retries in a loop. Full detail (cache layout, the retry rules, every failure mode) is in Wallpaper from a link.
