Skip to main content

Crate theme

Crate theme 

Source
Expand description

The app theme — two concrete appearances, one token set.

Colors are precomputed from an oklch-derived neutral scale (perceptually even lightness steps; the same scale the reference Tailwind theme used) into gpui Hsla. Numbers drive layout, colors are paint: layout constants live in theme/layout.rs as plain numbers and never depend on which color is painted.

§Light is designed, not inverted

Mirroring lightness produces the classic “washed-out inverted” look, for three reasons this module handles explicitly:

  1. Surface order flips meaning. In dark, the main content panel is the darkest plane and raised surfaces get lighter. In light, the content panel is white and the shell/sidebar goes grey — chrome recedes by getting darker, not lighter. Popovers stay white and earn separation from a border and shadow rather than from lightness.
  2. Elevation reverses. On dark, a faint white wash means “raised”. Its literal translation — a faint black wash on white — means “recessed”, so the composer read as a dent instead of a plate. Light lifts with white plus a border and shadow (Theme::input_bg, the elevation ladder). Fill alphas carry over unchanged (INK_FILL_SCALE); only hairlines scale, so a 1px edge survives a bright surround (INK_HAIRLINE_SCALE).
  3. Accents must move down the scale. The dark palette’s 400-level accents (red/amber, and whatever hue an app sets Theme::accent to) are chosen for contrast against near-black; on white they fall to 2–4:1 and fail WCAG AA. Light mode uses the 600-level siblings at the same hue, which restores the contrast ratio the dark token had.

Text tones are chosen so each light token lands within ~0.5 of its dark counterpart’s contrast ratio against its own background — the pairing is verified in tests/theme.rs, not eyeballed.

Installed as a gpui Global at boot; read with Theme::of.

Re-exports§

pub use paint::INK_FILL_SCALE;
pub use paint::INK_HAIRLINE_SCALE;
pub use paint::SCRIM_ALPHA_DARK;
pub use paint::band;
pub use paint::card_selected_bg;
pub use paint::card_selected_shadows;
pub use paint::content_shadows;
pub use paint::current_appearance;
pub use paint::frame_shadows;
pub use paint::glass_selected_bg;
pub use paint::glass_selected_shadows;
pub use paint::hairline;
pub use paint::ink;
pub use paint::scrim;
pub use paint::set_current_appearance;
pub use paint::surface_shadows;
pub use paint::theme_generation;
pub use paint::user_bubble_bg;
pub use paint::wash;

Modules§

appearance
Light/dark switching: what the user asked for, what the OS reports, and the plumbing that turns a change in either into a repaint.

Structs§

Brand
What an app changes about the shipped palette without redesigning it.
Ink
Text ink for both appearances.
MaterialSpec
The frost material, before a thickness picks its opacity.
Metrics
One role as it is actually set: a rung on the ladder, the leading it carries, and the weight it is set in.
Seed
The few colours a whole variant is grown from: a background, the text on it and an accent, with the diff and highlight colours optional.
SurfaceSpec
One surface’s numbers. out = gain * saturated(backdrop) + tint, over a backdrop blurred at blur.
SyntaxPalette
Paint-only syntax colors. The hues follow the Git history graph’s lane palette (indigo, pink, emerald, amber, red, neutral), while light-mode variants are darkened enough to remain readable as text on white.
TextInk
The alpha of each text role over the palette’s ink — the colour of Theme::text at full opacity. The shipped palettes’ ink is white in dark and black in light.
Theme
The app theme. Two concrete instances — Theme::dark and Theme::light.
ThemeFamily
One theme family, as a file declares it:
Tint
A hue and how much of it, in oklch terms. chroma: 0.0 is the shipped neutral, so Brand::default reproduces the built-in palette exactly.
Variant
What one variant sets: a Seed and the tokens laid over it. A token neither sets keeps the shipped palette’s value.

Enums§

