diff --git a/scss/_functions.scss b/scss/_functions.scss index 1f3263175e..e00395505f 100644 --- a/scss/_functions.scss +++ b/scss/_functions.scss @@ -32,6 +32,41 @@ } } +// Colors +@function to-rgb($value) { + @return red($value), green($value), blue($value); +} + +@function rgba-css-var($identifier, $target) { + @return rgba(var(--#{$variable-prefix}#{$identifier}-rgb), var(--#{$variable-prefix}#{$target}-opacity)); +} + +// stylelint-disable scss/dollar-variable-pattern +@function map-loop($map, $func, $args...) { + $_map: (); + + @each $key, $value in $map { + // allow to pass the $key and $value of the map as an function argument + $_args: (); + @each $arg in $args { + $_args: append($_args, if($arg == "$key", $key, if($arg == "$value", $value, $arg))); + } + + $_map: map-merge($_map, ($key: call(get-function($func), $_args...))); + } + + @return $_map; +} +// stylelint-enable scss/dollar-variable-pattern + +@function varify($list) { + $result: null; + @each $entry in $list { + $result: append($result, var(--#{$variable-prefix}#{$entry}), space); + } + @return $result; +} + // Internal Bootstrap function to turn maps into its negative variant. // It prefixes the keys with `n` and makes the value negative. @function negativify-map($map) { diff --git a/scss/_root.scss b/scss/_root.scss index 768d6343f8..95c7739011 100644 --- a/scss/_root.scss +++ b/scss/_root.scss @@ -8,6 +8,14 @@ --#{$variable-prefix}#{$color}: #{$value}; } + @each $color, $value in $theme-colors-rgb { + --#{$variable-prefix}#{$color}-rgb: #{$value}; + } + + --#{$variable-prefix}white-rgb: #{to-rgb($white)}; + --#{$variable-prefix}black-rgb: #{to-rgb($black)}; + --#{$variable-prefix}body-rgb: #{to-rgb($body-color)}; + // Use `inspect` for lists so that quoted items keep the quotes. // See https://github.com/sass/sass/issues/2383#issuecomment-336349172 --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)}; diff --git a/scss/_utilities.scss b/scss/_utilities.scss index 74f8a3a01c..960d6f1adf 100644 --- a/scss/_utilities.scss +++ b/scss/_utilities.scss @@ -514,32 +514,55 @@ $utilities: map-merge( "color": ( property: color, class: text, + local-vars: ( + "text-opacity": 1 + ), values: map-merge( - $theme-colors, + $utilities-text-colors, ( - "white": $white, - "body": $body-color, "muted": $text-muted, - "black-50": rgba($black, .5), - "white-50": rgba($white, .5), + "black-50": rgba($black, .5), // deprecated + "white-50": rgba($white, .5), // deprecated "reset": inherit, ) ) ), + "text-opacity": ( + css-var: true, + class: text-opacity, + values: ( + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ) + ), // scss-docs-end utils-color // scss-docs-start utils-bg-color "background-color": ( property: background-color, class: bg, + local-vars: ( + "bg-opacity": 1 + ), values: map-merge( - $theme-colors, + $utilities-bg-colors, ( - "body": $body-bg, - "white": $white, "transparent": transparent ) ) ), + "bg-opacity": ( + css-var: true, + class: bg-opacity, + values: ( + 10: .1, + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ) + ), // scss-docs-end utils-bg-color "gradient": ( property: background-image, diff --git a/scss/_variables.scss b/scss/_variables.scss index 9e921c07ef..3750fa2c23 100644 --- a/scss/_variables.scss +++ b/scss/_variables.scss @@ -90,6 +90,10 @@ $theme-colors: ( ) !default; // scss-docs-end theme-colors-map +// scss-docs-start theme-colors-rgb +$theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value") !default; +// scss-docs-end theme-colors-rgb + // The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.0 are 3, 4.5 and 7. // See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast $min-contrast-ratio: 4.5 !default; @@ -401,6 +405,28 @@ $body-bg: $white !default; $body-color: $gray-900 !default; $body-text-align: null !default; +// Utilities maps +// +// Extends the default `$theme-colors` maps to help create our utilities. + +// scss-docs-start utilities-colors +$utilities-colors: map-merge( + $theme-colors-rgb, + ( + "black": to-rgb($black), + "white": to-rgb($white), + "body": to-rgb($body-color) + ) +) !default; +// scss-docs-end utilities-colors + +// scss-docs-start utilities-text-colors +$utilities-text-colors: map-loop($utilities-colors, rgba-css-var, "$key", "text") !default; +// scss-docs-end utilities-text-colors + +// scss-docs-start utilities-bg-colors +$utilities-bg-colors: map-loop($utilities-colors, rgba-css-var, "$key", "bg") !default; +// scss-docs-end utilities-bg-colors // Links // diff --git a/scss/mixins/_utilities.scss b/scss/mixins/_utilities.scss index 4d2370a0bf..e871b42336 100644 --- a/scss/mixins/_utilities.scss +++ b/scss/mixins/_utilities.scss @@ -41,25 +41,46 @@ } } + $is-css-var: map-get($utility, css-var); + $is-local-vars: map-get($utility, local-vars); $is-rtl: map-get($utility, rtl); @if $value != null { @if $is-rtl == false { /* rtl:begin:remove */ } - .#{$property-class + $infix + $property-class-modifier} { - @each $property in $properties { - #{$property}: $value if($enable-important-utilities, !important, null); - } - } - @each $pseudo in $state { - .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { + @if $is-css-var { + .#{$property-class + $infix + $property-class-modifier} { + --#{$variable-prefix}#{$property-class}: #{$value}; + } + + @each $pseudo in $state { + .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { + --#{$variable-prefix}#{$property-class}: #{$value}; + } + } + } @else { + .#{$property-class + $infix + $property-class-modifier} { @each $property in $properties { + @if $is-local-vars { + @each $local-var, $value in $is-local-vars { + --#{$variable-prefix}#{$local-var}: #{$value}; + } + } #{$property}: $value if($enable-important-utilities, !important, null); } } + + @each $pseudo in $state { + .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { + @each $property in $properties { + #{$property}: $value if($enable-important-utilities, !important, null); + } + } + } } + @if $is-rtl == false { /* rtl:end:remove */ } diff --git a/site/content/docs/5.0/utilities/background.md b/site/content/docs/5.0/utilities/background.md index 7b1b481401..d5edbdf3b3 100644 --- a/site/content/docs/5.0/utilities/background.md +++ b/site/content/docs/5.0/utilities/background.md @@ -35,6 +35,44 @@ Do you need a gradient in your custom CSS? Just add `background-image: var(--bs- {{< /colors.inline >}} {{< /markdown >}} +## Opacity + +Added in v5.1.0 + +As of v5.1.0, `background-color` utilities are generated with Sass using CSS variables. This allows for real-time color changes without compilation and dynamic alpha transparency changes. + +### How it works + +Consider our default `.bg-success` utility. + +```css +.bg-success { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important; +} +``` + +We use an RGB version of our `--bs-succes` (with the value of `25, 135, 84`) CSS variable and attached a second CSS variable, `--bs-bg-opacity`, for the alpha transparency (with no default value, but a fallback of `1`). That means anytime you use `.bg-success` now, your computed `color` value is `rgba(25, 135, 84, 1)`. + +### Example + +To change that opacity, override `--bs-bg-opacity` via custom styles or inline styles. + +{{< example >}} +
This is default success background
+
This is 50% opacity success background
+{{< /example >}} + +Or, choose from any of the `.bg-opacity` utilities: + +{{< example >}} +
This is default success background
+
This is 75% opacity success background
+
This is 50% opacity success background
+
This is 25% opacity success background
+
This is 10% opacity success background
+{{< /example >}} + ## Sass In addition to the following Sass functionality, consider reading about our included [CSS custom properties]({{< docsref "/customize/css-variables" >}}) (aka CSS variables) for colors and more. @@ -63,6 +101,14 @@ Grayscale colors are also available as a Sass map. **This map is not used to gen {{< scss-docs name="gray-colors-map" file="scss/_variables.scss" >}} +RGB colors are generated from a separate Sass map: + +{{< scss-docs name="theme-colors-rgb" file="scss/_variables.scss" >}} + +And background color opacities build on that with their own map that's consumed by the utilities API: + +{{< scss-docs name="utilities-bg-colors" file="scss/_variables.scss" >}} + ### Mixins **No mixins are used to generate our background utilities**, but we do have some additional mixins for other situations where you'd like to create your own gradients. diff --git a/site/content/docs/5.0/utilities/colors.md b/site/content/docs/5.0/utilities/colors.md index 266f671f6d..9502c48777 100644 --- a/site/content/docs/5.0/utilities/colors.md +++ b/site/content/docs/5.0/utilities/colors.md @@ -23,10 +23,51 @@ Colorize text with color utilities. If you want to colorize links, you can use t

