* Update utility API to prevent border-color inheritance, write custom @property rules
* fix fg var
--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
// 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"),
"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
@use "sass:list";
@use "sass:map";
@use "sass:meta";
+@use "sass:string";
@use "../layout/breakpoints" as bp;
// Utility generator
}
}
+// 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.
@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);
}
## 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.
+<Callout>
+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.
+</Callout>
+
<Example class="d-flex flex-column gap-2 bd-example-border-color-utils" code={[
...getData('theme-colors').map((themeColor) => `<div class="border border-5 border-${themeColor.name}">${themeColor.name}</div>
<div class="border border-5 border-subtle-${themeColor.name}">${themeColor.name} subtle</div>`),
Changing border color and width
</div>`} />
+## 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.
+
+<Example code={`<div class="p-3 border border-danger rounded-5">
+ Parent with .border-danger
+ <div class="border p-3 mt-3">
+ Nested .border keeps the default color
+ </div>
+ </div>`} />
+
## Opacity
-Change the opacity of a border color by using any of the `.border-<percentage>` 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-<percentage>` 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.
<Example code={`<div class="border border-5 border-primary p-2 mb-2">Default border</div>
<div class="border border-5 border-primary border-90 p-2 mb-2">90% opacity</div>
## Width
+Border width utilities are available in increments of 1px from 0 to 5px.
+
<Example class="bd-example-border-utils" code={`<span class="border border-1"></span>
<span class="border border-2"></span>
<span class="border border-3"></span>
}
```
+<Callout>
+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.
+</Callout>
+
## CSS
### Sass utilities API