Skip to content

.sf-btn: fix color/style naming collision on "secondary", complete the color axis, and ship gradient fill/outline variants #571

Description

@jackgranatowski

Problem

.sf-btn currently overloads the word "secondary" for two unrelated things:

  1. A style/emphasis treatment.sf-btn--secondary (soft tonal wash), part of the emphasis ladder fill → --secondary → --ghost → --outline (optional/components.css).
  2. 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 themgrep -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

  • optional/components.css
    • Rename style rule .sf-btn--secondary.sf-btn--soft (keep the existing color-mix() tonal-wash implementation, incl. @supports fallback)
    • Remove .sf-btn--ghost entirely
    • Add color-family variants .sf-btn--secondary, .sf-btn--tertiary, .sf-btn--base, .sf-btn--action, wiring --sf-btn-color / --sf-btn-color--hover / --sf-btn-on-color to the matching --sf-color-* tokens (same pattern as --primary/--neutral/etc.)
    • Add .sf-btn--gradient fill rule(s) consuming var(--sf-gradient-{family}) for all 4 core brand colors: primary/secondary/tertiary/action (mandatory, equal footing — no partial coverage); decide base/neutral/status-family coverage per the open decision above
    • Promote the gradient-border recipe from docs/components.md into a real .sf-btn--gradient.sf-btn--outline rule, driven by var(--sf-gradient-{family}) instead of ad-hoc --sf-grad-a/--sf-grad-b, same 4-family mandatory coverage
  • core/tokens.css — add missing --sf-gradient-action token (mandatory, core-4 parity with primary/secondary/tertiary — same formula pattern as the existing three); resolve the separate open decision on base/neutral/status-family gradient tokens (scope-limit vs. extend) and document the choice inline
  • docs/components.md — update the .sf-btn Modifiers table: Colour group → all 10 families; Style group → fill (implicit) / --soft / --outline only; add Gradient group (--gradient, composability rules, confirm all 4 core brand colors supported including action); remove/replace the old manual gradient-border recipe once it's a real shipped modifier
  • docs/llm-guide.md §9.10 — update .sf-btn families/treatments summary to match, add gradient axis (note action parity 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-action token and any others if option (b) is chosen
  • token-registry.json — register --sf-gradient-action (and any others if option (b) is chosen); otherwise no further action expected
  • configurator/src/components/panels/ComponentsPanel.svelte
    • BTN_VARIANTS → extend to all 10 color families
    • BTN_STYLESfill / soft / outline (remove ghost)
    • Add a Gradient toggle, enabled for at least primary/secondary/tertiary/action (never fewer than these 4)
  • Run SLASHED-for-WP's npm run sync (in admin-app/) to pull the updated ComponentsPanel.svelte into the vendored plugin tree — do not hand-edit the vendored copy
  • CHANGELOG.md / CHANGELOG_FULL.md — call out as a breaking change: .sf-btn--secondary now selects the secondary brand color (previously the soft-tonal style, now .sf-btn--soft); .sf-btn--ghost removed. Call out .sf-btn--gradient (fill + outline, full core-4 parity including action) as new, additive.
  • npm run check:llm-guide passes
  • Tests under tests/ updated for renamed/removed/added modifiers (light + dark), including gradient fill + gradient outline for all 4 core brand colors
  • Version bump per repo's version-sync process (npm versionnpm run version-syncnpm run check:versionnpm run build)

Acceptance criteria

  • Every one of the 10 global color families is selectable as a .sf-btn--{color} modifier
  • No class name or documented modifier reuses a word that is also a color-family name for an unrelated purpose
  • .sf-btn--ghost no longer exists in CSS, docs, or configurator
  • Configurator button panel exposes all three axes as independent controls (color swatches + style picker + gradient toggle), matching the CSS 1:1
  • docs/components.md and docs/llm-guide.md reflect the new axis split, including the gradient axis
  • Breaking change documented in changelog with a clear "before → after" mapping for .sf-btn--secondary
  • .sf-btn--gradient works 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 variables
  • action has full gradient parity with primary/secondary/tertiary — fill and outline both — since it is one of the 4 core brand colors, not a second-tier addition
  • The manual gradient-border recipe in docs/components.md is removed or clearly marked superseded once the real modifier ships

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions