Skip to content

fix(switch): active down state specificity #3767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 2, 2025
Merged
5 changes: 5 additions & 0 deletions .changeset/fluffy-hands-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@spectrum-css/switch": patch
---

Fix active down state specificity by increasing the specificity of the active state selector. This change was necessary because the `postcss-hover-media-feature` plugin converts hover styles into a media query for devices that support hover. Without this adjustment, the hover styles could override the active down state styles due to the way CSS specificity and order are handled. By adding the additional `.spectrum-Switch` class in the active state selector, we ensure that the active state takes precedence over the hover state, maintaining the correct visual behavior of the switch component across different interaction states.
14 changes: 11 additions & 3 deletions components/switch/dist/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
".spectrum-Switch .spectrum-Switch-input[disabled] ~ .spectrum-Switch-label",
".spectrum-Switch .spectrum-Switch-input[disabled]:checked + .spectrum-Switch-switch",
".spectrum-Switch .spectrum-Switch-input[disabled]:checked + .spectrum-Switch-switch:before",
".spectrum-Switch--active .spectrum-Switch-input:checked + .spectrum-Switch-switch",
".spectrum-Switch--active .spectrum-Switch-input:checked + .spectrum-Switch-switch:before",
".spectrum-Switch--active .spectrum-Switch-input:not(:disabled) + .spectrum-Switch-switch:before",
".spectrum-Switch--active .spectrum-Switch-input:not(:disabled):checked + .spectrum-Switch-switch:before",
".spectrum-Switch--active .spectrum-Switch-input:not(:disabled):checked + .spectrum-Switch-switch:before:dir(rtl)",
".spectrum-Switch--disabled",
".spectrum-Switch--sizeL",
".spectrum-Switch--sizeS",
Expand All @@ -32,15 +37,17 @@
".spectrum-Switch-label:lang(ko)",
".spectrum-Switch-label:lang(zh)",
".spectrum-Switch-switch",
".spectrum-Switch-switch .spectrum-Switch--active",
".spectrum-Switch-switch:after",
".spectrum-Switch-switch:before",
".spectrum-Switch.spectrum-Switch--emphasized",
".spectrum-Switch:active .spectrum-Switch-input:checked + .spectrum-Switch-switch",
".spectrum-Switch.spectrum-Switch:active .spectrum-Switch-input:checked + .spectrum-Switch-switch",
".spectrum-Switch.spectrum-Switch:active .spectrum-Switch-input:checked + .spectrum-Switch-switch:before",
".spectrum-Switch.spectrum-Switch:active .spectrum-Switch-switch",
".spectrum-Switch.spectrum-Switch:active .spectrum-Switch-switch:before",
".spectrum-Switch:active .spectrum-Switch-input:not(:disabled) + .spectrum-Switch-switch:before",
".spectrum-Switch:active .spectrum-Switch-input:not(:disabled):checked + .spectrum-Switch-switch:before",
".spectrum-Switch:active .spectrum-Switch-input:not(:disabled):checked + .spectrum-Switch-switch:before:dir(rtl)",
".spectrum-Switch:active .spectrum-Switch-switch",
".spectrum-Switch:active .spectrum-Switch-switch:before",
".spectrum-Switch:active ~ .spectrum-Switch-label",
".spectrum-Switch:hover .spectrum-Switch-input:checked + .spectrum-Switch-switch",
".spectrum-Switch:hover .spectrum-Switch-input:checked + .spectrum-Switch-switch:before",
Expand All @@ -57,6 +64,7 @@
".spectrum-Switch:hover .spectrum-Switch-switch",
".spectrum-Switch:hover .spectrum-Switch-switch:before",
".spectrum-Switch:hover ~ .spectrum-Switch-label",
"[dir=\"rtl\"] .spectrum-Switch--active .spectrum-Switch-input:not(:disabled):checked + .spectrum-Switch-switch:before",
"[dir=\"rtl\"] .spectrum-Switch-input:checked + .spectrum-Switch-switch:before",
"[dir=\"rtl\"] .spectrum-Switch:active .spectrum-Switch-input:not(:disabled):checked + .spectrum-Switch-switch:before"
],
Expand Down
20 changes: 15 additions & 5 deletions components/switch/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@
border-color: var(--highcontrast-switch-border-color-hover, var(--mod-switch-border-color-hover, var(--spectrum-switch-border-color-hover)));
}

.spectrum-Switch:active & {
.spectrum-Switch.spectrum-Switch:active &,
.spectrum-Switch--active {
border-color: var(--highcontrast-switch-border-color-down, var(--mod-switch-border-color-down, var(--spectrum-switch-border-color-down)));
}

Expand All @@ -246,7 +247,9 @@
border-color: var(--highcontrast-switch-border-color-selected-hover, var(--mod-switch-border-color-selected-hover, var(--spectrum-switch-border-color-selected-hover)));
}

