Problem
.sf-btn currently overloads the word "secondary" for two unrelated things:
- A style/emphasis treatment —
.sf-btn--secondary (soft tonal wash), part of the emphasis ladder fill → --secondary → --ghost → --outline (optional/components.css).
- A color token —
--sf-color-secondary is a fully-built semantic brand color (source-light/dark, hover/active, 50–950 ramp, alpha ramp, gradient, on-color text) in core/tokens.css, one of the 4 brand colors (primary/secondary/tertiary/action). No component currently consumes it.
Same problem exists for --sf-color-tertiary (also fully built, also unconsumed) and, less obviously, --sf-color-base.
Today .sf-btn's color axis only covers --primary --neutral --success --warning --info --danger — it does not cover the full palette (secondary, tertiary, action, base are all missing as selectable button colors), while its style axis reuses secondary as a treatment name. A user trying to set a button's color to "secondary" finds no such color option — only a same-named style option that does something else entirely. This is confusing and was flagged directly by a user trying to change a button's color.
Docs (docs/components.md, docs/llm-guide.md) and the configurator (ComponentsPanel.svelte, mirrored 1:1 in SLASHED-for-WP's vendored copy) already label the two axes distinctly as "Colour" vs "Style", so the underlying design isn't broken — but the shared vocabulary is a real footgun for anyone scanning class names or writing custom CSS.
Proposed final design
Three fully independent axes, freely composable, zero shared vocabulary.
Axis 1 — Color (.sf-btn--{color})
1:1 with the global palette, all 10 families:
--primary --secondary --tertiary --action --base
--neutral --success --info --warning --danger
No modifier = --action (unchanged default). --secondary, --tertiary, --base, and an explicit --action are new — they wire the already-existing --sf-color-secondary / --sf-color-tertiary / --sf-color-base / --sf-color-action tokens into --sf-btn-color, same pattern as the existing color variants.
Axis 2 — Style (emphasis, .sf-btn--{style})
Three rungs, no ghost:
(no modifier) = fill solid background
--soft tonal wash (renamed from today's --secondary)
--outline bordered, fills on hover
--ghost is removed entirely (not deprecated/aliased — dropped).
The key fix is the rename .sf-btn--secondary (style) → .sf-btn--soft: this is what actually removes the collision, since "secondary" no longer exists in the style vocabulary at all. Both axes compose freely, e.g. .sf-btn--secondary.sf-btn--outline (secondary-colored, outline style) is now unambiguous.
Axis 3 — Gradient (optional overlay, .sf-btn--gradient)
Today gradient buttons are unsupported as a real modifier. Gradient tokens already exist (core/tokens.css:607-616: --sf-gradient-primary, --sf-gradient-secondary, --sf-gradient-tertiary, --sf-gradient-brand, plus non-brand --sf-gradient-surface / --sf-gradient-fade--{r,l,t,b}) and are documented in docs/tokens.md:382-390, but no component consumes them — grep -i gradient optional/components.css returns zero matches. The only "gradient button" guidance that exists is a manual copy-paste recipe in docs/components.md:183-224 for a gradient border only (.btn-gradient-outline, via a masked ::before pseudo-element using ad-hoc --sf-grad-a/--sf-grad-b vars, not tied to the family gradient tokens), and it says outright: "A gradient border isn't a shipped .sf-btn variant." There is no gradient fill option at all, shipped or as a recipe.
This axis should ship both, as a real modifier composing with axis 1 (only for families that have a defined --sf-gradient-* token) and axis 2:
- Gradient fill —
.sf-btn--primary.sf-btn--gradient swaps background: var(--sf-btn-color) for background: var(--sf-gradient-primary) (same for --secondary/--tertiary/--action). action is one of the 4 core brand colors (primary/secondary/tertiary/action) and must get exactly the same gradient support as the other three — not optional, not deferred. core/tokens.css currently has no --sf-gradient-action token; it must be added as part of this issue. (--sf-gradient-brand already exists as a separate decorative option, independent of the color axis — keep it as-is, don't conflate with --action.)
- Gradient outline —
.sf-btn--primary.sf-btn--gradient.sf-btn--outline promotes today's docs-only recipe into a real shipped rule: masked ::before (or border-image) painted with var(--sf-gradient-primary) instead of the ad-hoc --sf-grad-a/--sf-grad-b pair, so it's driven by the same family token as the fill variant instead of two free-standing custom properties the user has to set by hand. Same mandatory coverage: primary/secondary/tertiary/action.
Open decision to resolve during implementation (base / neutral / status families only): gradient tokens for base, neutral, and the 4 status families (success/warning/info/danger) don't exist yet. Either (a) scope .sf-btn--gradient to only compose with the 4 core brand colors (primary/secondary/tertiary/action) for v1 — no-op outside those, and that limitation must be explicit in docs, not a silent no-op — or (b) extend core/tokens.css with the missing --sf-gradient-{base,neutral,success,warning,info,danger} tokens so gradient composes with the full 10-family palette symmetrically. Recommend (a) for v1 scope with (b) as a documented follow-up if requested. This decision does not affect action, which is mandatory core-4 scope regardless of which option is chosen.
Breaking change
.sf-btn--secondary changes meaning (style → color) — this cannot be safely aliased, since the old and new meanings are different concepts with different visual results. Must ship as a documented breaking rename, not a silent deprecation. .sf-btn--ghost is a straight removal. The gradient axis (3) is purely additive — no breaking change there.
Implementation checklist
Acceptance criteria
Problem
.sf-btncurrently overloads the word "secondary" for two unrelated things:.sf-btn--secondary(soft tonal wash), part of the emphasis ladderfill → --secondary → --ghost → --outline(optional/components.css).--sf-color-secondaryis a fully-built semantic brand color (source-light/dark, hover/active, 50–950 ramp, alpha ramp, gradient, on-color text) incore/tokens.css, one of the 4 brand colors (primary/secondary/tertiary/action). No component currently consumes it.Same problem exists for
--sf-color-tertiary(also fully built, also unconsumed) and, less obviously,--sf-color-base.Today
.sf-btn's color axis only covers--primary --neutral --success --warning --info --danger— it does not cover the full palette (secondary,tertiary,action,baseare all missing as selectable button colors), while its style axis reusessecondaryas a treatment name. A user trying to set a button's color to "secondary" finds no such color option — only a same-named style option that does something else entirely. This is confusing and was flagged directly by a user trying to change a button's color.Docs (
docs/components.md,docs/llm-guide.md) and the configurator (ComponentsPanel.svelte, mirrored 1:1 inSLASHED-for-WP's vendored copy) already label the two axes distinctly as "Colour" vs "Style", so the underlying design isn't broken — but the shared vocabulary is a real footgun for anyone scanning class names or writing custom CSS.Proposed final design
Three fully independent axes, freely composable, zero shared vocabulary.
Axis 1 — Color (
.sf-btn--{color})1:1 with the global palette, all 10 families:
No modifier =
--action(unchanged default).--secondary,--tertiary,--base, and an explicit--actionare new — they wire the already-existing--sf-color-secondary/--sf-color-tertiary/--sf-color-base/--sf-color-actiontokens into--sf-btn-color, same pattern as the existing color variants.Axis 2 — Style (emphasis,
.sf-btn--{style})Three rungs, no ghost:
--ghostis removed entirely (not deprecated/aliased — dropped).The key fix is the rename
.sf-btn--secondary(style) →.sf-btn--soft: this is what actually removes the collision, since "secondary" no longer exists in the style vocabulary at all. Both axes compose freely, e.g..sf-btn--secondary.sf-btn--outline(secondary-colored, outline style) is now unambiguous.Axis 3 — Gradient (optional overlay,
.sf-btn--gradient)Today gradient buttons are unsupported as a real modifier. Gradient tokens already exist (
core/tokens.css:607-616:--sf-gradient-primary,--sf-gradient-secondary,--sf-gradient-tertiary,--sf-gradient-brand, plus non-brand--sf-gradient-surface/--sf-gradient-fade--{r,l,t,b}) and are documented indocs/tokens.md:382-390, but no component consumes them —grep -i gradient optional/components.cssreturns zero matches. The only "gradient button" guidance that exists is a manual copy-paste recipe indocs/components.md:183-224for a gradient border only (.btn-gradient-outline, via a masked::beforepseudo-element using ad-hoc--sf-grad-a/--sf-grad-bvars, not tied to the family gradient tokens), and it says outright: "A gradient border isn't a shipped.sf-btnvariant." There is no gradient fill option at all, shipped or as a recipe.This axis should ship both, as a real modifier composing with axis 1 (only for families that have a defined
--sf-gradient-*token) and axis 2:.sf-btn--primary.sf-btn--gradientswapsbackground: var(--sf-btn-color)forbackground: var(--sf-gradient-primary)(same for--secondary/--tertiary/--action).actionis one of the 4 core brand colors (primary/secondary/tertiary/action) and must get exactly the same gradient support as the other three — not optional, not deferred.core/tokens.csscurrently has no--sf-gradient-actiontoken; it must be added as part of this issue. (--sf-gradient-brandalready exists as a separate decorative option, independent of the color axis — keep it as-is, don't conflate with--action.).sf-btn--primary.sf-btn--gradient.sf-btn--outlinepromotes today's docs-only recipe into a real shipped rule: masked::before(orborder-image) painted withvar(--sf-gradient-primary)instead of the ad-hoc--sf-grad-a/--sf-grad-bpair, so it's driven by the same family token as the fill variant instead of two free-standing custom properties the user has to set by hand. Same mandatory coverage:primary/secondary/tertiary/action.Open decision to resolve during implementation (base / neutral / status families only): gradient tokens for
base,neutral, and the 4 status families (success/warning/info/danger) don't exist yet. Either (a) scope.sf-btn--gradientto only compose with the 4 core brand colors (primary/secondary/tertiary/action) for v1 — no-op outside those, and that limitation must be explicit in docs, not a silent no-op — or (b) extendcore/tokens.csswith the missing--sf-gradient-{base,neutral,success,warning,info,danger}tokens so gradient composes with the full 10-family palette symmetrically. Recommend (a) for v1 scope with (b) as a documented follow-up if requested. This decision does not affectaction, which is mandatory core-4 scope regardless of which option is chosen.Breaking change
.sf-btn--secondarychanges meaning (style → color) — this cannot be safely aliased, since the old and new meanings are different concepts with different visual results. Must ship as a documented breaking rename, not a silent deprecation..sf-btn--ghostis a straight removal. The gradient axis (3) is purely additive — no breaking change there.Implementation checklist
optional/components.css.sf-btn--secondary→.sf-btn--soft(keep the existingcolor-mix()tonal-wash implementation, incl.@supportsfallback).sf-btn--ghostentirely.sf-btn--secondary,.sf-btn--tertiary,.sf-btn--base,.sf-btn--action, wiring--sf-btn-color/--sf-btn-color--hover/--sf-btn-on-colorto the matching--sf-color-*tokens (same pattern as--primary/--neutral/etc.).sf-btn--gradientfill rule(s) consumingvar(--sf-gradient-{family})for all 4 core brand colors:primary/secondary/tertiary/action(mandatory, equal footing — no partial coverage); decidebase/neutral/status-family coverage per the open decision abovedocs/components.mdinto a real.sf-btn--gradient.sf-btn--outlinerule, driven byvar(--sf-gradient-{family})instead of ad-hoc--sf-grad-a/--sf-grad-b, same 4-family mandatory coveragecore/tokens.css— add missing--sf-gradient-actiontoken (mandatory, core-4 parity with primary/secondary/tertiary — same formula pattern as the existing three); resolve the separate open decision onbase/neutral/status-family gradient tokens (scope-limit vs. extend) and document the choice inlinedocs/components.md— update the.sf-btnModifiers table: Colour group → all 10 families; Style group →fill(implicit) /--soft/--outlineonly; add Gradient group (--gradient, composability rules, confirm all 4 core brand colors supported includingaction); remove/replace the old manual gradient-border recipe once it's a real shipped modifierdocs/llm-guide.md§9.10 — update.sf-btnfamilies/treatments summary to match, add gradient axis (noteactionparity explicitly)docs/tokens.md— no new public tokens for axis 1/2 (rule-local--sf-btn-color*stay unregistered, consistent with current pattern); document the new--sf-gradient-actiontoken and any others if option (b) is chosentoken-registry.json— register--sf-gradient-action(and any others if option (b) is chosen); otherwise no further action expectedconfigurator/src/components/panels/ComponentsPanel.svelteBTN_VARIANTS→ extend to all 10 color familiesBTN_STYLES→fill / soft / outline(removeghost)primary/secondary/tertiary/action(never fewer than these 4)SLASHED-for-WP'snpm run sync(inadmin-app/) to pull the updatedComponentsPanel.svelteinto the vendored plugin tree — do not hand-edit the vendored copyCHANGELOG.md/CHANGELOG_FULL.md— call out as a breaking change:.sf-btn--secondarynow selects the secondary brand color (previously the soft-tonal style, now.sf-btn--soft);.sf-btn--ghostremoved. Call out.sf-btn--gradient(fill + outline, full core-4 parity includingaction) as new, additive.npm run check:llm-guidepassestests/updated for renamed/removed/added modifiers (light + dark), including gradient fill + gradient outline for all 4 core brand colorsnpm version→npm run version-sync→npm run check:version→npm run build)Acceptance criteria
.sf-btn--{color}modifier.sf-btn--ghostno longer exists in CSS, docs, or configuratordocs/components.mdanddocs/llm-guide.mdreflect the new axis split, including the gradient axis.sf-btn--secondary.sf-btn--gradientworks for both fill (solid background → gradient background) and outline (solid border → gradient border) without requiring the user to hand-set any--sf-grad-a/--sf-grad-b-style ad-hoc variablesactionhas full gradient parity withprimary/secondary/tertiary— fill and outline both — since it is one of the 4 core brand colors, not a second-tier additiondocs/components.mdis removed or clearly marked superseded once the real modifier ships