Skip to content

Commit 9e39d01

Browse files
authored
Update Sass and Theme docs to document mixins and functions better (#42173)
* Update Sass and Theme docs to document mixins and functions better Also remove some unused functions while we're here. * document it instead * Fix cspell, add local cspell so i can test in dev
1 parent ac23867 commit 9e39d01

9 files changed

Lines changed: 1186 additions & 61 deletions

File tree

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"navbars",
7575
"navs",
7676
"navoverflow",
77+
"negativify",
7778
"Neue",
7879
"noindex",
7980
"Noto",

package-lock.json

Lines changed: 1050 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@
111111
"watch-js-docs": "nodemon --watch site/src/assets/ --ext js --exec \"npm run js-lint\"",
112112
"astro-dev": "astro dev --root site --port 9001",
113113
"astro-build": "astro build --root site && rm -rf _site && cp -r site/dist _site",
114-
"astro-preview": "astro preview --root site --port 9001"
114+
"astro-preview": "astro preview --root site --port 9001",
115+
"spellcheck": "cspell --config .cspell.json \"**/*.{md,mdx}\""
115116
},
116117
"peerDependencies": {
117118
"@floating-ui/dom": "^1.7.6",
@@ -143,6 +144,7 @@
143144
"bundlewatch": "^0.4.1",
144145
"clipboard": "^2.0.11",
145146
"cross-env": "^10.1.0",
147+
"cspell": "^9.7.0",
146148
"eslint": "^10.1.0",
147149
"eslint-config-xo": "^0.50.0",
148150
"eslint-plugin-html": "^8.1.4",

scss/_theme.scss

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
@use "sass:meta";
21
@use "sass:map";
32

43
@function theme-color-values($key) {
@@ -42,20 +41,6 @@
4241
}
4342
}
4443

