diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json index 23a003679e..94396f15c7 100644 --- a/.bundlewatch.config.json +++ b/.bundlewatch.config.json @@ -18,11 +18,11 @@ }, { "path": "./dist/css/bootstrap-utilities.css", - "maxSize": "10.5 kB" + "maxSize": "10.75 kB" }, { "path": "./dist/css/bootstrap-utilities.min.css", - "maxSize": "9.75 kB" + "maxSize": "10.0 kB" }, { "path": "./dist/css/bootstrap.css", @@ -30,7 +30,7 @@ }, { "path": "./dist/css/bootstrap.min.css", - "maxSize": "29.25 kB" + "maxSize": "29.5 kB" }, { "path": "./dist/js/bootstrap.bundle.js", diff --git a/scss/_helpers.scss b/scss/_helpers.scss index 644b693fbc..6126781cde 100644 --- a/scss/_helpers.scss +++ b/scss/_helpers.scss @@ -1,6 +1,7 @@ @import "helpers/clearfix"; @import "helpers/color-bg"; @import "helpers/colored-links"; +@import "helpers/focus-ring"; @import "helpers/ratio"; @import "helpers/position"; @import "helpers/stacks"; diff --git a/scss/_nav.scss b/scss/_nav.scss index 1b3cf082f0..3a27ee7925 100644 --- a/scss/_nav.scss +++ b/scss/_nav.scss @@ -38,6 +38,11 @@ text-decoration: if($link-hover-decoration == underline, none, null); } + &:focus { + outline: 0; + box-shadow: $nav-link-focus-box-shadow; + } + // Disabled state lightens text &.disabled { color: var(--#{$prefix}nav-link-disabled-color); diff --git a/scss/_root.scss b/scss/_root.scss index 45de310906..323a5217bf 100644 --- a/scss/_root.scss +++ b/scss/_root.scss @@ -127,6 +127,15 @@ @each $name, $value in $grid-breakpoints { --#{$prefix}breakpoint-#{$name}: #{$value}; } + + // Focus styles + // scss-docs-start root-focus-variables + --#{$prefix}focus-ring-width: #{$focus-ring-width}; + --#{$prefix}focus-ring-opacity: #{$focus-ring-opacity}; + --#{$prefix}focus-ring-color: #{$focus-ring-color}; + // By default, there is no `--bs-focus-ring-x`, `--bs-focus-ring-y`, or `--bs-focus-ring-blur`, but we provide CSS variables with fallbacks to initial `0` values + --#{$prefix}focus-ring-box-shadow: var(--#{$prefix}focus-ring-x, 0) var(--#{$prefix}focus-ring-y, 0) var(--#{$prefix}focus-ring-blur, 0) var(--#{$prefix}focus-ring-width) var(--#{$prefix}focus-ring-color); + // scss-docs-end root-focus-variables } @if $enable-dark-mode { diff --git a/scss/_utilities.scss b/scss/_utilities.scss index 4c2c39dd76..5134688e90 100644 --- a/scss/_utilities.scss +++ b/scss/_utilities.scss @@ -84,6 +84,14 @@ $utilities: map-merge( ) ), // scss-docs-end utils-shadow + // scss-docs-start utils-focus-ring + "focus-ring": ( + css-var: true, + css-variable-name: focus-ring-color, + class: focus-ring, + values: map-loop($theme-colors-rgb, rgba-css-var, "$key", "focus-ring") + ), + // scss-docs-end utils-focus-ring // scss-docs-start utils-position "position": ( property: position, diff --git a/scss/_variables.scss b/scss/_variables.scss index 24a112a12a..c220140157 100644 --- a/scss/_variables.scss +++ b/scss/_variables.scss @@ -549,6 +549,14 @@ $box-shadow-inset: inset 0 1px 2px rgba(var(--#{$prefix}body-color-rg $component-active-color: $white !default; $component-active-bg: $primary !default; +// scss-docs-start focus-ring-variables +$focus-ring-width: .25rem !default; +$focus-ring-opacity: .25 !default; +$focus-ring-color: rgba($primary, $focus-ring-opacity) !default; +$focus-ring-blur: 0 !default; +$focus-ring-box-shadow: 0 0 $focus-ring-blur $focus-ring-width $focus-ring-color !default; +// scss-docs-end focus-ring-variables + // scss-docs-start caret-variables $caret-width: .3em !default; $caret-vertical-align: $caret-width * .85 !default; @@ -761,11 +769,11 @@ $input-btn-font-family: null !default; $input-btn-font-size: $font-size-base !default; $input-btn-line-height: $line-height-base !default; -$input-btn-focus-width: .25rem !default; -$input-btn-focus-color-opacity: .25 !default; -$input-btn-focus-color: rgba($component-active-bg, $input-btn-focus-color-opacity) !default; -$input-btn-focus-blur: 0 !default; -$input-btn-focus-box-shadow: 0 0 $input-btn-focus-blur $input-btn-focus-width $input-btn-focus-color !default; +$input-btn-focus-width: $focus-ring-width !default; +$input-btn-focus-color-opacity: $focus-ring-opacity !default; +$input-btn-focus-color: $focus-ring-color !default; +$input-btn-focus-blur: $focus-ring-blur !default; +$input-btn-focus-box-shadow: $focus-ring-box-shadow !default; $input-btn-padding-y-sm: .25rem !default; $input-btn-padding-x-sm: .5rem !default; @@ -918,7 +926,7 @@ $form-check-input-border: var(--#{$prefix}border-width) solid va $form-check-input-border-radius: .25em !default; $form-check-radio-border-radius: 50% !default; $form-check-input-focus-border: $input-focus-border-color !default; -$form-check-input-focus-box-shadow: $input-btn-focus-box-shadow !default; +$form-check-input-focus-box-shadow: $focus-ring-box-shadow !default; $form-check-input-checked-color: $component-active-color !default; $form-check-input-checked-bg-color: $component-active-bg !default; @@ -1124,6 +1132,8 @@ $nav-link-color: var(--#{$prefix}link-color) !default; $nav-link-hover-color: var(--#{$prefix}link-hover-color) !default; $nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default; $nav-link-disabled-color: var(--#{$prefix}secondary-color) !default; +$nav-link-disabled-color: $gray-600 !default; +$nav-link-focus-box-shadow: $focus-ring-box-shadow !default; $nav-tabs-border-color: var(--#{$prefix}border-color) !default; $nav-tabs-border-width: var(--#{$prefix}border-width) !default; @@ -1265,7 +1275,7 @@ $pagination-border-color: var(--#{$prefix}border-color) !default; $pagination-focus-color: var(--#{$prefix}link-hover-color) !default; $pagination-focus-bg: var(--#{$prefix}secondary-bg) !default; -$pagination-focus-box-shadow: $input-btn-focus-box-shadow !default; +$pagination-focus-box-shadow: $focus-ring-box-shadow !default; $pagination-focus-outline: 0 !default; $pagination-hover-color: var(--#{$prefix}link-hover-color) !default; @@ -1665,8 +1675,9 @@ $btn-close-height: $btn-close-width !default; $btn-close-padding-x: .25em !default; $btn-close-padding-y: $btn-close-padding-x !default; $btn-close-color: $black !default; -$btn-close-bg: url("data:image/svg+xml,") !default; $btn-close-focus-shadow: $input-btn-focus-box-shadow !default; +$btn-close-bg: url("data:image/svg+xml,") !default; +$btn-close-focus-shadow: $focus-ring-box-shadow !default; $btn-close-opacity: .5 !default; $btn-close-hover-opacity: .75 !default; $btn-close-focus-opacity: 1 !default; diff --git a/scss/helpers/_focus-ring.scss b/scss/helpers/_focus-ring.scss new file mode 100644 index 0000000000..26508a8d6d --- /dev/null +++ b/scss/helpers/_focus-ring.scss @@ -0,0 +1,5 @@ +.focus-ring:focus { + outline: 0; + // By default, there is no `--bs-focus-ring-x`, `--bs-focus-ring-y`, or `--bs-focus-ring-blur`, but we provide CSS variables with fallbacks to initial `0` values + box-shadow: var(--#{$prefix}focus-ring-x, 0) var(--#{$prefix}focus-ring-y, 0) var(--#{$prefix}focus-ring-blur, 0) var(--#{$prefix}focus-ring-width) var(--#{$prefix}focus-ring-color); +} diff --git a/site/assets/scss/_component-examples.scss b/site/assets/scss/_component-examples.scss index 11800a638f..99bc460c12 100644 --- a/site/assets/scss/_component-examples.scss +++ b/site/assets/scss/_component-examples.scss @@ -397,3 +397,8 @@ margin-right: 0; } } + +.focused { + outline: 0; + box-shadow: var(--#{$variable-prefix}focus-ring-offset), var(--#{$variable-prefix}focus-ring-x, 0) var(--#{$variable-prefix}focus-ring-y, 0) var(--#{$variable-prefix}focus-ring-blur) var(--#{$variable-prefix}focus-ring-width) var(--#{$variable-prefix}focus-ring-color); +} diff --git a/site/content/docs/5.3/customize/css-variables.md b/site/content/docs/5.3/customize/css-variables.md index 838a1abf7a..ffb40c0c4a 100644 --- a/site/content/docs/5.3/customize/css-variables.md +++ b/site/content/docs/5.3/customize/css-variables.md @@ -74,6 +74,20 @@ a { } ``` +## Focus variables + +{{< added-in "5.3.0" >}} + +Bootstrap provides custom `:focus` styles using a combination of Sass and CSS variables that can be optionally added to specific components and elements. We do not yet globally override all `:focus` styles. + +In our Sass, we set default values that can be customized before compiling. + +{{< scss-docs name="focus-ring-variables" file="scss/_variables.scss" >}} + +Those variables are then reassigned to `:root` level CSS variables that can be customized in real-time, including with options for `x` and `y` offsets (which default to their fallback value of `0`). + +{{< scss-docs name="root-focus-variables" file="scss/_root.scss" >}} + ## Grid breakpoints While we include our grid breakpoints as CSS variables (except for `xs`), be aware that **CSS variables do not work in media queries**. This is by design in the CSS spec for variables, but may change in coming years with support for `env()` variables. Check out [this Stack Overflow answer](https://stackoverflow.com/a/47212942) for some helpful links. In the meantime, you can use these variables in other CSS situations, as well as in your JavaScript. diff --git a/site/content/docs/5.3/helpers/focus-ring.md b/site/content/docs/5.3/helpers/focus-ring.md new file mode 100644 index 0000000000..5819bf1c6f --- /dev/null +++ b/site/content/docs/5.3/helpers/focus-ring.md @@ -0,0 +1,60 @@ +--- +layout: docs +title: Focus ring +description: Utility classes that allows you to add and modify custom focus ring styles to elements and components. +group: helpers +toc: true +added: "5.3" +--- + +The `.focus-ring` helper removes the default `outline` on `:focus`, replacing it with a `box-shadow` that can be more broadly customized. The new shadow is made up of a series of CSS variables, inherited from the `:root` level, that can be modified for any element or component. + +## Example + +Click into the example below and press Tab to see the focus ring in action. + +{{< example >}} + + Custom focus ring + +{{< /example >}} + +## Customize + +Modify the styling of a focus ring with our CSS variables, Sass variables, utilities, or custom styles. + +### CSS variables + +Modify the `--bs-focus-ring-*` CSS variables as needed to change the default appearance. + +{{< example >}} + + Green focus ring + +{{< /example >}} + +`.focus-ring` sets styles via global CSS variables that can be overridden on any parent element, as shown above. These variables are generated from their Sass variable counterparts. + +{{< scss-docs name="root-focus-variables" file="scss/_root.scss" >}} + +### Sass + +Customize the focus ring Sass variables to modify all usage of the focus ring styles across your Bootstrap-powered project. + +{{< scss-docs name="focus-ring-variables" file="scss/_variables.scss" >}} + +### Utilities + +In addition to `.focus-ring`, we have several `.focus-ring-*` utilities to modify the helper class defaults. Modify the color with any of our [theme colors]({{< docsref "/customize/color#theme-colors" >}}). Note that the light and dark variants may not be visible on all background colors given current color mode support. + +{{< example >}} +{{< focus-ring.inline >}} +{{- range (index $.Site.Data "theme-colors") }} +
+{{- end -}} +{{< /focus-ring.inline >}} +{{< /example >}} + +Focus ring utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref "/utilities/api#using-the-api" >}}) + +{{< scss-docs name="utils-focus-ring" file="scss/_utilities.scss" >}} diff --git a/site/data/sidebar.yml b/site/data/sidebar.yml index 4199fa535e..b1d567e990 100644 --- a/site/data/sidebar.yml +++ b/site/data/sidebar.yml @@ -104,6 +104,7 @@ - title: Clearfix - title: Color & background - title: Colored links + - title: Focus ring - title: Position - title: Ratio - title: Stacks