You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Research-backed axis model for buttons in the builder, from an Aug 2026 survey of 16 design systems (CSS verified from source, not docs). Prototyped in panel-lab v2 (see linked PR); this issue tracks the product decisions and the eventual registry implementation.
The problem
The old panel-lab Buttons section's "Style" row was the variant enum (primary/secondary/quiet…) relabeled. Variants are semantic roles shipped in the end user's API — not a design-system aesthetic. Input already models this correctly (params.style in input/meta.ts); Button has no style param at all.
Findings
No builder separates style from variant for buttons. The industry crams aesthetics into variant enums (Radix classic, Mantine gradient, M3 elevated, HeroUI v2 shadow) or hides them in whole-theme presets. Radix's classic shows why that fails: it exists only for the solid look — no classic-outline/soft/ghost — so a whole-app 3D switch is impossible. A per-component style axis is a differentiator.
Styles reshape only fill-bearing variants; quiet/link are invariant. Radix, Untitled UI, Primer, and Geist all independently converged on this.
Hover: 0/16 systems use "none", 0/16 use lift (Stripe removed its famous lift). 14/16 dim (token swap, alpha, or Material state layer); Linear and Ant lighten.
Press is where systems diverge: darker -active token (8/16), nothing (5/16), scale .97 (Linear, HeroUI, Spectrum 2 pressScale), translate-y 1px (shadcn v4 styles). The state is named active (10) or pressed (5, incl. React Aria). Press stays uniform across variants (Linear precedent); hover mechanics vary per variant everywhere (ghost gains a bg fill on hover in every system).
Button groups: three axes cover all 14 systems surveyed — groupLayout (attached / gapped / container-track), separator (border / divider / none), and selectedStyle for the toggle flavor (fill / chip / inverse / M3 check-fill / M3 pill-morph). Today's ToggleButtonGroup hardcodes exactly one combination (attached, square inner corners). Most systems split action groups from segmented controls — maps to the Button ⇄ ToggleButton synced group; container belongs to the toggle flavor.
Proposed axes
style: flat (default; Geist/Chakra) · outline (Primer hairline border + resting shadow) · raised (3D: Radix classic / Untitled UI — gradient + inset ring + rim light + bottom shade) · elevated (Stripe/M3 soft layered shadow). Hover/press mechanics resolve per style family; styles need per-state slots and a dark-mode token swap (raised).
M3 pill-morph and ripple are deliberately out of scope (single-system outliers).
Full survey tables with per-system sources (Radix base-button.css, Primer ButtonBase.module.css, Linear's shipped CSS, shadcn style-nova.css, HeroUI theme, Spectrum pressScale.ts, Carbon/Cloudscape/Ant/Chakra/Mantine/Polaris/Atlassian/M3/Geist/Stripe) live in the research session; ask if needed.
Research-backed axis model for buttons in the builder, from an Aug 2026 survey of 16 design systems (CSS verified from source, not docs). Prototyped in panel-lab v2 (see linked PR); this issue tracks the product decisions and the eventual registry implementation.
The problem
The old panel-lab Buttons section's "Style" row was the
variantenum (primary/secondary/quiet…) relabeled. Variants are semantic roles shipped in the end user's API — not a design-system aesthetic. Input already models this correctly (params.styleininput/meta.ts); Button has no style param at all.Findings
classic, Mantinegradient, M3elevated, HeroUI v2shadow) or hides them in whole-theme presets. Radix'sclassicshows why that fails: it exists only for the solid look — no classic-outline/soft/ghost — so a whole-app 3D switch is impossible. A per-component style axis is a differentiator.-activetoken (8/16), nothing (5/16), scale .97 (Linear, HeroUI, Spectrum 2pressScale), translate-y 1px (shadcn v4 styles). The state is namedactive(10) orpressed(5, incl. React Aria). Press stays uniform across variants (Linear precedent); hover mechanics vary per variant everywhere (ghost gains a bg fill on hover in every system).groupLayout(attached / gapped / container-track),separator(border / divider / none), andselectedStylefor the toggle flavor (fill / chip / inverse / M3 check-fill / M3 pill-morph). Today'sToggleButtonGrouphardcodes exactly one combination (attached, square inner corners). Most systems split action groups from segmented controls — maps to the Button ⇄ ToggleButton synced group;containerbelongs to the toggle flavor.Proposed axes
style: flat (default; Geist/Chakra) · outline (Primer hairline border + resting shadow) · raised (3D: Radix classic / Untitled UI — gradient + inset ring + rim light + bottom shade) · elevated (Stripe/M3 soft layered shadow). Hover/press mechanics resolve per style family; styles need per-state slots and a dark-mode token swap (raised).hover: dim (default) · lighten (Linear) · nonepress: dim (default) · scale (.97) · push (1px) · nonegroupLayout·separator·selectedStyle(toggle only)Open questions
params.styleonbutton/meta.ts— registry + publisher work, touching the deferred variant-ladder follow-up (feat(buttons): rename default variant to secondary #531).Full survey tables with per-system sources (Radix base-button.css, Primer ButtonBase.module.css, Linear's shipped CSS, shadcn style-nova.css, HeroUI theme, Spectrum pressScale.ts, Carbon/Cloudscape/Ant/Chakra/Mantine/Polaris/Atlassian/M3/Geist/Stripe) live in the research session; ask if needed.