Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions src/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,41 @@
}
}

&:not(.indeterminate):has(input[type='checkbox']:checked) {
/*
* Safari (macOS & iOS, tested on Safari 26) & probably even earlier versions have
* a rendering bug where transitions on descendants whose end state is triggered
* ONLY via a parent selector using `:has()` may not animate. Instead, Safari
* sometimes jumps directly to the final state (or never paints the transition)
* until a subsequent layout invalidation (e.g. tab switch, resize) happens.
*
* Workaround: provide an equivalent selector that does NOT rely on `:has()`,
* using the adjacency between the input and the visual box. This ensures the
* `stroke-dashoffset` transition for the check mark runs reliably in Safari
* while keeping the simpler `:has()` version commented for future re-implementation
* or cleanup.
*
* &:not(.indeterminate):has(input[type='checkbox']:checked) {
* svg.check-mark {
* opacity: 1;
* path {
* stroke-dashoffset: 0;
* }
* }
* }
* Using the `:has()` selector is more reliable, because it doesn't
* depend on the DOM structure (e.g. if the markup changes and the input is
* no longer adjacent to the box), but Safari support for `:has()` is still
* somewhat inconsistent.
*/

&:not(.indeterminate)
> input[type='checkbox']:checked
+ .box
svg.check-mark {
opacity: 1;
opacity: 1;

path {
stroke-dashoffset: 0;
}
path {
stroke-dashoffset: 0;
}
}
}
Expand Down
52 changes: 44 additions & 8 deletions src/style/internal/boolean-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,31 @@ label.boolean-input-label {
rgb(var(--contrast-300))
);

/*
* NOTE: Original selectors using `:has()` are commented out due to Safari
* rendering bugs where descendant transitions (e.g. SVG stroke animations)
* or box/background updates sometimes fail to animate or even repaint
* reliably when the state change is detected only via `:has()`.
*
* Original (kept for future re-implementation, or cleanup):
* .boolean-input:has(input[type='checkbox']:checked) &,
* .boolean-input:has(input[type='radio']:checked) & {
* ...
* }
*
* Replacement uses adjacency: the markup places the `<input>` immediately
* before .box, so we can select the checked state with
* input:checked + .box. We retain the explicit `.checked` class pathway in
* case some templates toggle that class manually.
*
* Using the `:has()` selector is more reliable, because it doesn't
* depend on the DOM structure (e.g. if the markup changes and the input is
* no longer adjacent to the box), but Safari support for `:has()` is still
* somewhat inconsistent.
*/
.checked &,
.boolean-input:has(input[type='checkbox']:checked) &,
.boolean-input:has(input[type='radio']:checked) & {
.boolean-input > input[type='checkbox']:checked + &,
.boolean-input > input[type='radio']:checked + & {
background-color: var(
--lime-primary-color,
var(--limel-theme-primary-color)
Expand All @@ -127,12 +149,19 @@ label.boolean-input-label {
opacity: 0.4;
}

.boolean-input:not(.disabled):has(label.boolean-input-label:hover) & {
/*
* See previous comment about Safari rendering bugs ☝️.
*
* Original (kept for for future re-implementation, or cleanup):
* .boolean-input:not(.disabled):has(label.boolean-input-label:hover) & { ... }
* .boolean-input:not(.disabled):has(label.boolean-input-label:active) & { ... }
*/
.boolean-input:not(.disabled):hover & {
will-change: box-shadow;
box-shadow: var(--button-shadow-hovered);
}

.boolean-input:not(.disabled):has(label.boolean-input-label:active) & {
.boolean-input:not(.disabled):active & {
will-change: box-shadow;
box-shadow: var(--button-shadow-pressed);
}
Expand All @@ -145,10 +174,16 @@ label.boolean-input-label {
inset: -0.1875rem; // 3px
border-radius: inherit;

.boolean-input:has(input[type='checkbox']:focus-visible) &,
.boolean-input:has(input[type='radio']:focus-visible) & {
/*
* See previous comment about Safari rendering bugs ☝️.
*
* Original (kept for for future re-implementation, or cleanup):
* .boolean-input:has(input[type='checkbox']:focus-visible) &,
* .boolean-input:has(input[type='radio']:focus-visible) & { ...}
*/
.boolean-input > input[type='checkbox']:focus-visible + &,
.boolean-input > input[type='radio']:focus-visible + & {
will-change: box-shadow;

box-shadow: var(--shadow-depth-8-focused);
}
}
Expand All @@ -171,7 +206,8 @@ label.boolean-input-label {

background-color: rgb(var(--color-white));

.boolean-input:not(.disabled):has(label.boolean-input-label:hover) & {
/* Hover fallback for pseudo-element (see explanation above). */
.boolean-input:not(.disabled):hover & {
will-change: opacity, box-shadow, transform, width;
}
}
Expand Down