diff --git a/.changeset/focus-ring-mixin.md b/.changeset/focus-ring-mixin.md new file mode 100644 index 000000000..86cc610ec --- /dev/null +++ b/.changeset/focus-ring-mixin.md @@ -0,0 +1,5 @@ +--- +"@tabler/core": patch +--- + +Updated `focus-ring()` to be the single focus-indicator mixin; the `$*-focus-box-shadow` variables stay as aliases. diff --git a/BOOTSTRAP-V6-MIGRATION.md b/BOOTSTRAP-V6-MIGRATION.md index 3e6d81dff..32534fe3e 100644 --- a/BOOTSTRAP-V6-MIGRATION.md +++ b/BOOTSTRAP-V6-MIGRATION.md @@ -253,6 +253,12 @@ of `color-scheme`, the token-dump verification harness) but not its code. - Decide on `$spacers`: adopting the v6 0–9 scale silently changes every `.p-3` / `.m-4` in every Tabler template. Recommendation: keep the v5 scale, add finer steps at new keys. - Replace all `$*-focus-box-shadow` with one `focus-ring()` mixin over `--focus-ring*` tokens. + Step 1 done (#2972): `focus-ring()` reads `$focus-ring-box-shadow` and every focus consumer calls + it; the `$*-focus-box-shadow` variables stay as `!default` aliases; CSS byte-identical. Tabler + keeps the two-layer `box-shadow` look, not v6's `outline`, and the `forced-colors` fallback in + `layout/_accessibility.scss` stays. `.form-select` is still a single-layer exception. Step 2 + (emit `--focus-ring-box-shadow` on `:root`, fix the `.focus-ring` helper, decide the + `.form-select` ring) is a follow-up. - Consider the layered shadow scale (`.shadow-xs`…`.shadow-xl`) and `.border-keyline`. - Skip the `.fs-*` renaming — it inverts the scale direction and breaks every page. diff --git a/core/scss/bootstrap/_nav.scss b/core/scss/bootstrap/_nav.scss index 43b576a5d..b0ab8f98c 100644 --- a/core/scss/bootstrap/_nav.scss +++ b/core/scss/bootstrap/_nav.scss @@ -41,8 +41,7 @@ } &:focus-visible { - outline: 0; - box-shadow: $nav-link-focus-box-shadow; + @include focus-ring($shadow: $nav-link-focus-box-shadow); } // Disabled state lightens text diff --git a/core/scss/bootstrap/forms/_form-check.scss b/core/scss/bootstrap/forms/_form-check.scss index 04e6e12a4..62f774189 100644 --- a/core/scss/bootstrap/forms/_form-check.scss +++ b/core/scss/bootstrap/forms/_form-check.scss @@ -61,8 +61,7 @@ &:focus { border-color: $form-check-input-focus-border; - outline: 0; - box-shadow: $form-check-input-focus-box-shadow; + @include focus-ring($shadow: $form-check-input-focus-box-shadow); } &:checked { diff --git a/core/scss/bootstrap/forms/_form-range.scss b/core/scss/bootstrap/forms/_form-range.scss index 63f7e0091..cae7ae3e1 100644 --- a/core/scss/bootstrap/forms/_form-range.scss +++ b/core/scss/bootstrap/forms/_form-range.scss @@ -19,10 +19,10 @@ // Pseudo-elements must be split across multiple rulesets to have an effect. // No box-shadow() mixin for focus accessibility. &::-webkit-slider-thumb { - box-shadow: $form-range-thumb-focus-box-shadow; + @include focus-ring($shadow: $form-range-thumb-focus-box-shadow, $outline: null); } &::-moz-range-thumb { - box-shadow: $form-range-thumb-focus-box-shadow; + @include focus-ring($shadow: $form-range-thumb-focus-box-shadow, $outline: null); } } diff --git a/core/scss/mixins/_mixins.scss b/core/scss/mixins/_mixins.scss index 16fcf7337..01a52ed33 100644 --- a/core/scss/mixins/_mixins.scss +++ b/core/scss/mixins/_mixins.scss @@ -77,14 +77,15 @@ gap: var(--list-gap); } -@mixin focus-ring($show-border: false) { - outline: 0; - box-shadow: - 0 0 0 2px var(--primary), - 0 0 $focus-ring-blur $focus-ring-width color-transparent(var(--primary), 0.25); +@mixin focus-ring($shadow: $focus-ring-box-shadow, $show-border: false, $outline: 0) { + @if $outline != null { + outline: $outline; + } - @if ($show-border) { - border-color: color-transparent(var(--primary), 0.25); + box-shadow: $shadow; + + @if $show-border { + border-color: $focus-ring-color; } } diff --git a/core/scss/mixins/bootstrap/_forms.scss b/core/scss/mixins/bootstrap/_forms.scss index 7955b143e..74e4d61b4 100644 --- a/core/scss/mixins/bootstrap/_forms.scss +++ b/core/scss/mixins/bootstrap/_forms.scss @@ -4,6 +4,7 @@ @use '../../settings' as *; @use '../../variables' as *; @use '../functions' as *; +@use '../mixins' as *; @use 'breakpoints' as *; // This mixin uses an `if()` technique to be compatible with Dart Sass // See https://github.com/sass/sass/issues/1873#issuecomment-152293725 for more details @@ -70,7 +71,7 @@ @if $enable-shadows { @include box-shadow($input-box-shadow, $focus-box-shadow); } @else { - box-shadow: $focus-box-shadow; + @include focus-ring($shadow: $focus-box-shadow, $outline: null); } } } @@ -105,7 +106,7 @@ @if $enable-shadows { @include box-shadow($form-select-box-shadow, $focus-box-shadow); } @else { - box-shadow: $focus-box-shadow; + @include focus-ring($shadow: $focus-box-shadow, $outline: null); } } } @@ -128,7 +129,7 @@ } &:focus { - box-shadow: $focus-box-shadow; + @include focus-ring($shadow: $focus-box-shadow, $outline: null); } ~ .form-check-label { diff --git a/core/scss/tests/_mixins.test.scss b/core/scss/tests/_mixins.test.scss index 7770b9c8c..bdc66816b 100644 --- a/core/scss/tests/_mixins.test.scss +++ b/core/scss/tests/_mixins.test.scss @@ -140,6 +140,21 @@ } } } + + @include it('passes $shadow through and skips the outline reset with $outline: null') { + @include assert() { + @include output() { + .t { + @include focus-ring($shadow: 0 0 0 1px red, $outline: null); + } + } + @include expect() { + .t { + box-shadow: 0 0 0 1px red; + } + } + } + } } @include describe('media-print mixin') { diff --git a/core/scss/ui/_forms.scss b/core/scss/ui/_forms.scss index 60e52bed9..aa593bff1 100644 --- a/core/scss/ui/_forms.scss +++ b/core/scss/ui/_forms.scss @@ -207,7 +207,7 @@ Input group .input-group-flat { &:focus-within { - box-shadow: $input-focus-box-shadow; + @include focus-ring($shadow: $input-focus-box-shadow, $outline: null); @include border-radius($input-border-radius); .form-control, diff --git a/core/scss/ui/forms/_form-colorinput.scss b/core/scss/ui/forms/_form-colorinput.scss index e8f94dcea..b75c36107 100644 --- a/core/scss/ui/forms/_form-colorinput.scss +++ b/core/scss/ui/forms/_form-colorinput.scss @@ -46,7 +46,7 @@ Color Input .form-colorinput-input:focus ~ & { border-color: var(--primary); - box-shadow: $input-btn-focus-box-shadow; + @include focus-ring($shadow: $input-btn-focus-box-shadow, $outline: null); } @at-root .form-colorinput-light & { diff --git a/core/scss/ui/forms/_form-imagecheck.scss b/core/scss/ui/forms/_form-imagecheck.scss index ece924180..fa0403811 100644 --- a/core/scss/ui/forms/_form-imagecheck.scss +++ b/core/scss/ui/forms/_form-imagecheck.scss @@ -28,7 +28,7 @@ Image check .form-imagecheck-input:focus ~ & { border-color: var(--primary); - box-shadow: $input-btn-focus-box-shadow; + @include focus-ring($shadow: $input-btn-focus-box-shadow, $outline: null); } .form-imagecheck-input:checked ~ & { diff --git a/core/scss/ui/forms/_form-selectgroup.scss b/core/scss/ui/forms/_form-selectgroup.scss index a789db0d9..f47ed2f4c 100644 --- a/core/scss/ui/forms/_form-selectgroup.scss +++ b/core/scss/ui/forms/_form-selectgroup.scss @@ -111,7 +111,7 @@ Select group z-index: 2; color: var(--primary); border-color: var(--primary); - box-shadow: $input-btn-focus-box-shadow; + @include focus-ring($shadow: $input-btn-focus-box-shadow, $outline: null); } /** diff --git a/core/scss/vendor/_coloris.scss b/core/scss/vendor/_coloris.scss index 396dbb9c3..6ad16dce9 100644 --- a/core/scss/vendor/_coloris.scss +++ b/core/scss/vendor/_coloris.scss @@ -17,7 +17,7 @@ input.clr-color { border-color: var(--border-color); &:focus { - @include focus-ring(true); + @include focus-ring($show-border: true); } } diff --git a/core/scss/vendor/_nouislider.scss b/core/scss/vendor/_nouislider.scss index cc2531598..b68d36686 100644 --- a/core/scss/vendor/_nouislider.scss +++ b/core/scss/vendor/_nouislider.scss @@ -43,7 +43,7 @@ &.noUi-active, &:focus { - box-shadow: $form-range-thumb-focus-box-shadow; + @include focus-ring($shadow: $form-range-thumb-focus-box-shadow, $outline: null); } }