Skip to content
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

Focus ring helper and utilities #33125

Merged
merged 13 commits into from
Dec 29, 2022
6 changes: 3 additions & 3 deletions .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@
},
{
"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",
"maxSize": "31.5 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "29.25 kB"
"maxSize": "29.5 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
Expand Down
1 change: 1 addition & 0 deletions scss/_helpers.scss
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
5 changes: 5 additions & 0 deletions scss/_nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
text-decoration: if($link-hover-decoration == underline, none, null);
}

&:focus {
outline: 0;
mdo marked this conversation as resolved.
Show resolved Hide resolved
box-shadow: $nav-link-focus-box-shadow;
}

// Disabled state lightens text
&.disabled {
color: var(--#{$prefix}nav-link-disabled-color);
Expand Down
9 changes: 9 additions & 0 deletions scss/_root.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions scss/_utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
27 changes: 19 additions & 8 deletions scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/></svg>") !default;
$btn-close-focus-shadow: $input-btn-focus-box-shadow !default;
$btn-close-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/></svg>") !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;
Expand Down
5 changes: 5 additions & 0 deletions scss/helpers/_focus-ring.scss
Original file line number Diff line number Diff line change
@@ -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);
}
5 changes: 5 additions & 0 deletions site/assets/scss/_component-examples.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
14 changes: 14 additions & 0 deletions site/content/docs/5.3/customize/css-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
60 changes: 60 additions & 0 deletions site/content/docs/5.3/helpers/focus-ring.md
Original file line number Diff line number Diff line change
@@ -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 <kbd>Tab</kbd> to see the focus ring in action.

{{< example >}}
<a href="#" class="d-inline-flex focus-ring py-1 px-2 text-decoration-none border rounded-2">
Custom focus ring
</a>
{{< /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 >}}
<a href="#" class="d-inline-flex focus-ring py-1 px-2 text-decoration-none border rounded-2" style="--bs-focus-ring-color: rgba(var(--bs-success-rgb), .25)">
Green focus ring
</a>
{{< /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") }}
<p><a href="#" class="d-inline-flex focus-ring focus-ring-{{ .name }} py-1 px-2 text-decoration-none border rounded-2">{{ title .name }} focus</a></p>
{{- 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" >}}
1 change: 1 addition & 0 deletions site/data/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
- title: Clearfix
- title: Color & background
- title: Colored links
- title: Focus ring
mdo marked this conversation as resolved.
Show resolved Hide resolved
- title: Position
- title: Ratio
- title: Stacks
Expand Down