# Appearance (/docs/haus/rooms/appearance)



The whole system shares one palette: **nebelung**, a silver-mist Catppuccin
variant with the blue pulled out of the greys and the accents calmed down. Grey
is the point. It is named for a cat breed the colour of high fog.

It renders onto **50+ tools**, from the terminal to the browser. Three knobs
compose (the accent, the contrast, and dark or light) and all three land on the
next `haus rebuild`. Every hex and a preview of all four variants is in the
[nebelung repo](https://github.com/hausfold/nebelung), a flake you can consume
without haus, `nebelung.palette` giving you name → "#hex".

## The accent [#the-accent]

The greys stay fixed; only the accent hue moves.

```nix
haus.theme.accent = "sapphire";
```

Any of the fourteen Catppuccin accent names works, and the default is `mauve`:

`rosewater` · `flamingo` · `pink` · `mauve` · `red` · `maroon` · `peach` ·
`yellow` · `green` · `teal` · `sky` · `sapphire` · `blue` · `lavender`

It re-tints the tools with per-accent variants (lazygit, fzf, yazi, glow, Zen,
the [generated desktop](#the-desktop)); single-file dotfiles keep their built-in
theme.

<Callout type="warn" title="It moves Zen's chrome, not the web">
  github.com's Catppuccin theme is a *userstyle*: LESS source, compiled rather
  than copied, which is why no palette file haus writes can reach it. haus
  compiles the ones you name at build time in your accent and flavor:

  ```nix
  haus.zen.userStyles = [ "github" "youtube" ];
  ```

  A name that doesn't exist fails the build listing all 134. Zen reads the file
  once, at startup, so quit it and reopen. github and youtube together are \~320 KB
  on every document, so keep the list short.
  `haus.zen.extensions.stylus = { };` deploys the extension unthemed, and its copy
  loses to haus's `!important` sheet anyway.
</Callout>

## Contrast [#contrast]

The default greys are deliberately soft. If text doesn't separate enough:

```nix
haus.theme.contrast = "high";
```

Same hues, same accents; the neutral ramp is pulled apart in OKLCH. It reaches
everything haus colours and no native macOS app, so a genuinely high-contrast
Mac wants `haus.accessibility.increaseContrast = true` too, which needs Full
Disk Access on whatever you rebuild from. For bigger type along with it,
[`haus.appearance.largePrint`](/docs/haus/rooms/displays) moves the interface
scale, both contrast lifts and `displays.main.uiScale` together, each as a
default you can still pin by hand.

<Callout type="warn" title="Turning large print off is not the mirror image">
  `haus.appearance.largePrint = false` puts the interface scale and the contrast
  lift back. Three settings stay where the profile left them: macOS's own
  Increase Contrast, the display's scaled resolution, and the Dock's icon size,
  which `haus.ui.scale` moves on the profile's behalf. Each of the three is a key
  haus writes only while something declares it, so `false` stops declaring rather
  than declaring the opposite, and not writing a key is not the same as writing
  the old value back.

  These put the three back:

  ```nix
  haus.accessibility.increaseContrast = false;
  haus.displays.main.uiScale          = "default";
  system.defaults.dock.tilesize       = 48;
  ```

  They are assertions rather than undos, so delete them again after the rebuild
  unless you meant to keep them: a pinned `tilesize` outranks `haus.ui.scale`, so
  a later scale change would move everything except your Dock, and a named
  `uiScale` puts that panel under management instead of leaving it alone. Setting
  `increaseContrast` either way needs Full Disk Access.

  They also land you on a stock Mac rather than on your own old values, and
  [`haus capture`](/docs/haus/keeping-it-current) helps less here than it looks.
  A snapshot restores through `defaults import`, which merges: a key the snapshot
  holds goes back to what it held, and one that was unset when you captured is
  not removed again. The Dock and the contrast key are both normally unset on a
  Mac that never asked for them, which is the case it cannot help with. If you
  did pick your own Dock size, a plain `haus capture` covers it; your own
  contrast needs `haus capture com.apple.universalaccess` by name, and Full Disk
  Access at restore time or it is skipped. Write the display's mode down
  yourself: it is in no preference domain and no snapshot at all.
</Callout>

## Light mode [#light-mode]

```nix
haus.theme.flavor = "latte";
```

Not an inversion: the same recipe applied to Catppuccin **Latte**, so tools take
their light-mode branches properly. Body text measures 11.3:1 on mocha and
7.0:1 on latte, 19.9:1 and 9.9:1 at `contrast = "high"`, all four held above the
AAA floor by nebelung's CI.

<Callout type="warn" title="macOS's own appearance is opt-in">
  haus leaves **System Settings ▸ Appearance** alone in both directions, so a
  light config on a dark Mac looks half-finished:

  ```nix
  haus.theme.systemAppearance = "flavor";  # latte → Light, mocha → Dark
  ```

  `"light"` and `"dark"` pin it instead; the default `"unmanaged"` never undoes an
  appearance you set by hand. It needs an **Automation** grant for whatever runs
  your rebuild (Privacy & Security ▸ Automation); without one haus tells you and
  changes nothing. On **Auto**, macOS goes on switching polarity itself.

  **The launcher and the shelf** follow macOS at runtime, so under `latte` they
  stay dark on a dark Mac. `haus.launcher.followSystemAppearance = false` and
  `haus.shelf.followSystemAppearance = false` pin them.
</Callout>

## Motion [#motion]

One line stops the movement **haus** itself draws. macOS's own is elsewhere:
[`haus.animations`](/docs/haus/reference/options#haus-animations) curates the
Dock's timings, and `haus.accessibility.reduceMotion` is Apple's switch.

```nix
haus.appearance.reduceMotion = true;
```

| What stops                                        | Its own option                   |
| ------------------------------------------------- | -------------------------------- |
| the logo pill's six-accent sweep on hover         | `haus.bar.logo.sweep`            |
| a long track title sweeping past the media pill   | `haus.bar.media.marquee`         |
| the same in the calendar pill                     | `haus.bar.calendar.marquee`      |
| the pointer jumping to the window that took focus | `haus.windows.mouseFollowsFocus` |
| leaving a workspace when its last window closes   | `haus.windows.gravity`           |

Each is set as a **default**, so `reduceMotion = true` beside
`haus.bar.logo.sweep = true` keeps the sweep and drops the rest. A title too
long for its pill is clipped rather than swept, and the dropdowns still carry it
whole. **Gravity**, the last row, is the largest unasked movement haus makes: a
screenful replaced in a blink because an app quit.

<Callout type="warn" title="It also sets macOS's Reduce Motion, which reaches the web">
  Browsers read `haus.accessibility.reduceMotion` as
  `prefers-reduced-motion: reduce`, so a site whose scroll-reveal is what makes
  its text appear will show you nothing. Keep the local half alone:

  ```nix
  haus.appearance.reduceMotion = true;
  haus.accessibility.reduceMotion = false;
  ```

  Apple's half needs the same Full Disk Access; the five rows above need no
  permission at all.
</Callout>

## Type [#type]

`haus.fonts.mono` is what the machine is *drawn in*: the terminal, and every
pill label and icon in the bar. `haus.fonts.sans` is the proportional half, and
one line moves all of it:

```nix
haus.fonts.sans.name        = "Atkinson Hyperlegible";
haus.fonts.sans.packageName = "atkinson-hyperlegible";
```

It reaches the launcher and its rows, the notch shelf, every notification
banner, each of their settings windows, and the clock pill's date and time when
[`haus.bar.clock.monoFont`](/docs/haus/reference/options#haus-bar-clock-monofont)
is false. The default there is macOS's own UI font, whose zero has no dot, which
is what the clock's opt-out is for.

`package` takes a package and `packageName` its name in nixpkgs. Set one or the
other, never both, whenever you name a family the Mac hasn't got. A
[desktop](/docs/haus/desktops/creating) file can only use `packageName`, because
reaching `pkgs` is what that format forbids.

<Callout type="warn" title="A family that isn't there falls back silently">
  No tofu, and no warning as there is for the mono half, so a misspelling reads as
  "that option does nothing": check it against Font Book. It leaves the **system**
  UI font alone, macOS having no supported knob for it, and never touches
  monospaced text.
</Callout>

## The desktop [#the-desktop]

The wallpaper is generated on your machine rather than shipped as a picture:

```nix
haus.wallpaper.style = "minimal";   # none | minimal | orbits | constellation | flow | bold
```

`minimal&#x60; is what hacker sets: one flat colour from your palette, the &#x2A;*⌂** mark
at the centre in the family's six accents, a broad bloom in your accent behind
it. `"none"` is the option's own default and what the foundation leaves it at:
it runs nothing and leaves the wallpaper you have. The installer does not ask;
`HAUS_WALLPAPER` names a look at install time.

| Knob                          |                                                                                                                                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `haus.wallpaper.depth`        | `0` to `5`, how far in from the palette's outermost tone the field sits. `1` (default) is a step below what your terminal draws on. `haus.wallpaper.background` takes a hex instead. |
| `haus.wallpaper.mark.*`       | `enable`, `size`, `weight`, `opacity`, `rise`, `color`: `spectrum` (default), `muted`, `ink` or `accent`.                                                                            |
| `haus.wallpaper.glow.*`       | the bloom: `color` (your accent), `strength`, `spread`.                                                                                                                              |
| `haus.wallpaper.grain`        | film grain, which stops the bloom banding into rings. `0` only makes sense with `glow.enable = false`.                                                                               |
| `haus.wallpaper.size`         | the render size. Anything but your display's **native** pixel count (`system_profiler SPDisplaysDataType` prints it) is resampled, and resampling undoes the dither.                 |
| `haus.wallpaper.debug.enable` | off by default: which revision of each family repo this machine was built from, small at the bottom left, where a tiled window's corner covers it.                                   |

Preview any of it without rebuilding a Mac:

```sh
nix build github:hausfold/haus#wallpaper && open result
```

## Apps you added yourself [#apps-you-added-yourself]

An app you [added yourself](/docs/haus/rooms/apps) is themed too, as long as the
id you keyed it under matches a nebelung port:

```nix
haus.roster.zed = { key = "x"; name = "Zed"; appId = "dev.zed.Zed"; cask = "zed"; };
```

Its theme lands where that app looks (`~/.config/zed/themes/`) in your current
flavor, contrast and accent, and is rewritten whenever you change them. The key
is the **roster id**: `zed`, not `zed-editor`. It writes into apps haus did not
install; `haus.theme.ports.enable = false` stops the whole pass.

That works for apps that read a fixed path. Xcode, Warp, OBS and JetBrains have
no file interface for *choosing* a theme, so the file is placed and the one-time
pick stays yours; Slack and Raycast can't be installed by file at all, so `haus doctor` hands you the hex to paste into
Slack ▸ Preferences ▸ Themes. It says where every port stands, and the ones that
would need merging into a config file you own (VS Code's `settings.json`) are
**listed but never written**. nebelung's
[ports table](https://github.com/hausfold/nebelung/blob/main/docs/ports.md)
publishes which is which.

## Obsidian [#obsidian]

Obsidian keeps its theme *inside each vault*, so list yours as home-relative
paths:

```nix
haus.terminal.obsidianVaults = [
  "Library/Mobile Documents/iCloud~md~obsidian/Documents/notes"
];
```

Each gets the theme and has it selected, its other appearance settings
preserved; a path with no `.obsidian` directory is skipped with a warning.
Light mode doesn't reach Obsidian yet, so it stays dark.

## Options [#options]

[Every setting, with types and defaults](/docs/haus/reference/options#appearance).
