From: Mark Otto Date: Tue, 14 Jul 2026 16:48:48 +0000 (-0700) Subject: v6: Prevent `border-color` utility inheritance, harden other utilities (#42669) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=bf32c142b5a8221aa43260655898fa7f24fe9e6c;p=thirdparty%2Fbootstrap.git v6: Prevent `border-color` utility inheritance, harden other utilities (#42669) * Update utility API to prevent border-color inheritance, write custom @property rules * fix fg var --- diff --git a/scss/_accordion.scss b/scss/_accordion.scss index 6a9d7d56e3..c8cf38344e 100644 --- a/scss/_accordion.scss +++ b/scss/_accordion.scss @@ -25,7 +25,7 @@ $accordion-tokens: defaults( --accordion-btn-icon-width: 1rem, --accordion-btn-icon-transform: rotate(-180deg), --accordion-btn-icon-transition: transform .2s ease-in-out, - --accordion-active-color: var(--fg), + --accordion-active-color: var(--fg-body), --accordion-active-bg: var(--bg-2), ), $accordion-tokens diff --git a/scss/_utilities.scss b/scss/_utilities.scss index 3bd0962005..96aa639310 100644 --- a/scss/_utilities.scss +++ b/scss/_utilities.scss @@ -208,16 +208,16 @@ $utilities: map.merge( // scss-docs-start utils-border-color "border-color": ( property: ( - "--border-color": null, - "border-color": var(--border-color) + "--bc": null, + "border-color": var(--bc) ), class: border, values: map.merge(theme-color-refs("bg"), theme-token-refs($theme-borders, "border")), ), "border-color-subtle": ( property: ( - "--border-color": null, - "border-color": var(--border-color) + "--bc": null, + "border-color": var(--bc) ), class: border-subtle, values: theme-color-refs("border"), @@ -230,7 +230,7 @@ $utilities: map.merge( "border-opacity": ( class: border, property: border-color, - values: theme-opacity-values(--border-color) + values: theme-opacity-values(--bc) ), // scss-docs-end utils-border-color // Sizing utilities diff --git a/scss/mixins/_utilities.scss b/scss/mixins/_utilities.scss index 2d78150d7b..9076fc3b8b 100644 --- a/scss/mixins/_utilities.scss +++ b/scss/mixins/_utilities.scss @@ -1,6 +1,7 @@ @use "sass:list"; @use "sass:map"; @use "sass:meta"; +@use "sass:string"; @use "../layout/breakpoints" as bp; // Utility generator @@ -255,6 +256,77 @@ } } +// Helper: append a custom property name to a list only if not already present. +@function append-unique-var($list, $name) { + @if list.index($list, $name) == null { + @return list.append($list, $name); + } + @return $list; +} + +// Helper: collect custom property names (those starting with `--`) from a +// utility's `property` value, which may be a map, list, or string. +@function collect-property-vars($list, $properties) { + @if meta.type-of($properties) == "map" { + @each $prop, $value in $properties { + @if meta.type-of($prop) == "string" and string.slice($prop, 1, 2) == "--" { + $list: append-unique-var($list, $prop); + } + } + } @else if meta.type-of($properties) == "list" { + @each $prop in $properties { + @if meta.type-of($prop) == "string" and string.slice($prop, 1, 2) == "--" { + $list: append-unique-var($list, $prop); + } + } + } @else if meta.type-of($properties) == "string" and string.slice($properties, 1, 2) == "--" { + $list: append-unique-var($list, $properties); + } + @return $list; +} + +// Registers every custom property that utilities assign to as non-inheriting +// via `@property`. Utilities compose values through local custom properties +// (e.g. `--bc`, `--bg`, `--fg`, `--rounded-size`); because custom properties +// inherit by default, setting one on an element would otherwise leak into every +// descendant (e.g. `.border-inverse` tinting nested component borders). Marking +// them `inherits: false` scopes each utility to the element it's applied to. +// `syntax: "*"` is used so no `initial-value` is required (values are complex +// expressions like `light-dark()` / `color-mix()` that can't be registered). +@mixin generate-utility-at-properties($utilities) { + $names: (); + + @each $key, $utility in $utilities { + @if meta.type-of($utility) == "map" and map.get($utility, enabled) != false { + // Custom properties assigned via the `property` key + $names: collect-property-vars($names, map.get($utility, property)); + + // Custom properties assigned via the `variables` key (names lack the `--` prefix) + @if map.has-key($utility, variables) { + $variables: map.get($utility, variables); + @if meta.type-of($variables) == "map" { + @each $var-key, $var-value in $variables { + $names: append-unique-var($names, "--" + $var-key); + } + } @else if meta.type-of($variables) == "list" { + @each $var-name in $variables { + $names: append-unique-var($names, "--" + $var-name); + } + } @else { + $names: append-unique-var($names, "--" + $variables); + } + } + } + } + + @each $name in $names { + @property #{$name} { + syntax: "*"; + inherits: false; + } + } +} + // Generates all utility classes: base, responsive, print, and dark. // Extracted so that tests can call this mixin directly with a custom $utilities map // rather than having to mirror the loop conditions inline. diff --git a/scss/utilities/_api.scss b/scss/utilities/_api.scss index 327573a398..569ce6a402 100644 --- a/scss/utilities/_api.scss +++ b/scss/utilities/_api.scss @@ -2,6 +2,11 @@ @use "../mixins/utilities" as *; @use "../utilities" as *; +// Register utility-composition custom properties as non-inheriting so utilities +// (e.g. `.border-inverse`) don't leak their composed value into descendants. +// Emitted outside the cascade layer since `@property` registration is global. +@include generate-utility-at-properties($utilities); + @layer utilities { @include generate-utilities-loop($utilities, $breakpoints); } diff --git a/site/src/content/docs/utilities/border-color.mdx b/site/src/content/docs/utilities/border-color.mdx index 9216209663..7f5c953f04 100644 --- a/site/src/content/docs/utilities/border-color.mdx +++ b/site/src/content/docs/utilities/border-color.mdx @@ -15,19 +15,23 @@ import { getData } from '@libs/data' ## Colors -Change the `border-color` using utilities built on our theme colors using `.border-{color}` and `.border-subtle-{color}`. All these border color utilities set the `border-color` property to a local CSS variable, `--bs-border-color`, which has the value of the theme color. The root color tokens color values also use `light-dark()` to ensure sufficient contrast in both light and dark color modes. +Change the `border-color` using utilities built on our theme colors using `.border-{color}` and `.border-subtle-{color}`. All these border color utilities set the `border-color` property to a local CSS variable, `--bs-bc`, which has the value of the theme color. The root color tokens color values also use `light-dark()` to ensure sufficient contrast in both light and dark color modes. -For example, `.border-primary` sets the `--bs-border-color` variable to `var(--bs-primary-border)`: +For example, `.border-primary` sets the `--bs-bc` variable to `var(--bs-primary-border)`: ```css .border-primary { - --bs-border-color: var(--bs-primary-border); - border-color: var(--bs-border-color); + --bs-bc: var(--bs-primary-border); + border-color: var(--bs-bc); } ``` This approach allows us to also easily support translucency with our `.border-{opacity}` utilities as we can use `color-mix()` with the CSS variable to generate the appropriate color. See the [opacity section](#opacity) for more details. + +The `--bs-bc` variable is registered with `@property` as non-inheriting (`inherits: false`), so border color utilities are scoped to the element they’re applied to and never cascade into nested elements or components. This is why `--bs-bc` is deliberately separate from the inheriting `--bs-border-color` design token that components resolve their default borders from. + + `
${themeColor.name}
${themeColor.name} subtle
`), @@ -55,9 +59,20 @@ Or modify the default `border-color` of a component: Changing border color and width `} /> +## Nested elements + +Because border color utilities set the non-inheriting `--bs-bc` variable, parent border colors don’t affect the borders of nested elements. Below, the parent uses `.border-danger` while the nested `.border` keeps the default border color. + + + Parent with .border-danger +
+ Nested .border keeps the default color +
+ `} /> + ## Opacity -Change the opacity of a border color by using any of the `.border-` utilities which use `color-mix()` to mix the border color with `transparent` thanks to the CSS variable approach mentioned above. +Change the opacity of a border color by using any of the `.border-` utilities which use `color-mix()` to mix the border color with `transparent` thanks to the CSS variable approach mentioned above. Because `--bs-bc` is scoped to the element, pair these opacity utilities with a `.border-{color}` utility on the same element—they no longer inherit a border color from an ancestor. Default border
90% opacity
diff --git a/site/src/content/docs/utilities/border.mdx b/site/src/content/docs/utilities/border.mdx index 60f261337a..12a35b540f 100644 --- a/site/src/content/docs/utilities/border.mdx +++ b/site/src/content/docs/utilities/border.mdx @@ -40,6 +40,8 @@ Or remove borders: ## Width +Border width utilities are available in increments of 1px from 0 to 5px. + diff --git a/site/src/content/docs/utilities/divide.mdx b/site/src/content/docs/utilities/divide.mdx index 2b8e86808f..540fb8a494 100644 --- a/site/src/content/docs/utilities/divide.mdx +++ b/site/src/content/docs/utilities/divide.mdx @@ -65,6 +65,10 @@ Divide utilities apply `border-inline-start` or `border-block-start` to every di } ``` + +Divider color comes from the inheriting `--bs-border-color` design token, so set it on the parent to recolor dividers. Note this differs from the `.border-{color}` utilities, which are element-scoped (they set the non-inheriting `--bs-bc` variable) and therefore won’t recolor a parent’s dividers. + + ## CSS ### Sass utilities API