Skip to content

KDE Plasma (KColorScheme / kdeglobals)

The desktop that hands an application a whole palette rather than a light/dark bit — and the one whose portal coverage is narrower than its native surface.

Preference surfacefull color scheme (8 role sets × ~10 roles) · accent color · scheme name
Canonical APIKColorScheme / KStatefulBrush (KDE Frameworks), over kdeglobals
Neutral APIorg.freedesktop.portal.Settingscolor-scheme, accent-color, reduced-motion only
Change notificationpushKConfigWatcher (org.kde.kconfig.notify D-Bus) or the portal signal
Palette derivationthe palette is the preference; Plasma computes states, apps consume them
Reachable w/o Qtyes — kdeglobals is plain INI; the portal covers the light/dark bit
Runnable examplekde/examples/kdeglobals-appearance.d

Overview

What it solves

Where GNOME exports a preference and expects the toolkit to own the colors, Plasma exports the colors. A KDE color scheme is a document listing concrete RGB values for every role in every context, and switching themes means switching that document. kdeglobals is where the active one is materialized: "when a color scheme is applied, its values are copied to ~/.config/kdeglobals".

The consequence for a follower is inverted relative to GNOME: on GNOME you receive a bit and must derive a palette; on Plasma you receive a palette and must decide how much of it to honour.

Design philosophy

Roles are two-dimensional. One axis is the set — which kind of surface is being painted — and the other is the role within that set. The eight sets Breeze exports, verified against the upstream BreezeDark.colors:

ini
[Colors:Window]           [Colors:View]        [Colors:Button]
[Colors:Selection]        [Colors:Tooltip]     [Colors:Complementary]
[Colors:Header]           [Colors:Header][Inactive]

Within each, BackgroundNormal, ForegroundNormal, ForegroundActive, ForegroundLink, ForegroundNegative/Neutral/Positive, DecorationFocus, DecorationHover and friends. KStatefulBrush then derives the disabled and inactive variants from [ColorEffects:Disabled] and [ColorEffects:Inactive] rather than storing them, so a scheme stays authorable by hand.

How it works

kdeglobals is the interface

ini
[General]
ColorScheme=BreezeDark
AccentColor=61,174,233

[Colors:View]
BackgroundNormal=20,22,24
ForegroundNormal=252,252,252

Colors are decimal r,g,b (occasionally with a fourth alpha component). AccentColor under [General] is the user's override; absent means "inherit the scheme's own", not "no accent" — the fallback is the scheme's DecorationFocus.

Eight color sets, not one

Running kdeglobals-appearance.d against the upstream BreezeDark.colors:

Output
[General] ColorScheme = BreezeDark
[General] AccentColor  = #3DAEE9 (61,174,233)

[Colors:*] sets present, with their normal fg/bg:
  Window             bg #202326   fg #FCFCFC
  View               bg #141618   fg #FCFCFC
  Button             bg #292C30   fg #FCFCFC
  Selection          bg #3DAEE9   fg #FCFCFC
  Tooltip            bg #292C30   fg #FCFCFC
  Complementary      bg #202326   fg #FCFCFC
  Header             bg #292C30   fg #FCFCFC
  Header (inactive)  bg #202326   fg #FCFCFC

=> document surface is Colors:View bg #141618 (Rec.601 luma 21 ⇒ dark)

Window is #202326 and View is #141618 — a genuine, visible difference. View is the document background; Window is the chrome band around it. A file viewer, an editor or a terminal paints on View. Picking Window because it is the first section in the file produces a surface that is subtly wrong on every Plasma scheme that distinguishes them, which is most of them.

For a viewer like hue the mapping is direct and worth writing down:

Plasma setsparkles:ui slot
Colors:View bg/fgpage background / default foreground
Colors:Window bg/fgchrome
Colors:Selection bgselection
Colors:Header bgchrome band, tab strip
[General] AccentColorchromeAccent, chromeFocused, caret
Colors:Tooltip bgsurface (popups)

The portal covers less than the desktop

Plasma ships xdg-desktop-portal-kde, so the portal route works here too. Its src/settings.cpp builds the org.freedesktop.appearance map from exactly three keys:

