Files
gpui-native-kit/docs/platform-metrics-audit.md
T
2026-11-07 13:24:31 +07:00

13 KiB
Raw Blame History

Platform metrics audit — Apple HIG, GNOME HIG, Fluent, KDE HIG

Why our controls read small and foreign next to native ones. Assessed 2026-11 against the primary sources: the libadwaita stylesheet (libadwaita main, src/stylesheet/**.scss), WinUI 3 theme resources (microsoft-ui-xaml CommonStyles) and the Learn design pages, Kirigami units.cpp + Breeze breezemetrics.h/breezestyle.cpp, and the Apple HIG pages. Apple publishes no pixel metrics, so macOS numbers are measured on this machine via AppKit fittingSize — note the OS-version variance called out below. Queue entry: CHECKLIST.md — "Platform metric fidelity".

Verdict

Three real problems, none of them "spacing values are off by a bit":

  1. Heights. control_height is derived as 1.75 × root_font_size. Native platforms do not size controls that way — Windows fixes buttons at 32px, GNOME and Breeze add fixed padding to the font height. Result: our buttons are −23% vs native on Windows, −18% on GNOME. The main "looks small" driver.
  2. Radii. Only radius.control adapts per platform; sm/overlay are frozen at 4/10. Native overlay radii span 5px (KDE) to 15px (GNOME) — we are 5px off in both directions.
  3. Borders. We paint border_1() on nearly everything (shadcn-style). That matches Windows and KDE and contradicts macOS and GNOME/libadwaita, whose controls are borderless fills at normal contrast. The "feels weird" driver.

Structural gap underneath the spacing complaint: the theme has no spacing tokens — components hardcode gpui utilities (p_1, px_2, gap_1p5, min_w_56), so platform rhythm (GNOME's 6px grid, KDE's 4/6/8) cannot be expressed.

What the platforms specify

Apple / macOS

The HIG publishes no pixel metrics for macOS — verified across the buttons, text-fields, toolbars, toggles, menus, windows, layout, sidebars, sheets, materials and designing-for-macos pages. What is published: the type ramp (body 13pt, min 10pt) and the 44×44pt hit-region rule. Everything else below was measured locally (AppKit, regular control size, this macOS build):

Control Measured Confidence
Push button 24pt (small 20, mini 16) measured
Rounded text field 24pt measured
Pop-up button / segmented 24pt / 24pt measured
Switch 54×24 measured
Checkbox (empty control) 16×16 measured
Slider control height 16pt measured
Window titlebar 32pt on this build; 28pt on older macOS measured, version-dependent
Body text 13pt published

Border language: toolbar items and image buttons are explicitly borderless ("toolbar items don't include a bezel"); hierarchy is expressed by fill, not borders; fields keep a hairline bezel; menus/popovers separate by shadow and material, not borders. Concentricity rule: "buttons, text fields, headers, and footers have corner radii that are concentric with bar corners" (rule only, no values).

GNOME / libadwaita

Exact values from the stylesheet; GTK px are logical pixels at 96dpi.

Metric Value
Button total height min-height 24 + padding 5px 10px ⇒ ≈34px
Entry 34px (border-box, no vertical padding)
Header bar 47px
Menu item min-height 32px, padding 0 12px; container padding 6px
Switch knob 20px, trough ≈46×26
Checkbox 14px + 3px pad each side ⇒ 20px, radius 6px
Radius ladder button/entry 9px ($button_radius), checkbox 6px, cards 12px, menu/popover/dialog/window 15px, menu items 9px
Font Adwaita Sans 11pt = 14.67px
Focus 2px accent outline (accent @50% default, 80% high contrast)

Borders: controls are borderless at normal contrast — raised buttons, entries, switches, menus draw shape from translucent currentColor-mix fills. 1px borders are a high-contrast affordance only. Always-on hairlines are reserved for the window outline, headerbar bottom shade, frames, and boxed-list row separators.

Windows 11 / WinUI 3 (Fluent 2)

Published tokens from the theme-resource XAML and Learn pages, at 100% scaling:

Metric Value
Button 32px (padding 11,5,11,6 + 1px border + Body 14/20); ButtonHeight=32 lives in OS generic.xaml
TextBox 32px (TextControlThemeMinHeight, OS generic.xaml — not fetched)
CheckBox box 20×20, 1px stroke (row 32)
ToggleSwitch track 40×20 pill; knob 12×12 resting → 14 hover
Radius ControlCornerRadius 4px, OverlayCornerRadius 8px
Title bar 32px standard, 48px "Tall"
Type ramp Caption 12, Body 14, Body Large 18, Subtitle 20, Title 28
Spacing 8 between buttons, 12 control↔label, 16 edge↔text — a 4px grid
Focus system ring drawn outside the control (negative focus margin)

Borders: buttons and textboxes carry a 1px elevation border (gradient stroke, lighter top + darker bottom); the accent button keeps a border too; checkbox box 1px; switch track 1px while off; slider borderless except the thumb.

KDE Plasma 6 / Breeze

Source constants (breezemetrics.h, units.cpp); the HIG names the usage rules, not px.

Metric Value
Kirigami units gridUnit 18, smallSpacing 4, mediumSpacing 6, largeSpacing 8, cornerRadius 5
Button fontHeight + 2×(6+2) ⇒ ≈33px at the default 10pt font
Line edit fontHeight + 12 ⇒ ≈29px
Checkbox 20×20, radius 4
Switch (qqc2) 36×18, knob 18
Radius 5px everywhere — buttons, line edits, menus, popups, windows
Font Noto Sans 10pt = 13.33px
Titlebar (KWin) max(fontHeight, 2×gridUnit = 36) + margins ⇒ ≈44px
Focus rounded ring filled highlight @30%; item views get a focus underline

Borders: 1px outline (PenWidth::Frame) on all controls; flat buttons are borderless until hover/press/check; menus and popups framed 1px.

What our tokens resolve to today

SizeTable::with_root_font_size medium class (height 1.75rem, padding 1rem) fed by each adapter's inputs in crates/theme/src/source/:

macOS (root 13) Windows (root 14) GNOME (fallback 16) KDE (fallback 16)
control_height 22.75 24.5 28 28
input_height 24 (stated) 32 (stated) 30 30
padding_x 13 14 16 16
text / icon / indicator 13 / 13 / 15.9 14 / 14 / 17.2 16 / 16 / 19.6 16 / 16 / 19.6
radius sm / control / overlay 4 / 6 (stated) / 10 4 / 4 (stated) / 10 4 / 6 / 10 4 / 6 / 10
titlebar max(22.75, 34 floor) = 34 32 (stated) 34 34
switch (1.65×h × h, knob 1.225rem) 37.5×22.75 40.4×24.5 46.2×28 46.2×28

Gap analysis

Heights — the "looks small" bug

Button height Native Ours Delta
Windows 32 24.5 −7.5px (−23%)
GNOME ≈34 28 −6px (−18%)
KDE ≈33 28 −5px
macOS 24 22.75 −1.25pt

Root cause: native heights are not k × font. Windows fixes 32px; GNOME is font + 10px padding; Breeze is fontHeight + 16px. One rem factor cannot hit any of them — and the trap is that reading the real Linux fonts (14.67 / 13.33px) without changing the model makes our buttons smaller (24.4 / 23.3). The codebase already has the right seam: SystemTheme.input_height, an optional platform-stated value with ratio scaling for the other classes. control_height needs the same treatment. Our stated input_height values (macOS 24, Windows 32) are already exactly right — the derived button height next to them is what is off.

Text compounds it on Linux: the GNOME/KDE adapters never state root_font_size, so the 16px fallback makes labels oversized (16 vs 14.67 / 13.33) while controls read undersized.

Radii

Radius macOS Windows GNOME KDE
control 6 ~ bezel ≈6 4 ✓ 6 vs 9 6 vs 5 ~
overlay (menu/popover/dialog) 10 ~ (unpublished, ≈10–12) 10 vs 8 10 vs 15 10 vs 5
sm (checkbox) 4 ~ 4 ~ 4 vs 6 4 ✓

Only control is platform-overridable (resolve); Radius::default freezes sm/overlay/full. GNOME's whole ladder sits 3–5px above ours; KDE's overlays sit 5px below.

Borders — the "feels weird" bug

Our current policy: border_1() on secondary button, input, select, checkbox, radio, menu, popover, dialog, combobox popup, command palette, toast, tooltip, alert, even slider/switch knobs. Primary button is borderless.

Element macOS GNOME Windows KDE Ours
secondary button fill, no border fill, no border 1px elevation border 1px border 1px — ✗mac/GNOME
primary button fill, no border fill, no border accent fill + border accent fill + border no border — ✗Win/KDE
input hairline bezel borderless, 2px accent outline on focus 1px border → accent on focus 1px border → focus color 1px always
menu / popover no border (shadow/material) no border (shadow + 1px black@5% ring) framed flyout 1px frame 1px
separator roles hairline ✓ hairline ✓ ✓ ✓ ✓

Half-right on Windows/KDE, systematically wrong on macOS/GNOME — and even on Windows/KDE our primary button drops a border the natives keep. Our high-contrast mode sharpening borders toward the foreground is exactly GNOME's model (borders as a high-contrast affordance); it just never switches off at normal contrast.

Spacing — structural, not numeric

No spacing tokens exist in Theme; components hardcode a fixed 4px grid: menu p_1 + items px_2 py_1, popover mt_1 min_w_32, menu min_w_56, toast/tooltip px_3/px_2 py_1, button gap_1p5, hardcoded max_w(480)/max_w(360). Native rhythms: GNOME = 6px base (menu padding 12px, item min-height 32px — our GNOME menu rows ≈24px), KDE = 4/6/8, Windows and macOS ≈ 4px grid (we happen to match those two). Menu/popover density is frozen at a macOS-ish tuning.

Per-component calibration

  • Switch: macOS native 54×24 (ours 37.5 wide — too narrow); Windows 40×20 with a 12px knob (ours 24.5 tall, 17px knob — too big); KDE 36×18 (ours 46×28); only GNOME ≈ matches, by accident.
  • Checkbox: Windows/GNOME/KDE ≈20 (ours 17.2–19.6); macOS 16 ✓.
  • Icons: macOS ≈16 (ours 13), Windows 16 (ours 14); GNOME/KDE 16 ✓.
  • Titlebar: measured 32 on this macOS build vs our 34 floor (fine-ish; the floor silently drifted past older macOS's 28); GNOME headerbar 47 vs 34 — a visible −13px; KDE ≈44 vs 34; Windows 32 stated ✓.

What is already right

  • The input_height stated-value seam — the pattern to generalize.
  • Windows ControlCornerRadius 4; stated titlebar 32.
  • Body text on macOS (13) and Windows (14); scrollbar autohide mapping; per-platform elevation stacks; high-contrast border sharpening mirrors GNOME's model.
  • GNOME accent via portal/gsettings, KDE DecorationFocus/DecorationHover role mapping.
  1. State control_height per platform — add control_height: Option<Pixels> beside input_height on SystemTheme, reuse the ratio-scaling pattern: macOS 24 (measured), Windows 32, GNOME ≈34, KDE ≈33. Fixes the smallness everywhere in one change.
  2. State root_font_size from Linux font settings — GNOME font-name (11pt ⇒ 14.67px), KDE kdeglobals [General] font= (10pt ⇒ 13.33px); the parsing seams already exist for colors.
  3. Make the radius ladder platform-overridable — stated overlay/sm radius on SystemTheme: GNOME 15/6, Windows 8/4, KDE 5/4, macOS ≈10/4; keep preset > system precedence.
  4. Per-source border and focus policy — a theme-level token consumed by components: secondary button and inputs drop borders on macOS/GNOME (fill tints + 2px accent focus outline), keep 1px on Windows/KDE; primary gains a border on Windows/KDE; menus/popovers shadow-only on macOS/GNOME.
  5. State titlebar_height per platform — GNOME 47, KDE ≈44; macOS queried rather than a drifted floor (32 on current builds, 28 older); Windows stays stated.
  6. Add a minimal spacing scale to Theme driven by a per-platform base unit (GNOME 6, KDE 4/6/8, others 4); route menu/popover paddings, menu item min-height (GNOME 32), and the hardcoded min_w_/max_w values through it.
  7. Calibrate switches and icons per platform — macOS 54×24, Windows 40×20 with 12px knob, KDE 36×18; icon 16 on macOS/Windows.

Source confidence notes

Everything in the GNOME/Windows/KDE tables is quoted from the cited stylesheets, sources, or Learn pages (published). KDE button/line-edit/title heights are formulas from source constants (derived). Windows TextBox height token and the focus-stroke pixel spec live in the OS generic.xaml and were not retrievable — low confidence, commonly cited as 32. Apple publishes no macOS control metrics; all macOS control numbers here are AppKit measurements from 2026-11 on this machine and may shift across macOS releases (titlebar already differs from older builds).