From 96784417cb07292a42a3acdd0ba7bc899f2c67ce Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Thu, 15 Oct 2026 10:40:42 -0400 Subject: [PATCH 1/3] fix(button): use solid backgrounds outside selected states --- .changeset/solid-button-backgrounds.md | 5 + .../__snapshots__/stacks.less.test.ts.snap | 7 +- .../button/button-background.a11y.test.ts | 220 ++++++++++++++++++ .../lib/components/button/button.less | 18 +- .../s-btn-highcontrast-dark-tonal-badge.ico | 4 +- ...highcontrast-dark-tonal-dropdown-badge.ico | 4 +- ...t-dark-tonal-dropdown-is-loading-badge.ico | 4 +- ...n-highcontrast-dark-tonal-filled-badge.ico | 4 +- ...trast-dark-tonal-filled-dropdown-badge.ico | 4 +- ...tonal-filled-dropdown-is-loading-badge.ico | 4 +- ...hcontrast-dark-tonal-filled-icon-badge.ico | 4 +- ...ark-tonal-filled-icon-is-loading-badge.ico | 4 +- ...ast-dark-tonal-filled-is-loading-badge.ico | 4 +- ...ighcontrast-dark-tonal-filled-md-badge.ico | 4 +- ...-dark-tonal-filled-md-is-loading-badge.ico | 4 +- ...btn-highcontrast-dark-tonal-icon-badge.ico | 4 +- ...trast-dark-tonal-icon-is-loading-badge.ico | 4 +- ...ghcontrast-dark-tonal-is-loading-badge.ico | 4 +- ...s-btn-highcontrast-dark-tonal-md-badge.ico | 4 +- ...ontrast-dark-tonal-md-is-loading-badge.ico | 4 +- 20 files changed, 270 insertions(+), 44 deletions(-) create mode 100644 .changeset/solid-button-backgrounds.md create mode 100644 packages/stacks-classic/lib/components/button/button-background.a11y.test.ts diff --git a/.changeset/solid-button-backgrounds.md b/.changeset/solid-button-backgrounds.md new file mode 100644 index 0000000000..ca43e1e5e2 --- /dev/null +++ b/.changeset/solid-button-backgrounds.md @@ -0,0 +1,5 @@ +--- +"@stackoverflow/stacks": patch +--- + +Use solid backgrounds for non-selected buttons so axe can measure their color contrast. Preserve the two-tone gradients for selected buttons and checked radio labels; automatic contrast measurement of those gradients remains unsupported. diff --git a/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap b/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap index d8d51ca299..cb543da2c7 100644 --- a/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap +++ b/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap @@ -12250,7 +12250,7 @@ body.theme-highcontrast.theme-dark .s-bling.s-bling__filled.s-bling__bronze { --_bu-badge-px: var(--su6); --_bu-badge-py: var(--su2); --_bu-dropdown-bw: var(--su4); - background: linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg)) 50%, var(--_bu-bg) 50%) padding-box, linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg)) 50%, var(--_bu-bg) 50%) border-box; + background: var(--_bu-bg); border: var(--_bu-baw) solid var(--_bu-bc, transparent); color: var(--_bu-fc); font-size: var(--_bu-fs); @@ -12697,9 +12697,12 @@ body.theme-system .theme-dark__forced .s-btn--radio:checked + .s-btn { outline: var(--su2) solid transparent !important; } .s-btn:not(.is-selected):hover { - background: linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50%) padding-box, linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50%) border-box; + background: var(--_bu-bg-hover, var(--_bu-bg)); color: var(--_bu-fc-hover, var(--_bu-fc)); } +.s-btn--radio:checked + .s-btn:where(:not(.is-selected):hover) { + background: linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50%) padding-box, linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50%) border-box; +} fieldset[disabled] .s-btn, .s-btn[disabled], .s-btn[aria-disabled="true"] { diff --git a/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts b/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts new file mode 100644 index 0000000000..c49d497722 --- /dev/null +++ b/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts @@ -0,0 +1,220 @@ +import { expect, fixture, html } from "@open-wc/testing"; +import { sendMouse } from "@web/test-runner-commands"; +import axe from "axe-core"; +import "../../test/a11y-test-utils"; +import "../../index"; + +const apcaRule = "color-contrast-apca-custom"; +const themes = [ + "theme-light", + "theme-dark", + "theme-light theme-highcontrast", + "theme-dark theme-highcontrast", +]; + +const hover = async (el: HTMLElement) => { + const { x, y, width, height } = el.getBoundingClientRect(); + await sendMouse({ + type: "move", + position: [Math.round(x + width / 2), Math.round(y + height / 2)], + }); + expect(el.matches(":hover")).to.equal(true); +}; + +const measureContrast = (el: HTMLElement, rule: string) => + axe.run(el, { + runOnly: { type: "rule", values: [rule] }, + rules: { [rule]: { enabled: true } }, + }); + +describe("button backgrounds", () => { + let originalTheme: string; + + beforeEach(() => { + originalTheme = document.body.className; + }); + + afterEach(async () => { + document.body.className = originalTheme; + await sendMouse({ type: "move", position: [0, 0] }); + }); + + themes.forEach((theme) => { + describe(theme, () => { + beforeEach(() => { + document.body.className = theme; + }); + + [ + "s-btn", + "s-btn s-btn__danger", + "s-btn s-btn__featured", + "s-btn s-btn__tonal", + "s-btn s-btn__clear", + "s-btn s-btn__danger s-btn__clear", + ].forEach((classes) => { + [false, true].forEach((hovered) => { + it(`measures ${classes}${hovered ? " on hover" : ""}`, async () => { + const el = await fixture(html` +
+ +
+ `); + const button = el.querySelector("button")!; + if (hovered) { + await hover(button); + } + + expect( + getComputedStyle(button).backgroundImage + ).to.equal("none"); + const rule = theme.includes("highcontrast") + ? "color-contrast-enhanced" + : apcaRule; + const results = await measureContrast(el, rule); + + // Absence of violations alone also accepts unmeasurable gradients. + expect(results.incomplete).to.be.empty; + expect(results.violations).to.be.empty; + expect(results.passes).to.have.lengthOf(1); + expect(results.passes[0].nodes).to.have.lengthOf(1); + if (rule === apcaRule) { + expect( + results.passes[0].nodes[0].all[0].data + .apcaContrast + ) + .to.be.a("number") + .and.greaterThan(0); + } + }); + }); + }); + + ["button", "radio"].forEach((kind) => { + it(`preserves selected ${kind} gradients, including on hover`, async () => { + const el = await fixture(html` +
+ ${ + kind === "radio" + ? html` + + + ` + : html` + + ` + } +
+ `); + const button = el.querySelector(".s-btn")!; + const background = getComputedStyle(button).backgroundImage; + expect(background).to.contain("linear-gradient"); + await hover(button); + const hoveredStyle = getComputedStyle(button); + if (kind === "radio") { + expect(hoveredStyle.backgroundImage) + .to.contain("linear-gradient") + .and.not.equal(background); + expect(hoveredStyle.color).to.equal("rgb(0, 128, 0)"); + } else { + expect(hoveredStyle.backgroundImage).to.equal( + background + ); + } + + // Selected gradients still require manual contrast verification. + const results = await measureContrast(el, apcaRule); + expect( + results.incomplete.map((result) => result.id) + ).to.include(apcaRule); + }); + }); + + ["s-btn__link", "s-btn__unset"].forEach((modifier) => { + it(`keeps ${modifier} backgrounds transparent`, async () => { + const button = await fixture(html` + + `); + for (const hovered of [false, true]) { + if (hovered) { + await hover(button); + } + const style = getComputedStyle(button); + expect(style.backgroundImage).to.equal("none"); + expect(style.backgroundColor).to.equal( + "rgba(0, 0, 0, 0)" + ); + } + }); + }); + + ["disabled", "aria-disabled", "fieldset"].forEach((kind) => { + it(`preserves ${kind} backgrounds on hover`, async () => { + const el = await fixture(html` +
+ +
+ `); + const button = el.querySelector("button")!; + const background = getComputedStyle(button).backgroundColor; + await hover(button); + expect(getComputedStyle(button).backgroundImage).to.equal( + "none" + ); + expect(getComputedStyle(button).backgroundColor).to.equal( + background + ); + }); + }); + + it("keeps disabled fieldset styles above checked radio hover styles", async () => { + const el = await fixture(html` +
+ + +
+ `); + const button = el.querySelector(".s-btn")!; + await hover(button); + expect(getComputedStyle(button).backgroundImage).to.equal( + "none" + ); + }); + }); + }); +}); diff --git a/packages/stacks-classic/lib/components/button/button.less b/packages/stacks-classic/lib/components/button/button.less index e9d6a4ee62..cd164e8748 100644 --- a/packages/stacks-classic/lib/components/button/button.less +++ b/packages/stacks-classic/lib/components/button/button.less @@ -359,6 +359,12 @@ } &:not(.is-selected):hover { + background: var(--_bu-bg-hover, var(--_bu-bg)); + color: var(--_bu-fc-hover, var(--_bu-fc)); + } + + // Preserve checked radio hover gradients, without increasing specificity over disabled states. + .s-btn--radio:checked + &:where(:not(.is-selected):hover) { background: linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, @@ -368,7 +374,6 @@ var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50% ) border-box; - color: var(--_bu-fc-hover, var(--_bu-fc)); } fieldset[disabled] &, @@ -392,15 +397,8 @@ cursor: auto; } - background: - linear-gradient(0deg, - var(--_bu-bg-selected-overlay, var(--_bu-bg)) 50%, - var(--_bu-bg) 50% - ) padding-box, - linear-gradient(0deg, - var(--_bu-bg-selected-overlay, var(--_bu-bg)) 50%, - var(--_bu-bg) 50% - ) border-box; + // Only selected states need a gradient; solid backgrounds let axe measure contrast. + background: var(--_bu-bg); border: var(--_bu-baw) solid var(--_bu-bc, transparent); color: var(--_bu-fc); font-size: var(--_bu-fs); diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-badge.ico index c1ce62d987..a6f1b24012 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84e2787a5bc7d3dc8e244fdec635751d4a18aefd8345563ea595b14ecd1b1984 -size 4685 +oid sha256:745ccb26438509b59bc417a9ac2a9d20d215ec75a53966421da44bbe15b43775 +size 4757 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-is-loading-badge.ico index c1ce62d987..a6f1b24012 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-dropdown-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84e2787a5bc7d3dc8e244fdec635751d4a18aefd8345563ea595b14ecd1b1984 -size 4685 +oid sha256:745ccb26438509b59bc417a9ac2a9d20d215ec75a53966421da44bbe15b43775 +size 4757 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-badge.ico index c1ce62d987..a6f1b24012 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84e2787a5bc7d3dc8e244fdec635751d4a18aefd8345563ea595b14ecd1b1984 -size 4685 +oid sha256:745ccb26438509b59bc417a9ac2a9d20d215ec75a53966421da44bbe15b43775 +size 4757 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-is-loading-badge.ico index c1ce62d987..a6f1b24012 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-dropdown-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84e2787a5bc7d3dc8e244fdec635751d4a18aefd8345563ea595b14ecd1b1984 -size 4685 +oid sha256:745ccb26438509b59bc417a9ac2a9d20d215ec75a53966421da44bbe15b43775 +size 4757 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-is-loading-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-icon-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-is-loading-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-is-loading-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-filled-md-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-is-loading-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-icon-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-is-loading-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-is-loading-badge.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-is-loading-badge.ico index 59ecf6b001..009da48810 100644 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-is-loading-badge.ico +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-highcontrast-dark-tonal-md-is-loading-badge.ico @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c2e8bd74c65e1d174f024cd4b779a34f52292b9d50cfe870a2647bbd41a526c -size 4607 +oid sha256:00638d2a755c142807d8b4fcf9da03f6d904dc6f5bee6677d97a645e66e53cae +size 4690 From 04502f825cf0a9a89e1b133fb2015104ca1b8612 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Thu, 15 Oct 2026 15:32:36 -0400 Subject: [PATCH 2/3] fix(button): move selected gradients to decorative layers --- .changeset/solid-button-backgrounds.md | 2 +- .../__snapshots__/stacks.less.test.ts.snap | 31 +- .../button/button-background.a11y.test.ts | 57 --- .../button/button-selected.a11y.test.ts | 357 ++++++++++++++++++ .../button/button-selected.visual.test.ts | 148 ++++++++ .../lib/components/button/button.less | 64 +++- .../s-btn-selected-theme-dark-focus.ico | 3 + .../s-btn-selected-theme-dark-hover-radio.ico | 3 + .../s-btn-selected-theme-dark-rest.ico | 3 + ...ed-theme-dark-theme-highcontrast-focus.ico | 3 + ...me-dark-theme-highcontrast-hover-radio.ico | 3 + ...ted-theme-dark-theme-highcontrast-rest.ico | 3 + .../s-btn-selected-theme-light-focus.ico | 3 + ...s-btn-selected-theme-light-hover-radio.ico | 3 + .../s-btn-selected-theme-light-rest.ico | 3 + ...d-theme-light-theme-highcontrast-focus.ico | 3 + ...e-light-theme-highcontrast-hover-radio.ico | 3 + ...ed-theme-light-theme-highcontrast-rest.ico | 3 + .../s-btn-selected-theme-dark-focus.ico | 3 + .../s-btn-selected-theme-dark-hover-radio.ico | 3 + .../s-btn-selected-theme-dark-rest.ico | 3 + ...ed-theme-dark-theme-highcontrast-focus.ico | 3 + ...me-dark-theme-highcontrast-hover-radio.ico | 3 + ...ted-theme-dark-theme-highcontrast-rest.ico | 3 + .../s-btn-selected-theme-light-focus.ico | 3 + ...s-btn-selected-theme-light-hover-radio.ico | 3 + .../s-btn-selected-theme-light-rest.ico | 3 + ...d-theme-light-theme-highcontrast-focus.ico | 3 + ...e-light-theme-highcontrast-hover-radio.ico | 3 + ...ed-theme-light-theme-highcontrast-rest.ico | 3 + .../s-btn-selected-theme-dark-focus.ico | 3 + .../s-btn-selected-theme-dark-hover-radio.ico | 3 + .../s-btn-selected-theme-dark-rest.ico | 3 + ...ed-theme-dark-theme-highcontrast-focus.ico | 3 + ...me-dark-theme-highcontrast-hover-radio.ico | 3 + ...ted-theme-dark-theme-highcontrast-rest.ico | 3 + .../s-btn-selected-theme-light-focus.ico | 3 + ...s-btn-selected-theme-light-hover-radio.ico | 3 + .../s-btn-selected-theme-light-rest.ico | 3 + ...d-theme-light-theme-highcontrast-focus.ico | 3 + ...e-light-theme-highcontrast-hover-radio.ico | 3 + ...ed-theme-light-theme-highcontrast-rest.ico | 3 + 42 files changed, 689 insertions(+), 78 deletions(-) create mode 100644 packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts create mode 100644 packages/stacks-classic/lib/components/button/button-selected.visual.test.ts create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico diff --git a/.changeset/solid-button-backgrounds.md b/.changeset/solid-button-backgrounds.md index ca43e1e5e2..80428db80a 100644 --- a/.changeset/solid-button-backgrounds.md +++ b/.changeset/solid-button-backgrounds.md @@ -2,4 +2,4 @@ "@stackoverflow/stacks": patch --- -Use solid backgrounds for non-selected buttons so axe can measure their color contrast. Preserve the two-tone gradients for selected buttons and checked radio labels; automatic contrast measurement of those gradients remains unsupported. +Use solid backgrounds for buttons so APCA checks can measure their base color contrast. Render the two-tone selected styling on an inner decorative layer for buttons and checked radio labels, with separate regression tests for contrast against both visible colors. Transparent borders now show a thin solid-color rim around that layer. Axe's built-in WCAG contrast rules still report selected pseudo-element backgrounds as incomplete. diff --git a/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap b/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap index cb543da2c7..ab5934be82 100644 --- a/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap +++ b/packages/stacks-classic/lib/__snapshots__/stacks.less.test.ts.snap @@ -12611,8 +12611,9 @@ p .s-btn.s-btn__link { --_bu-bg-selected-overlay: color-mix(in srgb, var(--_bu-bg-selected, var(--_bu-bg)), var(--white) var(--_bu-bg-selected-overlay-o)); --_bu-bg-gradient-top: var(--_bu-bg-selected-overlay, var(--_bu-bg)); --_bu-bg-gradient-bottom: var(--_bu-bg-selected, var(--_bu-bg)); - background: linear-gradient(0deg, var(--_bu-bg-gradient-top) 50%, var(--_bu-bg-gradient-bottom) 50%) padding-box, linear-gradient(0deg, var(--_bu-bg-gradient-top) 50%, var(--_bu-bg-gradient-bottom) 50%) border-box; + background: var(--_bu-bg-selected, var(--_bu-bg)); color: var(--_bu-fc-selected, var(--_bu-fc)); + isolation: isolate; } @media (prefers-color-scheme: dark) { body.theme-system .s-btn.is-selected, @@ -12640,6 +12641,23 @@ body.theme-system .theme-dark__forced .s-btn--radio:checked + .s-btn { .s-btn--radio:checked + .s-btn .s-btn--badge .s-btn--number { color: var(--_bu-badge-fc, var(--_bu-fc)); } +.s-btn.is-selected:before, +.s-btn--radio:checked + .s-btn:before { + background: linear-gradient(0deg, var(--_bu-bg-gradient-top) 50%, var(--_bu-bg-gradient-bottom) 50%) padding-box, linear-gradient(0deg, var(--_bu-bg-gradient-top) 50%, var(--_bu-bg-gradient-bottom) 50%) border-box; + border: 0; + border-radius: inherit; + content: ""; + inset: 0; + pointer-events: none; + position: absolute; + z-index: -1; +} +@media (forced-colors: active) { + .s-btn.is-selected:before, + .s-btn--radio:checked + .s-btn:before { + display: none; + } +} .s-btn .s-btn--badge { background-color: var(--_bu-badge-bg); border-radius: var(--br-pill); @@ -12701,6 +12719,9 @@ body.theme-system .theme-dark__forced .s-btn--radio:checked + .s-btn { color: var(--_bu-fc-hover, var(--_bu-fc)); } .s-btn--radio:checked + .s-btn:where(:not(.is-selected):hover) { + background: var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))); +} +.s-btn--radio:checked + .s-btn:where(:not(.is-selected):hover):before { background: linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50%) padding-box, linear-gradient(0deg, var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50%) border-box; } fieldset[disabled] .s-btn, @@ -12718,6 +12739,14 @@ fieldset[disabled] .s-btn:hover, border-color: var(--_bu-bc-disabled, var(--_bu-bc, transparent)); color: var(--_bu-fc-disabled, var(--_bu-fc)); } +fieldset[disabled] .s-btn:before, +.s-btn[disabled]:before, +.s-btn[aria-disabled="true"]:before, +fieldset[disabled] .s-btn:hover:before, +.s-btn[disabled]:hover:before, +.s-btn[aria-disabled="true"]:hover:before { + content: none; +} fieldset[disabled] .s-btn .s-btn--badge, .s-btn[disabled] .s-btn--badge, .s-btn[aria-disabled="true"] .s-btn--badge, diff --git a/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts b/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts index c49d497722..af4082bdd3 100644 --- a/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts +++ b/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts @@ -92,63 +92,6 @@ describe("button backgrounds", () => { }); }); - ["button", "radio"].forEach((kind) => { - it(`preserves selected ${kind} gradients, including on hover`, async () => { - const el = await fixture(html` -
- ${ - kind === "radio" - ? html` - - - ` - : html` - - ` - } -
- `); - const button = el.querySelector(".s-btn")!; - const background = getComputedStyle(button).backgroundImage; - expect(background).to.contain("linear-gradient"); - await hover(button); - const hoveredStyle = getComputedStyle(button); - if (kind === "radio") { - expect(hoveredStyle.backgroundImage) - .to.contain("linear-gradient") - .and.not.equal(background); - expect(hoveredStyle.color).to.equal("rgb(0, 128, 0)"); - } else { - expect(hoveredStyle.backgroundImage).to.equal( - background - ); - } - - // Selected gradients still require manual contrast verification. - const results = await measureContrast(el, apcaRule); - expect( - results.incomplete.map((result) => result.id) - ).to.include(apcaRule); - }); - }); - ["s-btn__link", "s-btn__unset"].forEach((modifier) => { it(`keeps ${modifier} backgrounds transparent`, async () => { const button = await fixture(html` diff --git a/packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts b/packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts new file mode 100644 index 0000000000..5fcffe543f --- /dev/null +++ b/packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts @@ -0,0 +1,357 @@ +import { expect, fixture, html } from "@open-wc/testing"; +import { + emulateMedia, + resetMouse, + sendKeys, + sendMouse, +} from "@web/test-runner-commands"; +import axe from "axe-core"; +import Color from "colorjs.io"; +import "../../test/a11y-test-utils"; +import "../../index"; + +const themes = [ + "theme-light", + "theme-dark", + "theme-light theme-highcontrast", + "theme-dark theme-highcontrast", +]; +const variants = [ + "", + "s-btn__danger", + "s-btn__featured", + "s-btn__tonal", + "s-btn__clear", + "s-btn__danger s-btn__clear", +]; + +const selectedFixture = (kind: string, variant = "") => + fixture(html` +
+ ${ + kind === "radio" + ? html` + + + ` + : html` + + ` + } +
+ `); + +const moveMouse = async (el: HTMLElement, type: "move" | "click" = "move") => { + const { x, y, width, height } = el.getBoundingClientRect(); + await sendMouse({ + type, + position: [Math.round(x + width / 2), Math.round(y + height / 2)], + }); +}; + +const measureBaseContrast = async (el: HTMLElement) => { + // APCA measures the solid base. Axe's built-in WCAG rules detect the pseudo-element + // and remain incomplete; checkDecorativeContrast verifies AAA against both colors instead. + const rule = "color-contrast-apca-custom"; + const results = await axe.run(el, { + runOnly: { type: "rule", values: [rule] }, + rules: { [rule]: { enabled: true } }, + }); + expect(results.incomplete).to.be.empty; + expect(results.violations).to.be.empty; + expect(results.passes).to.have.lengthOf(1); + expect(results.passes[0].nodes).to.have.lengthOf(1); +}; + +const checkDecorativeContrast = ( + button: HTMLElement, + highcontrast: boolean +) => { + const style = getComputedStyle(button); + const decoration = getComputedStyle(button, "::before"); + expect(style.backgroundImage).to.equal("none"); + expect(decoration.content).to.equal('""'); + expect(decoration.pointerEvents).to.equal("none"); + + // Read the rendered colors, not the variables or the solid base that axe measures. + // Computed gradients resolve color-mix() to rgb()/color(), including on WebKit. + const stops = decoration.backgroundImage.match(/(?:rgba?|color)\([^)]*\)/g); + expect(stops).to.have.lengthOf(4); + expect(stops![0]).to.equal(stops![2]); + expect(stops![1]).to.equal(stops![3]); + const foreground = new Color(style.color); + expect(foreground.alpha).to.equal(1); + + stops!.slice(0, 2).forEach((stop) => { + const background = new Color(stop); + expect(background.alpha).to.equal(1); + const contrast = Math.abs( + background.contrast(foreground, highcontrast ? "WCAG21" : "APCA") + ); + const threshold = highcontrast + ? 7 + : parseFloat(style.fontSize) >= 32 + ? 45 + : 60; + expect(contrast, `${style.color} against ${stop}`).to.be.at.least( + threshold + ); + }); +}; + +describe("selected button backgrounds", () => { + let originalTheme: string; + + beforeEach(() => { + originalTheme = document.body.className; + }); + afterEach(async () => { + document.body.className = originalTheme; + await resetMouse(); + }); + + themes.forEach((theme) => { + describe(theme, () => { + beforeEach(() => { + document.body.className = theme; + }); + + ["button", "radio"].forEach((kind) => { + variants.forEach((variant) => { + [false, true].forEach((hovered) => { + it(`measures both visible colors of ${kind} ${variant || "base"}${hovered ? " on hover" : ""}`, async () => { + const el = await selectedFixture(kind, variant); + const button = + el.querySelector(".s-btn")!; + if (hovered) { + await moveMouse(button); + } + const highcontrast = theme.includes("highcontrast"); + await measureBaseContrast(el); + checkDecorativeContrast(button, highcontrast); + }); + }); + }); + }); + + it("rejects insufficient contrast in either decorative color", async () => { + const el = await selectedFixture("button"); + const button = el.querySelector(".s-btn")!; + const foreground = getComputedStyle(button).color; + for (const property of [ + "--_bu-bg-gradient-top", + "--_bu-bg-gradient-bottom", + ]) { + button.style.setProperty(property, foreground); + expect(() => + checkDecorativeContrast( + button, + theme.includes("highcontrast") + ) + ).to.throw(); + button.style.removeProperty(property); + } + }); + + it("preserves custom checked-radio hover colors", async () => { + const el = await selectedFixture("radio"); + const button = el.querySelector(".s-btn")!; + button.style.setProperty( + "--theme-button-hover-color", + "rgb(0, 128, 0)" + ); + button.style.setProperty( + "--theme-button-hover-background-color", + "rgb(200, 220, 240)" + ); + const background = getComputedStyle( + button, + "::before" + ).backgroundImage; + await moveMouse(button); + expect(getComputedStyle(button).color).to.equal( + "rgb(0, 128, 0)" + ); + expect(getComputedStyle(button).backgroundColor).to.equal( + "rgb(200, 220, 240)" + ); + expect(getComputedStyle(button, "::before").backgroundImage) + .to.contain("rgb(200, 220, 240)") + .and.not.equal(background); + }); + + ["baw0", "baw1", "baw2", "baw3", "btw0 brw2 bbw3 blw1"].forEach( + (borders) => { + it(`keeps decoration inside ${borders} borders`, async () => { + const el = await selectedFixture("button", borders); + const button = el.querySelector(".s-btn")!; + const style = getComputedStyle(button); + const decoration = getComputedStyle(button, "::before"); + const bounds = button.getBoundingClientRect(); + for (const edge of [ + "top", + "right", + "bottom", + "left", + ] as const) { + expect(decoration[edge]).to.equal("0px"); + } + expect(decoration.borderWidth).to.equal("0px"); + expect(parseFloat(decoration.width)).to.be.closeTo( + bounds.width - + parseFloat(style.borderLeftWidth) - + parseFloat(style.borderRightWidth), + 0.1 + ); + expect(parseFloat(decoration.height)).to.be.closeTo( + bounds.height - + parseFloat(style.borderTopWidth) - + parseFloat(style.borderBottomWidth), + 0.1 + ); + }); + } + ); + + ["disabled", "aria-disabled", "fieldset"].forEach((kind) => { + it(`does not decorate ${kind} selected buttons`, async () => { + const el = await fixture(html` +
+ +
+ `); + const button = el.querySelector("button")!; + const background = getComputedStyle(button).backgroundColor; + for (const hovered of [false, true]) { + if (hovered) { + await moveMouse(button); + } + expect( + getComputedStyle(button).backgroundImage + ).to.equal("none"); + expect( + getComputedStyle(button).backgroundColor + ).to.equal(background); + expect( + getComputedStyle(button, "::before").content + ).to.equal("none"); + } + }); + }); + + it("removes checked-radio decoration when disabled fieldset hover styles apply", async () => { + const el = await fixture(html` +
+ + +
+ `); + const button = el.querySelector(".s-btn")!; + await moveMouse(button); + expect(getComputedStyle(button, "::before").content).to.equal( + "none" + ); + expect(getComputedStyle(button).backgroundImage).to.equal( + "none" + ); + }); + + it("allows pointer and keyboard activation while selected and after deselection", async () => { + const el = await selectedFixture("button"); + const button = el.querySelector("button")!; + let activations = 0; + button.addEventListener("click", () => { + activations++; + button.classList.toggle("is-selected"); + button.setAttribute( + "aria-pressed", + String(button.classList.contains("is-selected")) + ); + }); + await moveMouse(button, "click"); + expect(activations).to.equal(1); + expect(getComputedStyle(button, "::before").content).to.equal( + "none" + ); + // WebKit does not focus native buttons on pointer activation. + button.focus(); + expect(document.activeElement).to.equal(button); + await sendKeys({ press: "Space" }); + expect(activations).to.equal(2); + expect(getComputedStyle(button, "::before").content).to.equal( + '""' + ); + }); + + it("selects a radio with the keyboard and by clicking its decorated label", async () => { + const el = await selectedFixture("radio"); + const input = el.querySelector("input")!; + const label = el.querySelector("label")!; + input.checked = false; + input.focus(); + await sendKeys({ press: "Space" }); + expect(input.checked).to.equal(true); + expect(getComputedStyle(label, "::before").content).to.equal( + '""' + ); + input.checked = false; + await moveMouse(label, "click"); + expect(input.checked).to.equal(true); + expect(getComputedStyle(label, "::before").content).to.equal( + '""' + ); + }); + }); + }); + + it("lets forced-colors mode paint selected buttons without decoration", async function () { + // Playwright does not support forced-colors emulation in WebKit. + if ( + navigator.userAgent.includes("AppleWebKit") && + !navigator.userAgent.includes("Chrome") + ) { + this.skip(); + } + try { + await emulateMedia({ forcedColors: "active" }); + const el = await selectedFixture("button"); + const button = el.querySelector("button")!; + expect(matchMedia("(forced-colors: active)").matches).to.equal( + true + ); + expect(getComputedStyle(button, "::before").display).to.equal( + "none" + ); + expect(getComputedStyle(button).backgroundImage).to.equal("none"); + } finally { + await emulateMedia({ forcedColors: "none" }); + } + }); +}); diff --git a/packages/stacks-classic/lib/components/button/button-selected.visual.test.ts b/packages/stacks-classic/lib/components/button/button-selected.visual.test.ts new file mode 100644 index 0000000000..b99552b1e0 --- /dev/null +++ b/packages/stacks-classic/lib/components/button/button-selected.visual.test.ts @@ -0,0 +1,148 @@ +import { fixture, html } from "@open-wc/testing"; +import { resetMouse, sendKeys, sendMouse } from "@web/test-runner-commands"; +import { visualDiff } from "@web/test-runner-visual-regression"; +import "../../index"; + +const themes = [ + "theme-light", + "theme-dark", + "theme-light theme-highcontrast", + "theme-dark theme-highcontrast", +]; +const variants = [ + "", + "s-btn__danger", + "s-btn__featured", + "s-btn__tonal", + "s-btn__clear", + "s-btn__danger s-btn__clear", +]; + +describe("selected button visuals", () => { + let originalTheme: string; + beforeEach(() => { + originalTheme = document.body.className; + }); + afterEach(async () => { + document.body.className = originalTheme; + await resetMouse(); + }); + + themes.forEach((theme) => { + ["rest", "hover-radio", "focus"].forEach((state) => { + it(`preserves ${theme} selected backgrounds on ${state}`, async () => { + document.body.className = theme; + const el = await fixture(html` +
+ ${variants.map( + (variant, i) => html` +
+ + + + + +
+ ` + )} +
+ ${[ + "baw0", + "baw1", + "baw2", + "baw3", + "btw0 brw2 bbw3 blw1", + ].map( + (borders) => html` + + ` + )} +
+
+ + +
+
+ `); + if (state === "hover-radio") { + const { x, y, width, height } = el + .querySelector("label")! + .getBoundingClientRect(); + await sendMouse({ + type: "move", + position: [ + Math.round(x + width / 2), + Math.round(y + height / 2), + ], + }); + } else if (state === "focus") { + await sendKeys({ press: "Tab" }); + if (document.activeElement !== el.querySelector("button")) { + throw new Error( + "Expected the first selected button to receive keyboard focus" + ); + } + } + await ( + visualDiff as ( + element: HTMLElement, + name: string + ) => Promise + )(el, `s-btn-selected-${theme.split(" ").join("-")}-${state}`); + }); + }); + }); +}); diff --git a/packages/stacks-classic/lib/components/button/button.less b/packages/stacks-classic/lib/components/button/button.less index cd164e8748..417307ce18 100644 --- a/packages/stacks-classic/lib/components/button/button.less +++ b/packages/stacks-classic/lib/components/button/button.less @@ -284,15 +284,33 @@ background-color: var(--_bu-badge-bg); } - background: linear-gradient(0deg, - var(--_bu-bg-gradient-top) 50%, - var(--_bu-bg-gradient-bottom) 50% - ) padding-box, - linear-gradient(0deg, - var(--_bu-bg-gradient-top) 50%, - var(--_bu-bg-gradient-bottom) 50% - ) border-box; + &:before { + // Keep decoration inside the actual border, including border-width utility overrides. + background: linear-gradient(0deg, + var(--_bu-bg-gradient-top) 50%, + var(--_bu-bg-gradient-bottom) 50% + ) padding-box, + linear-gradient(0deg, + var(--_bu-bg-gradient-top) 50%, + var(--_bu-bg-gradient-bottom) 50% + ) border-box; + border: 0; + border-radius: inherit; + content: ""; + inset: 0; + pointer-events: none; + position: absolute; + z-index: -1; + + @media (forced-colors: active) { + display: none; + } + } + + // The solid base is measurable by axe; tests check both decorative colors separately. + background: var(--_bu-bg-selected, var(--_bu-bg)); color: var(--_bu-fc-selected, var(--_bu-fc)); + isolation: isolate; } // CHILD ELEMENTS @@ -363,17 +381,21 @@ color: var(--_bu-fc-hover, var(--_bu-fc)); } - // Preserve checked radio hover gradients, without increasing specificity over disabled states. + // Preserve checked radio hover colors, without increasing specificity over disabled states. .s-btn--radio:checked + &:where(:not(.is-selected):hover) { - background: - linear-gradient(0deg, - var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, - var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50% - ) padding-box, - linear-gradient(0deg, - var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, - var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50% - ) border-box; + &:before { + background: + linear-gradient(0deg, + var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, + var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50% + ) padding-box, + linear-gradient(0deg, + var(--_bu-bg-selected-overlay, var(--_bu-bg-hover, var(--_bu-bg))) 50%, + var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))) 50% + ) border-box; + } + + background: var(--_bu-bg-hover, var(--_bu-bg-selected, var(--_bu-bg))); } fieldset[disabled] &, @@ -381,6 +403,10 @@ &[aria-disabled="true"] { &, &:hover { + &:before { + content: none; + } + .s-btn--badge { .s-btn--number { color: var(--_bu-badge-fc-disabled); @@ -397,7 +423,7 @@ cursor: auto; } - // Only selected states need a gradient; solid backgrounds let axe measure contrast. + // Selected gradients are decorative layers; the button itself always has a solid background. background: var(--_bu-bg); border: var(--_bu-baw) solid var(--_bu-bc, transparent); color: var(--_bu-fc); diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico new file mode 100644 index 0000000000..b4b4f7f1f2 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0204085508c319064f0b1e063210076b22eef75aa8d7343f24f2684ca60c796a +size 55906 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico new file mode 100644 index 0000000000..1d3e4a8c6c --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:344516dae5330d2629b3bb7cbcaa1182f5801edba312d7084748ead713ab661c +size 55731 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico new file mode 100644 index 0000000000..23a962c003 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54f7ab9374a30d6bca15400435b85fda018521becf117cf05ec6e91c411d5268 +size 54220 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico new file mode 100644 index 0000000000..a9ce58ecaa --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5da0298a0c0a02447a2b8f60b01ec99c0241cc459acd8e6581d52e21d08354a8 +size 59093 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico new file mode 100644 index 0000000000..02233d85cc --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ea08026b08b4c29653c1b9a61dc39de39c0740a08e7091dd9bdba9152738579 +size 58845 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico new file mode 100644 index 0000000000..96c4befac6 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bb3357f30b48265c2c0c642725baceb890ab7941c8598a45d99777ff81cd7b3 +size 57423 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico new file mode 100644 index 0000000000..f1c8a02f97 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fd684f0f4e0bd4a127fb82e1fd5a70f8a7e2883ddb9f4407c1c71867b72721d +size 56426 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico new file mode 100644 index 0000000000..67b09f7fbf --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b472db916aa96a42393a983d9a401756a2d26ed4b3807186cc6b4aab0324d42 +size 56073 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico new file mode 100644 index 0000000000..4f7ed9d680 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9ed47871069f402a9cedeb2f4a7af11c6666538f4e7c52b03cbcf568c7e0294 +size 54728 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico new file mode 100644 index 0000000000..726a1ee458 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c0ec38457f0c5f8af3dba342cc78275df83f0e4bd349b5b2860940a0c4a8fa5 +size 61009 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico new file mode 100644 index 0000000000..e6ab06fff1 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2b69676533b89e3aac31161ae17638398970ad5e9dab70b61e3d64262140129 +size 60774 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico new file mode 100644 index 0000000000..6aa284907a --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c12e81ec82af19a79eb7983ce4bc0f44b90feb4bfeae353efc205539e315136d +size 59289 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico new file mode 100644 index 0000000000..3603edf2ed --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1bc615cab0e8bbbb0427cc769aab3d939cc0df29b578db316abdfc035642250 +size 60986 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico new file mode 100644 index 0000000000..fea6d12144 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cbb205d62b22737fb4762ec2029d0c6ee897b2b115394ab971a689faccb3c85 +size 60752 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico new file mode 100644 index 0000000000..700f18671a --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f68de90fc34775c329c8bf21cf989f9a167c4b8557a5ac89dc1baca19143540 +size 59417 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico new file mode 100644 index 0000000000..48eef942ac --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:237a16d9570d500b14c2b725a384f72f0e9efede52e379c3a89fe9c54fde369f +size 65993 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico new file mode 100644 index 0000000000..cc8eccc094 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a165f1efe5db52d911707790e6ed5513984645c804efcc38e88858aa08f75bb +size 65793 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico new file mode 100644 index 0000000000..a168ed603a --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46349aaf731de7a92bcaa368610b8692566d9348630c8ea341ad2b3dc6891ba0 +size 64398 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico new file mode 100644 index 0000000000..6304be72a2 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daf56528ab4bcad21e9a6e74fa8749817c5f8001eb9befdd2f4727470f0510f0 +size 63041 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico new file mode 100644 index 0000000000..ac11d62c75 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a924fa9ed3ba23a0a5be897e6e6b1aa181d94b9ab2057df5827a3d1c65338edb +size 62754 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico new file mode 100644 index 0000000000..1ffce60b9f --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45d8b9df49a9dcc387e8f5ab0e8313899426fbd58fc530005e07eb223b90527f +size 61389 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico new file mode 100644 index 0000000000..96aebb0189 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c194bf7c8bf34783de5581b2a1f441218225e4a57dbd7a5b675fd83f63a23cb +size 68614 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico new file mode 100644 index 0000000000..a73eebb085 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aec8f32dbccebe34b87e4482ad8ecf89ae70f0b72a9ff44520e67c5d2f4e0b73 +size 68300 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico new file mode 100644 index 0000000000..3dfb688ed4 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ed82358bebb0aaa70c632389ae497c88ffd0617c26df721e5ffe60000b119f6 +size 66995 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico new file mode 100644 index 0000000000..d586459de0 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b12bfb4c3b4037df3c185d5db1a045fb9cda4e2ce19dcbdfb0d95c6f76dc45d3 +size 67695 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico new file mode 100644 index 0000000000..2c516da077 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59bcac92cadd75a235cd5e18df67b99f03f31318456b3e7e040c5dc2251f5b56 +size 67142 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico new file mode 100644 index 0000000000..6f57710862 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29a155bf2ce3231d478fc63158a9bc125b9872cf704101eabcb0520cfd6708d6 +size 65949 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico new file mode 100644 index 0000000000..a47542ab4b --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fb69b0433e8a3da911b72705f9d668e0e459c9bced9c98ec565241cac25dc15 +size 75576 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico new file mode 100644 index 0000000000..c421a3eed6 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6099dfbc442c652f36c3b6c9be7520d176d31f5b85701865df5f718bd5f64cf +size 75044 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico new file mode 100644 index 0000000000..1db84da411 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41e63756c33051b32209d8823ab49d2f1572af275e4830402980af651f009bf8 +size 73783 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico new file mode 100644 index 0000000000..412cb208e7 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a46f4b13de18103b79def6f2f0dd29603676e4b65caf65bded3014f335ee6c3d +size 66491 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico new file mode 100644 index 0000000000..83a31662cb --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96062b2234e9a27b23841a759dc0583c9197cb76030fedc8aefa28515dfb5d5d +size 65970 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico new file mode 100644 index 0000000000..3c6b2e7a4c --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:232278c75ba84abc88e3c550e6a90c99dce247cdd890e777995a1a798aa46aa7 +size 64713 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico new file mode 100644 index 0000000000..093a990745 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a205fbfab484c30ff7b4fc1d118e42565d6e3a5d688b6c954f2894c27c89250 +size 75421 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico new file mode 100644 index 0000000000..d88af6ea3d --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6deffd296649e983a2bc8184dd96e81d9d8c346c7db97a6b6a89a17ee853405 +size 74872 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico new file mode 100644 index 0000000000..38bceac3bc --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36b31e94098d40c33ab579814f8d52f78b72fc5f793867bf605232c2f3512cf9 +size 73587 From a18832f682f8d9de3dda941e8e8cee2279f9ec5a Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Thu, 15 Oct 2026 16:22:47 -0400 Subject: [PATCH 3/3] test(button): simplify contrast regression coverage --- .../button/button-background.a11y.test.ts | 163 -------- .../button/button-selected.a11y.test.ts | 357 ------------------ .../button/button-selected.visual.test.ts | 148 -------- .../lib/components/button/button.a11y.test.ts | 121 ++++++ .../components/button/button.visual.test.ts | 35 ++ .../Chromium/baseline/s-btn-selected-dark.ico | 3 + .../s-btn-selected-highcontrast-dark.ico | 3 + .../s-btn-selected-highcontrast-light.ico | 3 + .../baseline/s-btn-selected-light.ico | 3 + .../s-btn-selected-theme-dark-focus.ico | 3 - .../s-btn-selected-theme-dark-hover-radio.ico | 3 - .../s-btn-selected-theme-dark-rest.ico | 3 - ...ed-theme-dark-theme-highcontrast-focus.ico | 3 - ...me-dark-theme-highcontrast-hover-radio.ico | 3 - ...ted-theme-dark-theme-highcontrast-rest.ico | 3 - .../s-btn-selected-theme-light-focus.ico | 3 - ...s-btn-selected-theme-light-hover-radio.ico | 3 - .../s-btn-selected-theme-light-rest.ico | 3 - ...d-theme-light-theme-highcontrast-focus.ico | 3 - ...e-light-theme-highcontrast-hover-radio.ico | 3 - ...ed-theme-light-theme-highcontrast-rest.ico | 3 - .../Firefox/baseline/s-btn-selected-dark.ico | 3 + .../s-btn-selected-highcontrast-dark.ico | 3 + .../s-btn-selected-highcontrast-light.ico | 3 + .../Firefox/baseline/s-btn-selected-light.ico | 3 + .../s-btn-selected-theme-dark-focus.ico | 3 - .../s-btn-selected-theme-dark-hover-radio.ico | 3 - .../s-btn-selected-theme-dark-rest.ico | 3 - ...ed-theme-dark-theme-highcontrast-focus.ico | 3 - ...me-dark-theme-highcontrast-hover-radio.ico | 3 - ...ted-theme-dark-theme-highcontrast-rest.ico | 3 - .../s-btn-selected-theme-light-focus.ico | 3 - ...s-btn-selected-theme-light-hover-radio.ico | 3 - .../s-btn-selected-theme-light-rest.ico | 3 - ...d-theme-light-theme-highcontrast-focus.ico | 3 - ...e-light-theme-highcontrast-hover-radio.ico | 3 - ...ed-theme-light-theme-highcontrast-rest.ico | 3 - .../Webkit/baseline/s-btn-selected-dark.ico | 3 + .../s-btn-selected-highcontrast-dark.ico | 3 + .../s-btn-selected-highcontrast-light.ico | 3 + .../Webkit/baseline/s-btn-selected-light.ico | 3 + .../s-btn-selected-theme-dark-focus.ico | 3 - .../s-btn-selected-theme-dark-hover-radio.ico | 3 - .../s-btn-selected-theme-dark-rest.ico | 3 - ...ed-theme-dark-theme-highcontrast-focus.ico | 3 - ...me-dark-theme-highcontrast-hover-radio.ico | 3 - ...ted-theme-dark-theme-highcontrast-rest.ico | 3 - .../s-btn-selected-theme-light-focus.ico | 3 - ...s-btn-selected-theme-light-hover-radio.ico | 3 - .../s-btn-selected-theme-light-rest.ico | 3 - ...d-theme-light-theme-highcontrast-focus.ico | 3 - ...e-light-theme-highcontrast-hover-radio.ico | 3 - ...ed-theme-light-theme-highcontrast-rest.ico | 3 - 53 files changed, 192 insertions(+), 776 deletions(-) delete mode 100644 packages/stacks-classic/lib/components/button/button-background.a11y.test.ts delete mode 100644 packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts delete mode 100644 packages/stacks-classic/lib/components/button/button-selected.visual.test.ts create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-dark.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-dark.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-light.ico create mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-light.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-dark.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-dark.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-light.ico create mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-light.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-dark.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-dark.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-light.ico create mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-light.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico delete mode 100644 packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico diff --git a/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts b/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts deleted file mode 100644 index af4082bdd3..0000000000 --- a/packages/stacks-classic/lib/components/button/button-background.a11y.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { expect, fixture, html } from "@open-wc/testing"; -import { sendMouse } from "@web/test-runner-commands"; -import axe from "axe-core"; -import "../../test/a11y-test-utils"; -import "../../index"; - -const apcaRule = "color-contrast-apca-custom"; -const themes = [ - "theme-light", - "theme-dark", - "theme-light theme-highcontrast", - "theme-dark theme-highcontrast", -]; - -const hover = async (el: HTMLElement) => { - const { x, y, width, height } = el.getBoundingClientRect(); - await sendMouse({ - type: "move", - position: [Math.round(x + width / 2), Math.round(y + height / 2)], - }); - expect(el.matches(":hover")).to.equal(true); -}; - -const measureContrast = (el: HTMLElement, rule: string) => - axe.run(el, { - runOnly: { type: "rule", values: [rule] }, - rules: { [rule]: { enabled: true } }, - }); - -describe("button backgrounds", () => { - let originalTheme: string; - - beforeEach(() => { - originalTheme = document.body.className; - }); - - afterEach(async () => { - document.body.className = originalTheme; - await sendMouse({ type: "move", position: [0, 0] }); - }); - - themes.forEach((theme) => { - describe(theme, () => { - beforeEach(() => { - document.body.className = theme; - }); - - [ - "s-btn", - "s-btn s-btn__danger", - "s-btn s-btn__featured", - "s-btn s-btn__tonal", - "s-btn s-btn__clear", - "s-btn s-btn__danger s-btn__clear", - ].forEach((classes) => { - [false, true].forEach((hovered) => { - it(`measures ${classes}${hovered ? " on hover" : ""}`, async () => { - const el = await fixture(html` -
- -
- `); - const button = el.querySelector("button")!; - if (hovered) { - await hover(button); - } - - expect( - getComputedStyle(button).backgroundImage - ).to.equal("none"); - const rule = theme.includes("highcontrast") - ? "color-contrast-enhanced" - : apcaRule; - const results = await measureContrast(el, rule); - - // Absence of violations alone also accepts unmeasurable gradients. - expect(results.incomplete).to.be.empty; - expect(results.violations).to.be.empty; - expect(results.passes).to.have.lengthOf(1); - expect(results.passes[0].nodes).to.have.lengthOf(1); - if (rule === apcaRule) { - expect( - results.passes[0].nodes[0].all[0].data - .apcaContrast - ) - .to.be.a("number") - .and.greaterThan(0); - } - }); - }); - }); - - ["s-btn__link", "s-btn__unset"].forEach((modifier) => { - it(`keeps ${modifier} backgrounds transparent`, async () => { - const button = await fixture(html` - - `); - for (const hovered of [false, true]) { - if (hovered) { - await hover(button); - } - const style = getComputedStyle(button); - expect(style.backgroundImage).to.equal("none"); - expect(style.backgroundColor).to.equal( - "rgba(0, 0, 0, 0)" - ); - } - }); - }); - - ["disabled", "aria-disabled", "fieldset"].forEach((kind) => { - it(`preserves ${kind} backgrounds on hover`, async () => { - const el = await fixture(html` -
- -
- `); - const button = el.querySelector("button")!; - const background = getComputedStyle(button).backgroundColor; - await hover(button); - expect(getComputedStyle(button).backgroundImage).to.equal( - "none" - ); - expect(getComputedStyle(button).backgroundColor).to.equal( - background - ); - }); - }); - - it("keeps disabled fieldset styles above checked radio hover styles", async () => { - const el = await fixture(html` -
- - -
- `); - const button = el.querySelector(".s-btn")!; - await hover(button); - expect(getComputedStyle(button).backgroundImage).to.equal( - "none" - ); - }); - }); - }); -}); diff --git a/packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts b/packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts deleted file mode 100644 index 5fcffe543f..0000000000 --- a/packages/stacks-classic/lib/components/button/button-selected.a11y.test.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { expect, fixture, html } from "@open-wc/testing"; -import { - emulateMedia, - resetMouse, - sendKeys, - sendMouse, -} from "@web/test-runner-commands"; -import axe from "axe-core"; -import Color from "colorjs.io"; -import "../../test/a11y-test-utils"; -import "../../index"; - -const themes = [ - "theme-light", - "theme-dark", - "theme-light theme-highcontrast", - "theme-dark theme-highcontrast", -]; -const variants = [ - "", - "s-btn__danger", - "s-btn__featured", - "s-btn__tonal", - "s-btn__clear", - "s-btn__danger s-btn__clear", -]; - -const selectedFixture = (kind: string, variant = "") => - fixture(html` -
- ${ - kind === "radio" - ? html` - - - ` - : html` - - ` - } -
- `); - -const moveMouse = async (el: HTMLElement, type: "move" | "click" = "move") => { - const { x, y, width, height } = el.getBoundingClientRect(); - await sendMouse({ - type, - position: [Math.round(x + width / 2), Math.round(y + height / 2)], - }); -}; - -const measureBaseContrast = async (el: HTMLElement) => { - // APCA measures the solid base. Axe's built-in WCAG rules detect the pseudo-element - // and remain incomplete; checkDecorativeContrast verifies AAA against both colors instead. - const rule = "color-contrast-apca-custom"; - const results = await axe.run(el, { - runOnly: { type: "rule", values: [rule] }, - rules: { [rule]: { enabled: true } }, - }); - expect(results.incomplete).to.be.empty; - expect(results.violations).to.be.empty; - expect(results.passes).to.have.lengthOf(1); - expect(results.passes[0].nodes).to.have.lengthOf(1); -}; - -const checkDecorativeContrast = ( - button: HTMLElement, - highcontrast: boolean -) => { - const style = getComputedStyle(button); - const decoration = getComputedStyle(button, "::before"); - expect(style.backgroundImage).to.equal("none"); - expect(decoration.content).to.equal('""'); - expect(decoration.pointerEvents).to.equal("none"); - - // Read the rendered colors, not the variables or the solid base that axe measures. - // Computed gradients resolve color-mix() to rgb()/color(), including on WebKit. - const stops = decoration.backgroundImage.match(/(?:rgba?|color)\([^)]*\)/g); - expect(stops).to.have.lengthOf(4); - expect(stops![0]).to.equal(stops![2]); - expect(stops![1]).to.equal(stops![3]); - const foreground = new Color(style.color); - expect(foreground.alpha).to.equal(1); - - stops!.slice(0, 2).forEach((stop) => { - const background = new Color(stop); - expect(background.alpha).to.equal(1); - const contrast = Math.abs( - background.contrast(foreground, highcontrast ? "WCAG21" : "APCA") - ); - const threshold = highcontrast - ? 7 - : parseFloat(style.fontSize) >= 32 - ? 45 - : 60; - expect(contrast, `${style.color} against ${stop}`).to.be.at.least( - threshold - ); - }); -}; - -describe("selected button backgrounds", () => { - let originalTheme: string; - - beforeEach(() => { - originalTheme = document.body.className; - }); - afterEach(async () => { - document.body.className = originalTheme; - await resetMouse(); - }); - - themes.forEach((theme) => { - describe(theme, () => { - beforeEach(() => { - document.body.className = theme; - }); - - ["button", "radio"].forEach((kind) => { - variants.forEach((variant) => { - [false, true].forEach((hovered) => { - it(`measures both visible colors of ${kind} ${variant || "base"}${hovered ? " on hover" : ""}`, async () => { - const el = await selectedFixture(kind, variant); - const button = - el.querySelector(".s-btn")!; - if (hovered) { - await moveMouse(button); - } - const highcontrast = theme.includes("highcontrast"); - await measureBaseContrast(el); - checkDecorativeContrast(button, highcontrast); - }); - }); - }); - }); - - it("rejects insufficient contrast in either decorative color", async () => { - const el = await selectedFixture("button"); - const button = el.querySelector(".s-btn")!; - const foreground = getComputedStyle(button).color; - for (const property of [ - "--_bu-bg-gradient-top", - "--_bu-bg-gradient-bottom", - ]) { - button.style.setProperty(property, foreground); - expect(() => - checkDecorativeContrast( - button, - theme.includes("highcontrast") - ) - ).to.throw(); - button.style.removeProperty(property); - } - }); - - it("preserves custom checked-radio hover colors", async () => { - const el = await selectedFixture("radio"); - const button = el.querySelector(".s-btn")!; - button.style.setProperty( - "--theme-button-hover-color", - "rgb(0, 128, 0)" - ); - button.style.setProperty( - "--theme-button-hover-background-color", - "rgb(200, 220, 240)" - ); - const background = getComputedStyle( - button, - "::before" - ).backgroundImage; - await moveMouse(button); - expect(getComputedStyle(button).color).to.equal( - "rgb(0, 128, 0)" - ); - expect(getComputedStyle(button).backgroundColor).to.equal( - "rgb(200, 220, 240)" - ); - expect(getComputedStyle(button, "::before").backgroundImage) - .to.contain("rgb(200, 220, 240)") - .and.not.equal(background); - }); - - ["baw0", "baw1", "baw2", "baw3", "btw0 brw2 bbw3 blw1"].forEach( - (borders) => { - it(`keeps decoration inside ${borders} borders`, async () => { - const el = await selectedFixture("button", borders); - const button = el.querySelector(".s-btn")!; - const style = getComputedStyle(button); - const decoration = getComputedStyle(button, "::before"); - const bounds = button.getBoundingClientRect(); - for (const edge of [ - "top", - "right", - "bottom", - "left", - ] as const) { - expect(decoration[edge]).to.equal("0px"); - } - expect(decoration.borderWidth).to.equal("0px"); - expect(parseFloat(decoration.width)).to.be.closeTo( - bounds.width - - parseFloat(style.borderLeftWidth) - - parseFloat(style.borderRightWidth), - 0.1 - ); - expect(parseFloat(decoration.height)).to.be.closeTo( - bounds.height - - parseFloat(style.borderTopWidth) - - parseFloat(style.borderBottomWidth), - 0.1 - ); - }); - } - ); - - ["disabled", "aria-disabled", "fieldset"].forEach((kind) => { - it(`does not decorate ${kind} selected buttons`, async () => { - const el = await fixture(html` -
- -
- `); - const button = el.querySelector("button")!; - const background = getComputedStyle(button).backgroundColor; - for (const hovered of [false, true]) { - if (hovered) { - await moveMouse(button); - } - expect( - getComputedStyle(button).backgroundImage - ).to.equal("none"); - expect( - getComputedStyle(button).backgroundColor - ).to.equal(background); - expect( - getComputedStyle(button, "::before").content - ).to.equal("none"); - } - }); - }); - - it("removes checked-radio decoration when disabled fieldset hover styles apply", async () => { - const el = await fixture(html` -
- - -
- `); - const button = el.querySelector(".s-btn")!; - await moveMouse(button); - expect(getComputedStyle(button, "::before").content).to.equal( - "none" - ); - expect(getComputedStyle(button).backgroundImage).to.equal( - "none" - ); - }); - - it("allows pointer and keyboard activation while selected and after deselection", async () => { - const el = await selectedFixture("button"); - const button = el.querySelector("button")!; - let activations = 0; - button.addEventListener("click", () => { - activations++; - button.classList.toggle("is-selected"); - button.setAttribute( - "aria-pressed", - String(button.classList.contains("is-selected")) - ); - }); - await moveMouse(button, "click"); - expect(activations).to.equal(1); - expect(getComputedStyle(button, "::before").content).to.equal( - "none" - ); - // WebKit does not focus native buttons on pointer activation. - button.focus(); - expect(document.activeElement).to.equal(button); - await sendKeys({ press: "Space" }); - expect(activations).to.equal(2); - expect(getComputedStyle(button, "::before").content).to.equal( - '""' - ); - }); - - it("selects a radio with the keyboard and by clicking its decorated label", async () => { - const el = await selectedFixture("radio"); - const input = el.querySelector("input")!; - const label = el.querySelector("label")!; - input.checked = false; - input.focus(); - await sendKeys({ press: "Space" }); - expect(input.checked).to.equal(true); - expect(getComputedStyle(label, "::before").content).to.equal( - '""' - ); - input.checked = false; - await moveMouse(label, "click"); - expect(input.checked).to.equal(true); - expect(getComputedStyle(label, "::before").content).to.equal( - '""' - ); - }); - }); - }); - - it("lets forced-colors mode paint selected buttons without decoration", async function () { - // Playwright does not support forced-colors emulation in WebKit. - if ( - navigator.userAgent.includes("AppleWebKit") && - !navigator.userAgent.includes("Chrome") - ) { - this.skip(); - } - try { - await emulateMedia({ forcedColors: "active" }); - const el = await selectedFixture("button"); - const button = el.querySelector("button")!; - expect(matchMedia("(forced-colors: active)").matches).to.equal( - true - ); - expect(getComputedStyle(button, "::before").display).to.equal( - "none" - ); - expect(getComputedStyle(button).backgroundImage).to.equal("none"); - } finally { - await emulateMedia({ forcedColors: "none" }); - } - }); -}); diff --git a/packages/stacks-classic/lib/components/button/button-selected.visual.test.ts b/packages/stacks-classic/lib/components/button/button-selected.visual.test.ts deleted file mode 100644 index b99552b1e0..0000000000 --- a/packages/stacks-classic/lib/components/button/button-selected.visual.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { fixture, html } from "@open-wc/testing"; -import { resetMouse, sendKeys, sendMouse } from "@web/test-runner-commands"; -import { visualDiff } from "@web/test-runner-visual-regression"; -import "../../index"; - -const themes = [ - "theme-light", - "theme-dark", - "theme-light theme-highcontrast", - "theme-dark theme-highcontrast", -]; -const variants = [ - "", - "s-btn__danger", - "s-btn__featured", - "s-btn__tonal", - "s-btn__clear", - "s-btn__danger s-btn__clear", -]; - -describe("selected button visuals", () => { - let originalTheme: string; - beforeEach(() => { - originalTheme = document.body.className; - }); - afterEach(async () => { - document.body.className = originalTheme; - await resetMouse(); - }); - - themes.forEach((theme) => { - ["rest", "hover-radio", "focus"].forEach((state) => { - it(`preserves ${theme} selected backgrounds on ${state}`, async () => { - document.body.className = theme; - const el = await fixture(html` -
- ${variants.map( - (variant, i) => html` -
- - - - - -
- ` - )} -
- ${[ - "baw0", - "baw1", - "baw2", - "baw3", - "btw0 brw2 bbw3 blw1", - ].map( - (borders) => html` - - ` - )} -
-
- - -
-
- `); - if (state === "hover-radio") { - const { x, y, width, height } = el - .querySelector("label")! - .getBoundingClientRect(); - await sendMouse({ - type: "move", - position: [ - Math.round(x + width / 2), - Math.round(y + height / 2), - ], - }); - } else if (state === "focus") { - await sendKeys({ press: "Tab" }); - if (document.activeElement !== el.querySelector("button")) { - throw new Error( - "Expected the first selected button to receive keyboard focus" - ); - } - } - await ( - visualDiff as ( - element: HTMLElement, - name: string - ) => Promise - )(el, `s-btn-selected-${theme.split(" ").join("-")}-${state}`); - }); - }); - }); -}); diff --git a/packages/stacks-classic/lib/components/button/button.a11y.test.ts b/packages/stacks-classic/lib/components/button/button.a11y.test.ts index a670beacb8..1c03f20d84 100644 --- a/packages/stacks-classic/lib/components/button/button.a11y.test.ts +++ b/packages/stacks-classic/lib/components/button/button.a11y.test.ts @@ -1,5 +1,9 @@ import testArgs from "./button.test.setup"; import { runA11yTests } from "../../test/a11y-test-utils"; +import { expect, fixture, html } from "@open-wc/testing"; +import { resetMouse, sendMouse } from "@web/test-runner-commands"; +import axe from "axe-core"; +import Color from "colorjs.io"; import "../../index"; describe("button", () => { @@ -9,4 +13,121 @@ describe("button", () => { /^s-btn-(?=.*unset).*badge$/, // s-btn with badge and unset variant not supported ], }); + + [ + "theme-light", + "theme-dark", + "theme-light theme-highcontrast", + "theme-dark theme-highcontrast", + ].forEach((theme) => { + it(`measures ordinary and selected backgrounds in ${theme}, including hover`, async () => { + const originalTheme = document.body.className; + try { + document.body.className = theme; + const el = await fixture(html` +
+ + ${[ + "", + "s-btn__danger", + "s-btn__featured", + "s-btn__tonal", + "s-btn__clear", + "s-btn__danger s-btn__clear", + ].map( + (variant) => html` + + ` + )} + + +
+ `); + for (const hovered of [false, true]) { + for (const button of el.querySelectorAll( + ".s-btn" + )) { + if (hovered) { + const before = getComputedStyle( + button, + "::before" + ).backgroundImage; + const { x, y, width, height } = + button.getBoundingClientRect(); + await sendMouse({ + type: "move", + position: [ + Math.round(x + width / 2), + Math.round(y + height / 2), + ], + }); + if (button.matches("label")) { + expect( + getComputedStyle(button, "::before") + .backgroundImage + ).not.to.equal(before); + } + } + expect( + getComputedStyle(button).backgroundImage + ).to.equal("none"); + const rule = "color-contrast-apca-custom"; + const results = await axe.run(button, { + runOnly: [rule], + rules: { [rule]: { enabled: true } }, + }); + expect(results.incomplete).to.be.empty; + expect(results.violations).to.be.empty; + expect(results.passes).to.have.lengthOf(1); + expect(results.passes[0].nodes).to.have.lengthOf(1); + + if (!button.matches(".is-selected, label")) { + continue; + } + // Axe measures the solid base, so check both visible gradient colors separately. + const stops = getComputedStyle( + button, + "::before" + ).backgroundImage.match(/(?:rgba?|color)\([^)]*\)/g); + expect(stops).to.have.lengthOf(4); + const foreground = new Color( + getComputedStyle(button).color + ); + const highcontrast = theme.includes("highcontrast"); + for (const stop of stops!.slice(0, 2)) { + const background = new Color(stop); + const contrast = Math.abs( + background.contrast( + foreground, + highcontrast ? "WCAG21" : "APCA" + ) + ); + expect( + contrast, + `${theme}: ${button.className} against ${stop}` + ).to.be.at.least(highcontrast ? 7 : 60); + } + } + } + } finally { + document.body.className = originalTheme; + await resetMouse(); + } + }); + }); }); diff --git a/packages/stacks-classic/lib/components/button/button.visual.test.ts b/packages/stacks-classic/lib/components/button/button.visual.test.ts index f801287a85..bf473cb300 100644 --- a/packages/stacks-classic/lib/components/button/button.visual.test.ts +++ b/packages/stacks-classic/lib/components/button/button.visual.test.ts @@ -15,4 +15,39 @@ describe("button", () => { `, }); + + runVisualTests({ + baseClass: "s-btn-selected", + template: ({ testid }) => html` +
+ ${[ + "", + "s-btn__danger", + "s-btn__featured", + "s-btn__tonal", + "s-btn__clear", + "s-btn__danger s-btn__clear", + ].map( + (variant) => html` + + ` + )} + + +
+ `, + }); }); diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-dark.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-dark.ico new file mode 100644 index 0000000000..3f1b42e01e --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-dark.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d5b1e033606792fc68dd5926aad550f2a90875a07f83908995102b365d1c59 +size 13604 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-dark.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-dark.ico new file mode 100644 index 0000000000..43eeb79c9e --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-dark.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b8d97f4060f7cc9a667086be373d652b122d7e48f81f84338ac4724051f9672 +size 14432 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-light.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-light.ico new file mode 100644 index 0000000000..bcc04d1cc3 --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-highcontrast-light.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e477bba3273bb749d1fb83ce5681bdb211678e78c5487aa898cde7f5003953b +size 14870 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-light.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-light.ico new file mode 100644 index 0000000000..b0af99201d --- /dev/null +++ b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-light.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad477a98060bb458064831cfd0736eec5c6be44fd2b39a3942dda4a012108245 +size 14013 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico deleted file mode 100644 index b4b4f7f1f2..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0204085508c319064f0b1e063210076b22eef75aa8d7343f24f2684ca60c796a -size 55906 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico deleted file mode 100644 index 1d3e4a8c6c..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:344516dae5330d2629b3bb7cbcaa1182f5801edba312d7084748ead713ab661c -size 55731 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico deleted file mode 100644 index 23a962c003..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54f7ab9374a30d6bca15400435b85fda018521becf117cf05ec6e91c411d5268 -size 54220 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico deleted file mode 100644 index a9ce58ecaa..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5da0298a0c0a02447a2b8f60b01ec99c0241cc459acd8e6581d52e21d08354a8 -size 59093 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico deleted file mode 100644 index 02233d85cc..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ea08026b08b4c29653c1b9a61dc39de39c0740a08e7091dd9bdba9152738579 -size 58845 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico deleted file mode 100644 index 96c4befac6..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6bb3357f30b48265c2c0c642725baceb890ab7941c8598a45d99777ff81cd7b3 -size 57423 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico deleted file mode 100644 index f1c8a02f97..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fd684f0f4e0bd4a127fb82e1fd5a70f8a7e2883ddb9f4407c1c71867b72721d -size 56426 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico deleted file mode 100644 index 67b09f7fbf..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b472db916aa96a42393a983d9a401756a2d26ed4b3807186cc6b4aab0324d42 -size 56073 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico deleted file mode 100644 index 4f7ed9d680..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9ed47871069f402a9cedeb2f4a7af11c6666538f4e7c52b03cbcf568c7e0294 -size 54728 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico deleted file mode 100644 index 726a1ee458..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c0ec38457f0c5f8af3dba342cc78275df83f0e4bd349b5b2860940a0c4a8fa5 -size 61009 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico deleted file mode 100644 index e6ab06fff1..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2b69676533b89e3aac31161ae17638398970ad5e9dab70b61e3d64262140129 -size 60774 diff --git a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico deleted file mode 100644 index 6aa284907a..0000000000 --- a/packages/stacks-classic/screenshots/Chromium/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c12e81ec82af19a79eb7983ce4bc0f44b90feb4bfeae353efc205539e315136d -size 59289 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-dark.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-dark.ico new file mode 100644 index 0000000000..bdba2e251e --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-dark.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35a37e3f07e923aaae170984cb213fb71f449240672c24a5d910866f0b5761a3 +size 14219 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-dark.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-dark.ico new file mode 100644 index 0000000000..bab3fcaa70 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-dark.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78b4f6d46fdf7a0fb82e97c3a1d5481fb80673a96afa558c5a04214222813864 +size 15759 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-light.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-light.ico new file mode 100644 index 0000000000..3ea65b5985 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-highcontrast-light.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:882bd62dc12024249b2ec925f05ae73036b6a2bdf15b52b3dc489d939483412b +size 16503 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-light.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-light.ico new file mode 100644 index 0000000000..6026559325 --- /dev/null +++ b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-light.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:391a9df70c6b1bd980704a15e3cf81d4ff2d0a1d46a86325b7cddc97fdc68348 +size 15055 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico deleted file mode 100644 index 3603edf2ed..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1bc615cab0e8bbbb0427cc769aab3d939cc0df29b578db316abdfc035642250 -size 60986 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico deleted file mode 100644 index fea6d12144..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7cbb205d62b22737fb4762ec2029d0c6ee897b2b115394ab971a689faccb3c85 -size 60752 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico deleted file mode 100644 index 700f18671a..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3f68de90fc34775c329c8bf21cf989f9a167c4b8557a5ac89dc1baca19143540 -size 59417 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico deleted file mode 100644 index 48eef942ac..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:237a16d9570d500b14c2b725a384f72f0e9efede52e379c3a89fe9c54fde369f -size 65993 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico deleted file mode 100644 index cc8eccc094..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a165f1efe5db52d911707790e6ed5513984645c804efcc38e88858aa08f75bb -size 65793 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico deleted file mode 100644 index a168ed603a..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:46349aaf731de7a92bcaa368610b8692566d9348630c8ea341ad2b3dc6891ba0 -size 64398 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico deleted file mode 100644 index 6304be72a2..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:daf56528ab4bcad21e9a6e74fa8749817c5f8001eb9befdd2f4727470f0510f0 -size 63041 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico deleted file mode 100644 index ac11d62c75..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a924fa9ed3ba23a0a5be897e6e6b1aa181d94b9ab2057df5827a3d1c65338edb -size 62754 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico deleted file mode 100644 index 1ffce60b9f..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45d8b9df49a9dcc387e8f5ab0e8313899426fbd58fc530005e07eb223b90527f -size 61389 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico deleted file mode 100644 index 96aebb0189..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c194bf7c8bf34783de5581b2a1f441218225e4a57dbd7a5b675fd83f63a23cb -size 68614 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico deleted file mode 100644 index a73eebb085..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aec8f32dbccebe34b87e4482ad8ecf89ae70f0b72a9ff44520e67c5d2f4e0b73 -size 68300 diff --git a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico deleted file mode 100644 index 3dfb688ed4..0000000000 --- a/packages/stacks-classic/screenshots/Firefox/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ed82358bebb0aaa70c632389ae497c88ffd0617c26df721e5ffe60000b119f6 -size 66995 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-dark.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-dark.ico new file mode 100644 index 0000000000..e7941bcd94 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-dark.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91444ab40822a50307a85d3e292163df451182c152303c59562127aea8bb348f +size 15860 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-dark.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-dark.ico new file mode 100644 index 0000000000..5396a91d30 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-dark.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4897dfcab3f030483cb586c2fa4ec4cda66e7e6504ff1dc29011a7dbfcf94e10 +size 17166 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-light.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-light.ico new file mode 100644 index 0000000000..e9993fb611 --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-highcontrast-light.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:069883d8428d8be2a11d8a30503c8fdf4f788dddef0e2be66ce511385c7a4115 +size 17184 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-light.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-light.ico new file mode 100644 index 0000000000..c73332866f --- /dev/null +++ b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-light.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dcb243a5d30026c0bbac8ffe12fe67ad0454c85720cb489f781f40591ad3e8a +size 15769 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico deleted file mode 100644 index d586459de0..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b12bfb4c3b4037df3c185d5db1a045fb9cda4e2ce19dcbdfb0d95c6f76dc45d3 -size 67695 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico deleted file mode 100644 index 2c516da077..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:59bcac92cadd75a235cd5e18df67b99f03f31318456b3e7e040c5dc2251f5b56 -size 67142 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico deleted file mode 100644 index 6f57710862..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29a155bf2ce3231d478fc63158a9bc125b9872cf704101eabcb0520cfd6708d6 -size 65949 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico deleted file mode 100644 index a47542ab4b..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fb69b0433e8a3da911b72705f9d668e0e459c9bced9c98ec565241cac25dc15 -size 75576 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico deleted file mode 100644 index c421a3eed6..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6099dfbc442c652f36c3b6c9be7520d176d31f5b85701865df5f718bd5f64cf -size 75044 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico deleted file mode 100644 index 1db84da411..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-dark-theme-highcontrast-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41e63756c33051b32209d8823ab49d2f1572af275e4830402980af651f009bf8 -size 73783 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico deleted file mode 100644 index 412cb208e7..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a46f4b13de18103b79def6f2f0dd29603676e4b65caf65bded3014f335ee6c3d -size 66491 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico deleted file mode 100644 index 83a31662cb..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96062b2234e9a27b23841a759dc0583c9197cb76030fedc8aefa28515dfb5d5d -size 65970 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico deleted file mode 100644 index 3c6b2e7a4c..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:232278c75ba84abc88e3c550e6a90c99dce247cdd890e777995a1a798aa46aa7 -size 64713 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico deleted file mode 100644 index 093a990745..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-focus.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a205fbfab484c30ff7b4fc1d118e42565d6e3a5d688b6c954f2894c27c89250 -size 75421 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico deleted file mode 100644 index d88af6ea3d..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-hover-radio.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6deffd296649e983a2bc8184dd96e81d9d8c346c7db97a6b6a89a17ee853405 -size 74872 diff --git a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico b/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico deleted file mode 100644 index 38bceac3bc..0000000000 --- a/packages/stacks-classic/screenshots/Webkit/baseline/s-btn-selected-theme-light-theme-highcontrast-rest.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36b31e94098d40c33ab579814f8d52f78b72fc5f793867bf605232c2f3512cf9 -size 73587