# Overrides

Every component starts with the OS's own default. Start with no overrides, and add one only when
you want a specific difference. Each layer below changes less than the one after it, so pick the
smallest layer that does the job. The Overrides example uses every layer in one window.

> **Example: Overrides.** Source in [examples/overrides](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides). Live on the web and captured on macOS, Linux and Windows in the docs app.


| You want | Layer | Reach for |
| --- | --- | --- |
| One element tinted, rounded, or in another font | [element style](#element-style) | `.bg()`, `.rounded()`, `.font()` |
| One element different on one OS | [per platform](#per-platform) | `.on_macos(..)`, `.on_linux(..)` |
| A different native control, or a drawn one | [mapping](#mapping) | `.native(Mapping::..)`, `.drawn()` |
| New colors, accent, or radii | [theme](#theme) | `Application::theme`, `window.set_theme`, `.theme(..)` |
| A drawn menu, tooltip or titlebar instead of the OS's | [patterns](#patterns) | `.pattern(..)`, `Application::patterns`, `[patterns]` |

## Element style

Style methods apply to a native control when the control can express them. On a macOS button, a
fill becomes `bezelColor`, a text color becomes `contentTintColor`, and a font becomes an `NSFont`.

```rust
// Element style on a native control: tint and font map to bezelColor and NSFont
// on macOS; a radius AppKit cannot draw is ignored with a dev warning.
.child(
    button("rename", "Rename…")
        .dispatch(Rename)
        .bg(theme::forest())
        .text_color(theme::forest_ink())
        .font(tokens.fonts.rounded.clone()),
)
```

<sub>From [examples/overrides/src/lib.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/lib.rs), line 51.</sub>


When the native control can't draw a property, such as a corner radius on an `NSButton`, nd
ignores it, and a dev build prints one warning that names the fix. When you see that warning, move
to [mapping](#mapping) instead of stacking more style on top. Take the color from a token or a
theme file. A literal color in view code fails `nd check`.

## Per platform

`.on_macos`, `.on_linux`, `.on_windows`, and `.on_web` take a closure that runs only on that
platform, so the call site needs no `cfg`. Methods that mean something on only one OS, such as
`bezel`, exist on every target, and the other targets ignore them.

```rust
// Per platform: glass bezel on macOS, accent style on Windows, pill on Linux.
button("share", "Share")
    .icon(Icon::Share)
    .dispatch(Share)
    .on_macos(|b| b.bezel(Bezel::Glass))
    .on_windows(|b| b.accent())
    .on_linux(|b| b.rounded_full().px_4())
    .on_web(|b| b.outline()),
```

<sub>From [examples/overrides/src/lib.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/lib.rs), line 31.</sub>


Use it when the OSes offer different things: a glass bezel exists on macOS 26, and an accent
button style exists on Windows. Don't use it to give every platform the same pixels, because that
undoes the native look that the framework exists to keep. When two branches differ only in color
or radius, put the difference in the theme instead.

## Mapping

A mapping override changes which implementation draws an element. `.drawn()` forces the composed
implementation on every OS, and that implementation accepts every style override that the native
control can't. Today `slider` has it. `.native(Mapping::..)` picks another native control for the
same element: `select` can be a pop-up, a segmented control, or a radio group.

```rust
// Mapping override: the composed (drawn) slider on every OS, for a look the
// native control cannot express.
.child(slider("volume", self.volume).range(0.0..=1.0).drawn().h(px(28.)).rounded_full().on_change(
    cx.listener(|this, e: &SliderChanged, _window, cx| {
        this.volume = e.value;
        cx.notify();
    }),
))
// Mapping override: a picker shown as a segmented control instead of a pop-up.
.child(
    select("sort", ["Name", "Date", "Size"])
        .selected(self.sort)
        .native(Mapping::Segmented)
        .on_change(cx.listener(|this, e: &SelectChanged, _window, cx| {
            this.sort = e.index;
            cx.notify();
        })),
)
```

<sub>From [examples/overrides/src/lib.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/lib.rs), line 62.</sub>


A drawn control looks the same everywhere and doesn't follow OS restyling. Use `.drawn()` when the
exact look matters most, and use `.native(..)` when the OS has a control that fits.

## Theme

Theme overrides change tokens (see [Theming](https://native.formalsnake.dev/concepts/theming.md#tokens)). They stack, and a deeper layer
wins over a shallower one:

- App: `Application::theme(..)`, on top of the platform preset and palette sources.
- Window: `window.set_theme(..)` inside `open_window`, or `window.clear_theme(cx)`.
- Subtree: `.theme(|t| t.accent(..))` on any container; everything inside resolves against it.

```rust
/// The app with its app-level overrides.
pub fn app() -> Application {
    Application::new(nd::manifest!())
        // App-level theme override, applied on top of the platform preset.
        .theme(theme::app())
        // App-level pattern override; nativedesktop.toml [patterns] sits below this.
        .patterns(Patterns { tooltips: Pattern::Drawn, ..Patterns::default() })
}
```

<sub>From [examples/overrides/src/lib.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/lib.rs), line 105.</sub>


The app layer is in [theme.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/theme.rs), the only file in the
example that names colors:

```rust
//! The only place this app names colours. `nd check` allows colour literals in theme files.

use nd::prelude::*;

pub fn app() -> ThemeOverride {
    ThemeOverride::new().accent(rgb(0x3d6bff)).radius_scale(1.25)
}

pub fn warm(theme: ThemeRefinement) -> ThemeRefinement {
    theme.accent(rgb(0xff8a3d)).radius_scale(1.5)
}

pub fn warm_glass_tint() -> Hsla {
    rgba(0xff8a3d33).into()
}

pub fn forest() -> Hsla {
    rgb(0x2f6f4f).into()
}

pub fn forest_ink() -> Hsla {
    rgb(0xffffff).into()
}
```

<sub>From [examples/overrides/src/theme.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/theme.rs), line 1.</sub>


The subtree layer wraps a tinted glass container, so its accent button and radii differ from the
rest of the window:

```rust
// Subtree theme override: everything inside uses a warm accent and larger radii.
.child(
    glass()
        .variant(GlassVariant::Clear)
        .tint(theme::warm_glass_tint())
        .rounded_xl()
        .p_4()
        .gap_2()
        .theme(theme::warm)
        .child(text("Tinted glass with its own accent"))
        .child(
            h_flex().gap_2().child(button("ok", "Accept").primary()).child(button("no", "Decline")),
        )
        // Pattern override on one element: this context menu is drawn, not an NSMenu.
        .context_menu(|menu, _window, _cx| menu.item("Rename…", Rename).item("Share", Share))
        .pattern(Pattern::Drawn),
),
```

<sub>From [examples/overrides/src/lib.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/lib.rs), line 82.</sub>


`radius_scale` multiplies down the layers: an app scale of 1.25 and a subtree scale of 1.5 give
1.875. On macOS, the accent reaches native controls inside the subtree as their bezel color, and a
forced appearance sets the subtree's `NSAppearance`. To recolor the whole app in a way that users
can edit, ship a `theme.toml` instead of code (see [Theming](https://native.formalsnake.dev/concepts/theming.md#themetoml)).

## Patterns

A pattern is a structure that the OS draws itself, such as a menu, tooltip, popover, or titlebar.
A pattern override swaps the OS's version for one that nd draws. You can set it in these places,
from narrowest to widest:

- `.pattern(Pattern::Drawn)` on one container. It covers the menus and tooltips inside it.
- `Application::patterns(Patterns { tooltips: Pattern::Drawn, .. })` for the app.
- `[patterns]` in `nativedesktop.toml`, below the code: `tooltips`, `menus`, `popovers`,
  `context_menu` (each `native` or `drawn`), and `settings_window` (`window` or `sheet`).
- `WindowOptions::titlebar(Titlebar::Custom)` for a custom titlebar on macOS too.

Build a custom titlebar with `titlebar()`. It reserves the room that the OS controls need, so you
don't tune any margin by hand:

```rust
// Pattern override: a custom titlebar even on macOS. `titlebar()` reserves the OS
// controls' side and size (traffic lights left on macOS, caption buttons right on
// Windows, nothing on Hyprland), so no margin is tuned by hand.
.child(
    titlebar()
        .title("Overrides")
        .trailing(
            // Per platform: glass bezel on macOS, accent style on Windows, pill on Linux.
            button("share", "Share")
                .icon(Icon::Share)
                .dispatch(Share)
                .on_macos(|b| b.bezel(Bezel::Glass))
                .on_windows(|b| b.accent())
                .on_linux(|b| b.rounded_full().px_4())
                .on_web(|b| b.outline()),
        )
        // Coordinates instead of the default slot, macOS only.
        .on_macos(|t| t.controls(window_controls().at(point(px(18.), px(20.))))),
)
```

<sub>From [examples/overrides/src/lib.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overrides/src/lib.rs), line 23.</sub>


On macOS, the traffic lights sit on the left. On Windows, the caption buttons sit on the right. On
Linux, GNOME's `button-layout` decides, and Hyprland has no controls. See [Patterns](https://native.formalsnake.dev/concepts/patterns.md)
for what each structure maps to.

## When not to override

- Don't hand-tune padding, magic offsets, or one-off colors to make one screen look native. If a
  structure looks wrong on an OS, fix it in the backend so that every app gets the fix.
- Don't use `cfg(target_os)` in view code. `.on_*` keeps both branches type-checked on every
  target.
- Don't use `.drawn()` or a drawn pattern to avoid learning what the native control offers. A
  drawn control stops following the OS when the OS restyles.

To run the example's tests, use `cargo test -p overrides`. The appearance audit checks that its
colors are tokens (see [Theming](https://native.formalsnake.dev/concepts/theming.md#the-appearance-audit)). The macOS goldens compare the
showcase in light and dark mode, and the test process needs Screen Recording permission.
