Platform UI Guidelines
A breadth-first survey of how eight platforms let an application follow the user's OS-level appearance preferences — not merely light and dark, but the accent color, the contrast level, forced colors and reduced motion — and of what an application must do with those preferences once it has them.
The motivating problem is concrete. hue bases every color it draws on a single sparkles.ui.theme.Theme value, chosen by name and then fixed for the life of the process. A user in dark mode gets whatever the default theme is; a user who switches to light mode at dawn gets no change at all until they restart. The survey asks what the platforms offer, what the field's consensus is, and what it would take to derive that Theme from the OS instead of authoring it.
This survey answers seven questions:
- What is the shared vocabulary — the appearance triple, semantic vs literal color, tone, quantized accents, forced colors, push vs poll, and why capability flags beat a support bit? See concepts.
- What each platform exposes, and how — the preference surface, the query API, the change-notification mechanism, and whether a palette comes with it. See the master catalog.
- What a self-rendering application can actually reach — with no native toolkit linked, which is the position
sparkles:uiis in on every target. See comparison § Dimension 6. - How to turn three scalars into a palette — tone placement, the tone-delta rule (measured, not assumed), and per-role accent resolution. See color-derivation.
- How somebody already solved this — libadwaita follows the native appearance on six platforms behind one interface. See libadwaita.
- Where the field agrees and where it genuinely forks, and where Sparkles stands against it today. See comparison and its delta table.
- What Sparkles should build — a milestoned plan for
sparkles:appearanceand theThemederivation. See the proposal.
NOTE
Scope. This tree is about following OS preferences, not about window-system integration generally — for decorations, scaling, input translation and the event loop, see window-system-integration, whose os-apis subtree covers the same platforms from the windowing side. Where this survey needs a platform's windowing behaviour it links there rather than restating it.
Last reviewed: August 9, 2026
Master Catalog
One row per surveyed subject; the name links to its deep-dive. "Ships a palette" means the platform hands the application resolved colors rather than preferences it must derive from. "Reachable" is the survey's key column for Sparkles: whether a self-rendering application can read the preference without linking the platform's UI toolkit.
| Subject | Category | Scheme | Accent | Contrast | Push | Ships a palette | Reachable without the toolkit |
|---|---|---|---|---|---|---|---|
| iOS / iPadOS | mobile OS | 3-valued | none | 2-valued | ✓ | ✓ semantic | no — UIKit traits only |
| Android | mobile OS | 3-valued | wallpaper-seeded | 3-valued | ✓ | ✓ generated (5 × 13) | scheme only; rest needs JNI |
| GNOME | Linux desktop | 3-valued | 9 named | 2-valued | ✓ | — | yes — D-Bus portal |
| KDE Plasma | Linux desktop | 3-valued | free RGB | native only | ✓ | ✓ 8 role sets | yes — INI + portal |
| Windows | desktop OS | 2-valued | free RGB + ladder | forced colors | ✓ | — | partly — accent needs WinRT |
| macOS | desktop OS | 2-valued | 8 indexed | 2-valued | ✓ racy | ✓ semantic | yes — global defaults |
| Terminal emulators | pty protocol | 2-valued | none | none | ✓ | the 16-color palette | yes — two escape sequences |
| The web | sandboxed runtime | 2-valued | none | 4-valued | ✓ | — | n/a — it is the sandbox |
libadwaita AdwSettings | prior art (toolkit) | abstracts 6 platforms | quantized to 9 | boolean | ✓ | — | (the reference implementation) |
Two facts frame the whole survey and are visible in the table. Light/dark is universal and solved; nothing else is — accent is missing on three platforms, contrast on two, and no two platforms agree on what "contrast" means. And push notification is universal, including over a pty — which makes re-theming at runtime, not reading at startup, the real requirement.
Library & platform deep-dives
Every deep-dive applies the same seven-dimension spine — preference surface, reading it, change notification, what the platform derives for you, what the vendor's guidelines require, reachability from a non-native application, and traps — so the pages can be read against each other. Where a dimension does not apply, the absence is recorded as a finding (the terminal has no accent; iOS has no accent; KDE exposes no contrast neutrally).
| Category | Subjects |
|---|---|
| Mobile | iOS / iPadOS · Android (Material You) |
| Linux desktop | GNOME (portal) · KDE Plasma |
| Desktop OS | Windows · macOS |
| Non-GUI surfaces | Terminal emulators · The web |
| Prior art | libadwaita AdwSettings |
| Applying it | Color derivation · Comparison · Sparkles proposal |
Taxonomy
By what the platform hands you
| The platform gives you | Subjects | The application must… |
|---|---|---|
| A complete semantic palette | iOS, macOS | name roles; write no color code |
| A generated tonal palette | Android | name tokens; the wallpaper drives the rest |
| A full role-scoped palette | KDE | map its roles onto its own |
| Scalars only | GNOME, Windows, terminal, web | derive everything — see color-derivation |
By how the scheme is answered
| Mechanism | Subjects |
|---|---|
| A semantic value | iOS, Android, GNOME, KDE, macOS, terminal (mode 2031), web |
| Inferred from a color | Windows (documented!), terminal (OSC 11 fallback) |
By accent model
| Model | Subjects |
|---|---|
| Free RGB | KDE, Windows, Android |
| Quantized to a named set | GNOME (9), macOS (8), libadwaita (9) |
| Not exposed | iOS, terminal, web |
Milestones
When each capability became available to applications.
| Year | Milestone |
|---|---|
| 2018 | macOS Mojave ships Dark Mode; NSAppearance gains darkAqua |
| 2019 | iOS 13 adds UITraitCollection.userInterfaceStyle; Android 10 adds system-wide dark theme |
| 2019 | prefers-color-scheme reaches all major browsers (widely available Jan 2020) |
| 2019 | Windows 10 exposes dark mode to Win32 via UISettings; DWMWA_USE_IMMERSIVE_DARK_MODE 19→20 |
| 2021 | Android 12 ships Material You — wallpaper-seeded tonal palettes as android.R.color.system_* |
| 2021 | org.freedesktop.appearance color-scheme lands in xdg-desktop-portal |
| 2023 | xdg-desktop-portal 1.17.1 adds accent-color |
| 2023 | Android 14 adds a three-step contrast level (UiModeManager.getContrast()) |
| 2023 | iOS 17 replaces traitCollectionDidChange with registerForTraitChanges |
| 2024 | libadwaita 1.6 adds accent colors, with native macOS and Windows backends |
| 2024 | light-dark() reaches baseline availability in CSS |
| 2024–2025 | DEC mode 2031 spreads across terminals — Contour, Ghostty 1.0, foot 1.23, kitty, iTerm2, VTE, tmux 3.6 |
The bottom row is the newest and, for hue, the most consequential: terminal applications only recently gained the semantic answer plus change notification that GUI applications have had since 2018.
Runnable examples
Four programs the ci helper compiles and runs on every pass, so the behaviour these pages describe cannot rot silently. Each prints SKIP: and exits 0 where the host lacks the capability.
| Example | Backs | What it proves |
|---|---|---|
color-derivation/examples/derive-palette.d | color-derivation | the tone-delta rule misses its claimed 4.5:1 by 0.022; derived palettes clear WCAG AA |
gnome/examples/portal-appearance.d | GNOME | GNOME's accent is one of libadwaita's nine; reduced-motion answers NotFound on a live v2 portal |
kde/examples/kdeglobals-appearance.d | KDE | Colors:View and Colors:Window genuinely differ (#141618 vs #202326 in Breeze Dark) |
terminal/examples/color-scheme-probe.d | terminal | mode 2031 and OSC 11 round-trip, and a silent terminal is handled by timeout, not a hang |
Suggested reading paths
"I want the conclusions."comparison → its delta table → the proposal.
"I'm implementing the Sparkles feature this informs."concepts → libadwaita (the abstraction to copy) → color-derivation (the math) → terminal (the first backend) → the proposal.
"I'm porting hue to a specific platform."concepts → that platform's deep-dive → comparison § what follows the system → the relevant proposal milestone.
"I only care about the terminal."terminal → color-scheme-probe.d → proposal § P1.
"I want to understand the color math."concepts § tone → color-derivation → derive-palette.d.
Sources
Primary sources are cited per deep-dive; each page's Sources section is its provenance. The recurring ones:
- XDG Desktop Portal —
org.freedesktop.portal.Settings - libadwaita
src/adw-settings*.c, pinned at01d51e39 - Material Design 3 and AOSP dynamic color
- Apple HIG — Dark Mode and
UITraitCollection - Support Dark and Light themes in Win32 apps
- Color scheme reporting — DEC mode 2031
- MDN — CSS user-preference media features
Live captures on a GNOME session with portal Settings v2, and pty-harness captures of a mode-2031 terminal, were taken in August 2026 and are reproduced by the examples above.