45-
// Recursive mixin to handle nested maps
46-
@mixin create-css-vars($map, $parent-key: "") {
47-
@each $key, $value in $map {
48-
// stylelint-disable-next-line scss/at-function-named-arguments
49-
$current-key: if(sass($parent-key == ""): $key; else: "#{$parent-key}-#{$key}");
50-
51-
@if meta.type-of($value) == "map" {
52-
@include create-css-vars($value, $current-key);
53-
} @else {
54-
--#{$current-key}: #{$value};
55-
}
56-
}
57-
}
58-
5944
// scss-docs-start theme-colors
6045
$theme-colors: (
6146
"primary": (

scss/forms/_validation.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
.form-control-color {
109109
@include form-validation-state-selector($state) {
110110
@if $enable-validation-icons {
111-
// width: add($form-color-width, $input-height-inner);
111+
// width: calc($form-color-width + $input-height-inner);
112112
}
113113
}
114114
}

scss/mixins/_forms.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
.form-control-color {
119119
@include form-validation-state-selector($state) {
120120
@if $enable-validation-icons {
121-
width: add($form-color-width, $input-height-inner);
121+
width: calc($form-color-width + $input-height-inner);
122122
}
123123
}
124124
}

site/src/content/docs/customize/color.mdx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,24 @@ Those options and the `$colors` map are used to generate a `$color-tokens` map,
7777

7878
<ScssDocs name="color-tokens" file="scss/_colors.scss" />
7979

80+
In practice, our CSS variables that get output from this looks like this:
81+
82+
```scss
83+
// Example generated custom properties from _colors.scss
84+
--bs-blue-025: color-mix(in lab, var(--bs-white) 94%, oklch(60% 0.24 240));
85+
--bs-blue-050: color-mix(in lab, var(--bs-white) 90%, oklch(60% 0.24 240));
86+
--bs-blue-100: color-mix(in lab, var(--bs-white) 80%, oklch(60% 0.24 240));
87+
// ...
88+
--bs-blue-500: oklch(60% 0.24 240);
89+
// ...
90+
--bs-blue-900: color-mix(in lab, var(--bs-black) 64%, oklch(60% 0.24 240));
91+
--bs-blue-975: color-mix(in lab, var(--bs-black) 88%, oklch(60% 0.24 240));
92+
```
93+
94+
<Callout type="info">
95+
We use CSS variables `var(--bs-white)` and `var(--bs-black)` to prevent LightningCSS from converting the real-time mixing into static `lab()` values. Unfortunately, this feature cannot be disabled in LightningCSS at this time.
96+
</Callout>
97+
8098
## Customizing
8199

82100
You can customize the colors by adding or removing colors from the `$colors` map. You can also customize the tint and shade stops, the `color-mix()` color space, and the mix colors. Say you want to add another blue-gray color, like slate.

site/src/content/docs/customize/sass.mdx

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -360,31 +360,15 @@ For `$theme-colors`, the `primary`, `success`, and `danger` keys are required fo
360360
361361
### Defaults
362362
363-
The `defaults()` function is the backbone of Bootstrap's customization system. It merges user overrides on top of built-in defaults and strips any `null` entries from the result. Every token map, size map, and variant map uses it:
363+
The `defaults()` function is the backbone of Bootstrap's customization system. It merges user overrides on top of built-in default tokens and strips any `null` entries from the result. Every token map, size map, and variant map uses it:
364364

365365
```scss
366366
@function defaults($defaults, $overrides) { ... }
367367
```
368368

369369
It accepts either a map or a list as the first argument. Lists are automatically converted to maps with `true` values, which is how size maps like `$button-sizes` work with a clean `("xs", "sm", "lg")` syntax.
370370

371-
### Colors
372-
373-
Base colors are defined as `oklch()` values and expanded into full scales via `color-mix()`. Each hue generates steps from `025` to `975`:
374-
375-
```scss
376-
// Example generated custom properties from _colors.scss
377-
--blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 240));
378-
--blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 240));
379-
--blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 240));
380-
// ...
381-
--blue-500: oklch(60% 0.24 240);
382-
// ...
383-
--blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 240));
384-
--blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 240));
385-
```
386-
387-
You can reference any color scale step as a CSS custom property: `var(--blue-500)`, `var(--red-200)`, `var(--gray-900)`, etc.
371+
`defaults()` are generated on specific selectors with the [`tokens()` mixin](#tokens).
388372

389373
### Color contrast
390374

@@ -406,47 +390,119 @@ For example, to generate color swatches from our `$theme-colors` map:
406390

407391
We use the `escape-svg` function to escape the `<`, `>` and `#` characters for SVG background images. When using the `escape-svg` function, data URIs must be quoted.
408392

409-
### Add and Subtract functions
393+
```scss
394+
// Input — an inline SVG with special characters
395+
$icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#00000080' stroke-width='2' d='m2 5 6 6 6-6'/></svg>");
396+
397+
// After escape-svg(), the <, >, and # characters are percent-encoded
398+
// so the data URI works reliably as a CSS background-image
399+
.element {
400+
background-image: escape-svg($icon);
401+
}
402+
```
403+
404+
### Map helpers
410405

411-
We use the `add` and `subtract` functions to wrap the CSS `calc` function. The primary purpose of these functions is to avoid errors when a "unitless" `0` value is passed into a `calc` expression. Expressions like `calc(10px - 0)` will return an error in all browsers, despite being mathematically correct.
406+
We include several Sass functions for working with maps beyond what Sass provides natively.
412407

413-
Example where the calc is valid:
408+
<BsTable>
409+
| Function | Description |
410+
| --- | --- |
411+
| `map-merge-multiple($maps...)` | Merges any number of maps into one, processing them left to right. |
412+
| `map-get-multiple($map, $values)` | Returns a subset of a map filtered to only the keys listed in `$values`. |
413+
| `map-get-nested($map, $nested-key)` | Extracts a single property from every nested map entry, returning a flat map. |
414+
</BsTable>
415+
416+
#### `map-merge-multiple($maps...)`
414417

415418
```scss
416-
$border-radius: .25rem;
417-
$border-width: 1px;
419+
@function map-merge-multiple($maps...) { ... }
418420

419-
.element {
420-
// Output calc(.25rem - 1px) is valid
421-
border-radius: calc($border-radius - $border-width);
422-
}
421+
// Example: combine spacers, negative spacers, and auto into a single values map
422+
values: map-merge-multiple($spacers, $negative-spacers, (auto: auto))
423+
```
423424

424-
.element {
425-
// Output the same calc(.25rem - 1px) as above
426-
border-radius: subtract($border-radius, $border-width);
427-
}
425+
#### `map-get-multiple($map, $values)`
426+
427+
```scss
428+
@function map-get-multiple($map, $values) { ... }
429+
430+
// Example: extract only the flex and order utilities
431+
$utilities: map-get-multiple(
432+
$utilities,
433+
("flex", "flex-direction", "flex-grow", "order")
434+
);
428435
```
429436

430-
Example where the calc is invalid:
437+
#### `map-get-nested($map, $nested-key)`
431438

432439
```scss
433-
$border-radius: .25rem;
434-
$border-width: 0;
440+
@function map-get-nested($map, $nested-key) { ... }
435441

436-
.element {
437-
// Output calc(.25rem - 0) is invalid
438-
border-radius: calc($border-radius - $border-width);
439-
}
442+
// Example: pull just the "font-size" value from each entry in $font-sizes
443+
values: map-get-nested($font-sizes, "font-size")
444+
// Returns: ("xs": clamp(...), "sm": clamp(...), ...)
445+
```
440446

441-
.element {
442-
// Output .25rem
443-
border-radius: subtract($border-radius, $border-width);
444-
}
447+
#### `negativify-map($map)`
448+
449+
Turns a map into its negative variant by prefixing each key with `n` and negating the value. Keys equal to `0` are skipped. This is used internally to generate negative spacer utilities (e.g., `n1`, `n2`, etc.) from the `$spacers` map.
450+
451+
```scss
452+
@function negativify-map($map) { ... }
453+
454+
// Example: generate negative spacers from a spacers map
455+
$spacers: (0: 0, 1: .25rem, 2: .5rem, 3: 1rem);
456+
$negative-spacers: negativify-map($spacers);
457+
// Returns: ("n1": -.25rem, "n2": -.5rem, "n3": -1rem)
458+
```
459+
460+
### Theme color values
461+
462+
The [`theme-color-values($key)`]([[docsref:/customize/theme]]) function in `scss/_theme.scss` extracts a specific sub-key from every entry in the `$theme-colors` map, returning a flat map of color names to values. Since each theme color is a nested map with sub-keys like `base`, `text`, `bg`, `border`, etc., this function lets you pull one of those sub-keys across all colors at once. It powers most of the color-related utilities in `_utilities.scss`:
463+
464+
```scss
465+
// In _utilities.scss — generate .focus-ring-{color} classes
466+
"focus-ring": (
467+
property: --focus-ring-color,
468+
class: focus-ring,
469+
values: theme-color-values("focus-ring"),
470+
),
471+
472+
// Merge theme color backgrounds with semantic border tokens for .border-{color} classes
473+
"border-color": (
474+
property: (
475+
"--border-color": null,
476+
"border-color": var(--border-color)
477+
),
478+
class: border,
479+
values: map.merge(theme-color-values("bg"), $theme-borders),
480+
),
481+
```
482+
483+
### Theme opacity values
484+
485+
The [`theme-opacity-values($color-var, $opacities)`]([[docsref:/customize/theme]]) function in `scss/_theme.scss` generates a map of opacity variants for a CSS custom property using `color-mix()`. The `$opacities` argument defaults to `$util-opacity` (10 through 100 in steps of 10). At `100`, it returns the variable directly; for all other steps, it mixes the variable against `transparent`:
486+
487+
```scss
488+
// In _utilities.scss — generate .border-{opacity} classes
489+
"border-opacity": (
490+
class: border,
491+
property: border-color,
492+
values: theme-opacity-values(--border-color)
493+
),
494+
495+
// Generate .fg-{opacity} classes
496+
"fg-opacity": (
497+
class: fg,
498+
property: color,
499+
values: theme-opacity-values(--fg)
500+
),
445501
```
446502

447503
## Mixins
448504

449-
Our `scss/mixins/` directory has a ton of mixins that power parts of Bootstrap and can also be used across your own project.
505+
Our `scss/mixins/` directory has snippets of code that power Bootstrap and can also be used across your own project.
450506

451507
### Tokens
452508

@@ -478,3 +534,15 @@ A shorthand mixin for the `prefers-color-scheme` media query is available with s
478534
}
479535
}
480536
```
537+
538+
### Theme classes
539+
540+
The `generate-theme-classes()` mixin loops over the `$theme-colors` map and outputs a `.theme-*` class for each color. Each class maps generic `--theme-*` custom properties to the color's specific values, so components can reference `--theme-base`, `--theme-bg`, etc., and pick up the right palette from a parent `.theme-*` class:
541+
542+
```scss
543+
@include generate-theme-classes();
544+
// Outputs:
545+
// .theme-primary { --theme-base: var(--primary-base); --theme-bg: var(--primary-bg); ... }
546+
// .theme-success { --theme-base: var(--success-base); --theme-bg: var(--success-bg); ... }
547+
// ...
548+
```

site/src/content/docs/migration.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Bootstrap 6 is a major release with many breaking changes to modernize our codeb
6969
- Dropped support for Node Sass, including no longer testing any of our source CSS against it.
7070
- Rearranged several Sass files in the process.
7171
- Removed `add()` and `subtract()` functions. Use `calc()` instead.
72+
- Removed `create-css-vars()` mixin (unused).
7273
- **CSS variable prefixing now handled by PostCSS.** The `$prefix` Sass variable has been removed. CSS custom properties are now written without a prefix in the Sass source and prefixed automatically via `postcss-prefix-custom-properties` during the build. To customize the prefix, update your PostCSS configuration instead of Sass.
7374
- **Removes all deprecated Sass variables and values:**
7475
- Removed `$nested-kbd-font-weight`, no replacement.

0 commit comments

Comments
 (0)