.spectrum-Switch:active & {
/* Added specifity to ensure active state takes precedence over hover media queries */
.spectrum-Switch.spectrum-Switch:active &,
.spectrum-Switch--active & {
background-color: var(--highcontrast-switch-background-color-selected-down, var(--mod-switch-background-color-selected-down, var(--spectrum-switch-background-color-selected-down)));
border-color: var(--highcontrast-switch-border-color-selected-down, var(--mod-switch-border-color-selected-down, var(--spectrum-switch-border-color-selected-down)));
}
Expand Down Expand Up @@ -283,7 +286,7 @@
background-color: var(--highcontrast-switch-handle-background-color-hover, var(--mod-switch-handle-background-color-hover, var(--spectrum-switch-handle-background-color-hover)));
}

.spectrum-Switch:active & {
.spectrum-Switch.spectrum-Switch:active & {
background-color: var(--highcontrast-switch-handle-background-color-down, var(--mod-switch-handle-background-color-down, var(--spectrum-switch-handle-background-color-down)));
}

Expand All @@ -310,13 +313,20 @@
.spectrum-Switch:hover & {
background-color: var(--highcontrast-switch-handle-background-color-selected, var(--mod-switch-handle-background-color-selected, var(--spectrum-switch-handle-background-color-selected)));
}

.spectrum-Switch.spectrum-Switch:active &,
.spectrum-Switch--active & {
background-color: var(--spectrum-switch-handle-background-color-selected, var(--mod-switch-handle-background-color-selected, var(--spectrum-switch-handle-background-color-selected)));
}
}

.spectrum-Switch:active .spectrum-Switch-input:not(:disabled) + & {
.spectrum-Switch:active .spectrum-Switch-input:not(:disabled) + &,
.spectrum-Switch--active .spectrum-Switch-input:not(:disabled) + & {
transform: perspective(var(--spectrum-component-size-minimum-perspective-down)) translateZ(var(--spectrum-component-size-difference-down));
}

.spectrum-Switch:active .spectrum-Switch-input:not(:disabled):checked + & {
.spectrum-Switch:active .spectrum-Switch-input:not(:disabled):checked + &,
.spectrum-Switch--active .spectrum-Switch-input:not(:disabled):checked + & {
/* Add down state without overriding translateX */
transform: translateX(calc(100% + (var(--mod-switch-border-width, var(--spectrum-switch-border-width)) * 0.25))) perspective(var(--spectrum-component-size-minimum-perspective-down)) translateZ(var(--spectrum-component-size-difference-down));

Expand Down
4 changes: 3 additions & 1 deletion components/switch/stories/switch.stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Sizes } from "@spectrum-css/preview/decorators";
import { disableDefaultModes } from "@spectrum-css/preview/modes";
import { isChecked, isDisabled, isEmphasized, size } from "@spectrum-css/preview/types";
import { isActive, isChecked, isDisabled, isEmphasized, size } from "@spectrum-css/preview/types";
import metadata from "../dist/metadata.json";
import packageJson from "../package.json";
import { SwitchGroup } from "./switch.test.js";
Expand All @@ -16,6 +16,7 @@ export default {
size: size(["s", "m", "l", "xl"]),
isEmphasized,
isDisabled,
isActive,
isChecked: {
...isChecked,
name: "Selected",
Expand All @@ -35,6 +36,7 @@ export default {
isDisabled: false,
isChecked: false,
isEmphasized: false,
isActive: false,
label: "Switch label",
size: "m",
},
Expand Down
9 changes: 9 additions & 0 deletions components/switch/stories/switch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ export const SwitchGroup = Variants({
testHeading: "Checked",
isChecked: true,
},
{
testHeading: "Checked + Active",
isChecked: true,
isActive: true,
},
{
testHeading: "UnChecked + Active",
isActive: true
},
{
testHeading: "Checked and disabled",
isChecked: true,
Expand Down
2 changes: 2 additions & 0 deletions components/switch/stories/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const Template = ({
label = "Switch label",
isDisabled = false,
isChecked = false,
isActive = false,
isEmphasized = false,
customClasses = [],
customStyles = {},
Expand All @@ -29,6 +30,7 @@ export const Template = ({
[`${rootClass}--emphasized`] : isEmphasized,
[`${rootClass}--size${size?.toUpperCase()}`]:
typeof size !== "undefined",
[`${rootClass}--active`]: isActive,
...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}),
})}
id=${ifDefined(id)}
Expand Down
Loading