Appearance
Which appearance the app is painting.
ControlSize
A control’s size — SwiftUI’s ControlSize. A closed pair, not a scale: the two that ship are the row of a form and the chip on an overlay.
Glass
The two shipped glasses — SwiftUI’s Glass.regular and Glass.clear. A closed variant rather than knobs: Apple exposes no numbers on glass either, only the variant and a tint.
HighlightKind
The token category a highlighter paints with, mirroring SyntaxPalette field-for-field so classification can name a kind without knowing its color.
Material
SwiftUI’s frost scale. Measured 2026-08-31: the five thicknesses are ONE material at five opacities — the tone implied by tint / (1 - gain) holds to within 9% across the scale, and the sigma does not move at all (23.1/20.2/21.0pt). So this is a knob, not five looks.
SurfaceStyle
Which surface a caller names. Material and glass are different things with different vocabularies — a material has thickness, a glass has a variant — and they meet only at the numbers they resolve to.
TextStyle
A role in the type ladder — SwiftUI’s Font.TextStyle.
Vibrancy
Whether the window composites translucent — AppKit’s vibrancy, and Mica.

Constants§

BASE_COLORS
The greys a UI is built on, as oklch hue and chroma.
INK_FILL_SCALE
Light-mode alpha multiplier for fills (hover/active washes, chip and pill backgrounds).
INK_HAIRLINE_SCALE
Light-mode alpha multiplier for hairlines (borders, dividers, rings). Opposite of fills: a 1px edge has to hold its own against a bright surround, and the dark palette’s white hairlines are deliberately faint. Scaling up keeps separators legible instead of dissolving into the panel.
LEGIBLE
WCAG AA for body text: the most contrast kept asks text to keep.
LENSED
Whether this build has the backdrop-blur primitive behind it — the lens a glass surface refracts through, and the frost a card lays over the content it covers. Metal’s and wgpu’s; it tracks the gpui in use rather than the platform, and the DirectX renderer carries no such primitive.
SCRIM_ALPHA_DARK
Alpha of the standard modal backdrop in dark mode. Call sites that need a heavier or lighter scrim pass their own dark-mode alpha to scrim.

Traits§

AppExt
Configuration carried by the application. Import as use theme::AppExt as _;.
Sizing
The size ladder, on anything styled — SwiftUI’s .controlSize(..), and Typeset’s shape for the metrics that come with a type role.
ThemeExt
The carrier seam for catalog traits: any type holding a Theme exposes it through this one method, and every component group (ui::widgets::Scaffolding, …) extends it, so group methods read the environment through self.theme().
Typeset
The ladder, on anything styled.

Functions§