.text-white-50

{{< /example >}} +{{< callout warning >}} +**Deprecation:** With the addition of `.text-opacity-*` utilities and CSS variables for text utilities, `.text-black-50` and `.text-white-50` are deprecated as of v5.1.0. They'll be removed in v6.0.0. +{{< /callout >}} + {{< callout info >}} {{< partial "callout-warning-color-assistive-technologies.md" >}} {{< /callout >}} +## Opacity + +Added in v5.1.0 + +As of v5.1.0, text color utilities are generated with Sass using CSS variables. This allows for real-time color changes without compilation and dynamic alpha transparency changes. + +### How it works + +Consider our default `.text-primary` utility. + +```css +.text-primary { + --bs-text-opacity: 1; + color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important; +} +``` + +We use an RGB version of our `--bs-primary` (with the value of `13, 110, 253`) CSS variable and attached a second CSS variable, `--bs-text-opacity`, for the alpha transparency (with no default value, but a fallback of `1`). That means anytime you use `.text-primary` now, your computed `color` value is `rgba(13, 110, 253, 1)`. + +### Example + +To change that opacity, override `--bs-text-opacity` via custom styles or inline styles. + +{{< example >}} +
This is default primary text
+
This is 50% opacity primary text
+{{< /example >}} + +Or, choose from any of the `.text-opacity` utilities: + +{{< example >}} +
This is default primary text
+
This is 75% opacity primary text
+
This is 50% opacity primary text
+
This is 25% opacity primary text
+{{< /example >}} + ## Specificity Sometimes contextual classes cannot be applied due to the specificity of another selector. In some cases, a sufficient workaround is to wrap your element's content in a `
` or more semantic element with the desired class. @@ -57,6 +98,14 @@ Grayscale colors are also available as a Sass map. **This map is not used to gen {{< scss-docs name="gray-colors-map" file="scss/_variables.scss" >}} +RGB colors are generated from a separate Sass map: + +{{< scss-docs name="theme-colors-rgb" file="scss/_variables.scss" >}} + +And color opacities build on that with their own map that's consumed by the utilities API: + +{{< scss-docs name="utilities-text-colors" file="scss/_variables.scss" >}} + ### Utilities API Color utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref "/utilities/api#using-the-api" >}})