cpp
appearanceSettings.insert(colorScheme, readFdoColorScheme().variant());
appearanceSettings.insert(accentColor, readAccentColor().variant());
appearanceSettings.insert(reducedMotion, readReducedMotion().variant());

There is no contrast. That is the mirror image of the GNOME gap, where reduced-motion was missing from the deployed backend and contrast was present — and together the two make the general point: the neutral namespace is a union on paper and an intersection in practice. Probe per key.

Change notification

Two mechanisms, at different levels:

  • Native. Plasma rewrites kdeglobals and KConfigWatcher picks the change up — a KConfig-level watch backed by org.kde.kconfig.notify D-Bus signals plus a file watch. The KDE-side design note is that the watching is centralized: rather than every consumer watching the file, "only the KColorScheme library does it".
  • Neutral. The portal's SettingChanged, with the same caveats as on GNOME.

A non-KDE application should subscribe to the portal signal for the when and re-read kdeglobals for the what — the portal tells you something changed promptly and correctly; the file has the detail the portal does not carry.

What the guidelines require

KDE's Human Interface Guidelines ask applications to source colors from the active scheme rather than hard-coding them, and Plasma's own components resolve every color through KColorScheme. The accent-color work (D27263) made the accent loadable from kdeglobals rather than only from a scheme file, so a user can keep a scheme and re-tint it — which means an application must read both and let AccentColor win when present.

Reachability from a non-Qt application

Better than it looks. kdeglobals is plain INI at a well-known XDG path, and the values are already concrete RGB — no color-space work, no theme-name lookup, no Qt. The example parses it in ~120 lines of D with no dependency beyond sparkles:base for the color type.

Two rough edges a parser must handle:

  • [Colors:Header][Inactive] is two bracket groups on one line. A naive INI reader that takes everything between the first [ and the last ] gets Colors:Header][Inactive, which happens to work as a key; one that stops at the first ] silently merges the inactive header into the active one.
  • Section presence is meaningful. An older or partial scheme omits sets, and the correct response is to fall back along a documented chain (ViewWindow → the portal's light/dark bit), not to substitute black.

Traps

TrapConsequence
Using Colors:Window as the document backgroundwrong surface on nearly every scheme; View is the document
Expecting contrast from the KDE portalNotFound; Plasma exposes it nowhere neutral
Treating absent [General] AccentColor as "no accent"drops the scheme's own accent instead of inheriting it
Parsing [Colors:Header][Inactive] with a naive INI readerinactive header silently overwrites the active one
Watching only kdeglobals mtimePlasma writes the file more than once per change; debounce
Assuming the scheme name implies light/darkuser-installed schemes are named anything; read the colors

Strengths

  • The richest preference surface in the survey: an app that consumes it fully is genuinely indistinguishable from a native one.
  • No derivation needed — no contrast math, no tone algebra, no accent recipe.
  • Readable without linking anything, from any language.
  • The accent is a free color, not a nine-value enum, so users get real choice.

Weaknesses

  • The neutral (portal) surface is narrower than GNOME's, missing contrast.
  • The richness is only usable by an app whose slot vocabulary is close to Plasma's role vocabulary; anything else has to map, and the mapping is lossy.
  • Two sources of truth (scheme file and kdeglobals) with an override rule that has to be known.
  • No tonal structure: the scheme gives you the colors it gives you, and an app that needs an in-between tone must invent it.

Key design decisions and trade-offs

DecisionRationaleTrade-off
Export a full palette rather than a preference bitapps match the desktop exactly, with no derivationapps whose roles differ from Plasma's must map, losing fidelity
Materialize the active scheme into kdeglobalsone well-known file; language- and toolkit-neutrala second source of truth beside the scheme file
Derive disabled/inactive via [ColorEffects:*]schemes stay hand-authorable; states stay consistentconsumers must implement the effects or lose state differentiation
Accent as a free RGB in [General]real user choice, unlike GNOME's ninean app deriving from it cannot rely on a pre-tuned palette
Centralize watching in KColorSchemeone watcher, no thundering herd of file watchesnon-KDE consumers get no equivalent and must roll their own
Ship only three keys through the portalthe ones Plasma can answer honestly and cheaplycontrast is unreachable neutrally, so cross-desktop code cannot use it

Sources