band
Recessed band behind a palette/picker header or footer strip.
base_text_size
The body size in points. TextStyle::Body’s own size paints the measured ladder.
behind_window_blending
Whether the compositor blends a material behind a translucent window — AppKit’s .behindWindow blending, and Mica on Windows.
card_selected_bg
Selected/keyboard-active treatment for rows and chips INSIDE a floating card (menu rows, the picker rail, segmented chips): --color-active again, tone-flipped by the palette convention so a card that is already the bright plane lifts with black at 6% rather than white.
card_selected_shadows
Selection outline for rows and chips INSIDE a floating card (menu rows, the picker rail, segmented chips): the inset ring alone, in both appearances. Card rows fill with a translucent wash (card_selected_bg), and a drop shadow — a filled rect painted BEHIND the element — shows straight through a translucent fill as a grey plate (the same lesson glass_selected_shadows records for dark glass). The card already carries the elevation shadow; selection inside it only needs the edge.
content_shadows
The shadow bezel::ui::window::content’s floating card casts on the chrome around it. Its blur stays inside Theme::CONTENT_INSET, which the card’s parent may clip at.
contrast_ratio
WCAG 2.1 contrast ratio between two opaque colors (1.0 … 21.0).
current_appearance
The appearance the context-free paint helpers are painting for.
flatten
Composite fg (which may be translucent) over an opaque bg, returning the opaque result — the color the eye actually receives.
frame_shadows
Centered frame shadow, with half-inset blur to leave room for its tail inside the transparent Theme::CLIENT_INSET band.
glass_selected_bg
Selected-state glass treatment (tabs, session rows, space rows): ../desktop’s --color-active — white at 12% on dark, black at 6% on light, translucent enough for the vibrancy to read through (heavier flat washes blocked the glass, user request).
glass_selected_shadows
The selected chip’s bright outline, as an INSET shadow: gpui paints inset shadows ON TOP of the background, edges only — a border with zero layout cost. Drop shadows are filled rects painted BEHIND the element, and behind a 5% fill they showed straight through as an opaque dark plate with a greyed ring (user report) — nothing may paint behind a glass chip.
grey
An exact achromatic tone from an 8-bit channel value (grey(13) ≡ #0d0d0d) — for surfaces matched against reference-screenshot samples.
hairline
Translucent hairline ink for borders, dividers and rings: a light grey on dark, a dark grey on light at INK_HAIRLINE_SCALE of the alpha. Never pure white or black.
hsl_to_rgb
HSL (gpui convention, all 0..1) → sRGB components 0..1.
ink
Translucent fill ink for interactive states and chip plates: soft-white on dark, soft-black on light at INK_FILL_SCALE of the alpha.
ink_over
The colour to draw text in over plate. surface is what text was coloured for: the opaque page with the washes under it, a highlight mark or a find match. plate is what is painted under it now, a selection or a solid caret over that surface. text where it reads there at the better of the contrasts it kept on the page and on the surface, else the surface’s own colour (inverse video) where that does, else whichever of black and white reads better.
kept
The contrast text keeps when something is painted under it: what it has over page, up to LEGIBLE. Faint text is held to its own contrast, not raised to body text’s.
legible
The color to draw text (which may be translucent) in over the opaque plate: text where it reads at floor there, else text with its lightness inverted where that does, else whichever of black and white reads better.
legible_on
Whichever of a and b has the higher contrast ratio against plate, a on a tie.
lightness
The oklch lightness of an achromatic tone: neutral inverted.
mix
Linear per-component mix of two colors (paint helper for the gradient spinner).
mix_oklab
from moved t of the way to to (0..1) in OKLab, alpha alongside.
neutral
A neutral (chroma 0) oklch tone as Hsla. Chroma 0 means r == g == b exactly, so this goes straight to an achromatic Hsla (skipping the hue math avoids float-noise saturation).
oklch
Convert an oklch color (CSS notation: L 0..1, C, H in degrees) to gpui Hsla.
oklch_to_srgb
oklch → sRGB (each 0..1, clamped/gamut-clipped per channel). Reference: Björn Ottosson’s OKLab definition (the same matrices CSS Color 4 uses).
over
Composite top over under, both of which may be translucent: the one color that paints what the two painted in that order do, over anything.
relative_luminance
WCAG 2.1 relative luminance of an opaque color.
rgb_to_hsl
sRGB (0..1 components) → HSL, all components 0..1 (gpui’s Hsla convention).
scrim
Modal backdrop at alpha_dark (quoted, as everywhere, in dark-mode terms).
set_current_appearance
Point the context-free paint helpers at an appearance. Called by Theme::install; exposed for tests that build a theme without an App.
stack
layers, each of which may be translucent, painted in order over the opaque base: the color the eye receives where they all overlap.
surface_shadows
The elevation shadow a floating surface casts — shadow_lg’s shape, which is where it lived until the surface moved into the material. Painted cut to outside the shape, because the same drop shadow under a translucent fill is the grey plate card_selected_shadows records. SurfaceSpec::shadow says which looks carry one.
theme_generation
Monotonic id of the current palette.
tint
Re-emit an achromatic tone at hue, carrying as much chroma as its lightness can hold. Alpha rides through untouched.
user_bubble_bg
The user message bubble’s plate: the softened wash at 8% dark, 4% light. At the selection weight the bubble read too strong for settled content (user report), and an opaque plate before that read as a solid slab over glass.
wash
Interactive-state wash: a softened ink that stops short of pure black or white so hover plates read as tinted glass rather than paint.