Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@
},
{
"path": "./dist/css/bootstrap-reboot.css",
"maxSize": "5.25 kB"
"maxSize": "5.5 kB"
},
{
"path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "5.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "18.0 kB"
"maxSize": "18.5 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "16.75 kB"
"maxSize": "17.25 kB"
},
{
"path": "./dist/css/bootstrap.css",
"maxSize": "49.75 kB"
"maxSize": "50.0 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
Expand Down
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"jsdelivr",
"Jumpstart",
"keyframes",
"keyline",
"libera",
"libman",
"Libsass",
Expand Down
36 changes: 36 additions & 0 deletions scss/_config.scss
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ $paragraph-margin-bottom: 1rem !default;
// scss-docs-start border-variables
$border-width: 1px !default;
$border-widths: (
keyline: .5px,
1: 1px,
2: 2px,
3: 3px,
Expand All @@ -213,6 +214,41 @@ $border-style: solid !default;
$border-color: color-mix(in oklch, var(--gray-100), var(--gray-200)) !default;
// scss-docs-end border-variables

// Each layer's alpha is base * strength * opacity, so shadow-color retints
// (e.g. .shadow-primary), shadow-opacity dims or boosts (.shadow-opacity-*),
// and shadow-strength deepens every shadow in dark mode. Color and opacity
// each read a local, non-inheriting override set by their utility
// (scss/_utilities.scss)—var(--sc, var(--shadow-color)) and var(--so, 1)—so
// neither leaks to nested .shadow-* descendants; --sc falls back to the
// cascading global --shadow-color token (scss/_root.scss) when unset.
// scss-docs-start box-shadow-variables
$shadows: (
null: #{(
"0 .125rem .25rem -.0625rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.04 * var(--shadow-strength) * var(--so, 1)))",
"0 .375rem .75rem -.125rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.05 * var(--shadow-strength) * var(--so, 1)))",
"0 .75rem 1.5rem -.25rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.06 * var(--shadow-strength) * var(--so, 1)))"
)},
xs: 0 .0625rem .125rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.04 * var(--shadow-strength) * var(--so, 1))),
sm: #{(
"0 .0625rem .125rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.04 * var(--shadow-strength) * var(--so, 1)))",
"0 .125rem .375rem -.0625rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.05 * var(--shadow-strength) * var(--so, 1)))"
)},
lg: #{(
"0 .125rem .375rem -.0625rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.05 * var(--shadow-strength) * var(--so, 1)))",
"0 .375rem 1rem -.1875rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.06 * var(--shadow-strength) * var(--so, 1)))",
"0 .75rem 2rem -.375rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.07 * var(--shadow-strength) * var(--so, 1)))",
"0 1.5rem 3.5rem -.75rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.08 * var(--shadow-strength) * var(--so, 1)))"
)},
xl: #{(
"0 .1875rem .5rem -.0625rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.05 * var(--shadow-strength) * var(--so, 1)))",
"0 .5rem 1.25rem -.1875rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.06 * var(--shadow-strength) * var(--so, 1)))",
"0 1rem 2.5rem -.375rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.07 * var(--shadow-strength) * var(--so, 1)))",
"0 2rem 5rem -.75rem oklch(from var(--sc, var(--shadow-color)) l c h / calc(.09 * var(--shadow-strength) * var(--so, 1)))"
)},
inset: inset 0 1px 2px light-dark(oklch(from var(--sc, var(--shadow-color)) l c h / calc(8% * var(--so, 1))), oklch(from var(--sc, var(--shadow-color)) l c h / calc(26% * var(--so, 1)))),
) !default;
// scss-docs-end box-shadow-variables

$transition-base: all .2s ease-in-out !default;
$transition-fade: opacity .15s linear !default;

Expand Down
2 changes: 1 addition & 1 deletion scss/_dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ $dialog-tokens: defaults(
--dialog-border-color: var(--border-color-translucent),
--dialog-border-width: var(--border-width),
--dialog-border-radius: var(--radius-7),
--dialog-box-shadow: var(--box-shadow-lg),
--dialog-box-shadow: var(--box-shadow-xl),
--dialog-transition-duration: .3s,
--dialog-transition-timing: cubic-bezier(.22, 1, .36, 1),
--dialog-backdrop-bg: light-dark(rgb(0 0 0 / 50%), rgb(0 0 0 / 65%)),
Expand Down
2 changes: 1 addition & 1 deletion scss/_drawer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ $drawer-tokens: defaults(
--drawer-border-width: var(--border-width),
--drawer-border-color: var(--border-color-translucent),
--drawer-border-radius: var(--radius-7),
--drawer-box-shadow: var(--box-shadow-lg),
--drawer-box-shadow: var(--box-shadow-xl),
--drawer-transition-duration: .3s,
--drawer-transition-timing: cubic-bezier(.22, 1, .36, 1),
--drawer-title-line-height: 1.5,
Expand Down
2 changes: 1 addition & 1 deletion scss/_menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $menu-tokens: defaults(
// --menu-border-color: var(--border-color-translucent),
// --menu-border-radius: var(--radius-7),
// --menu-border-width: var(--border-width),
--menu-box-shadow: var(--box-shadow),
--menu-box-shadow: var(--box-shadow-lg),
// --menu-max-height: none,
--menu-divider-bg: var(--border-color-translucent),
--menu-divider-margin-y: .125rem,
Expand Down
35 changes: 28 additions & 7 deletions scss/_root.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
$root-tokens: () !default;

// scss-docs-start root-tokens
// stylelint-disable @stylistic/value-list-max-empty-lines, @stylistic/function-max-empty-lines
// stylelint-disable @stylistic/value-list-max-empty-lines, @stylistic/function-max-empty-lines, custom-property-no-missing-var-function
// stylelint-disable-next-line scss/dollar-variable-default
$root-tokens: defaults(
(
Expand Down Expand Up @@ -49,17 +49,17 @@ $root-tokens: defaults(

// scss-docs-start root-border-var
--border-width: #{$border-width},
--border-width-keyline: .5px,
--border-style: #{$border-style},
--border-color: light-dark(var(--gray-200), var(--gray-700)),
--border-color-translucent: color-mix(in oklch, var(--fg-body) 15%, transparent),
// scss-docs-end root-border-var

// scss-docs-start root-box-shadow-variables
--box-shadow-xs: 0 .0625rem .1875rem rgb(0 0 0 / 7.5%),
--box-shadow-sm: 0 .125rem .25rem rgb(0 0 0 / 7.5%),
--box-shadow: 0 .5rem 1rem rgb(0 0 0 / 15%),
--box-shadow-lg: 0 1rem 3rem rgb(0 0 0 / 17.5%),
--box-shadow-inset: inset 0 1px 2px rgb(0 0 0 / 7.5%),
// --box-shadow* tokens are generated below (see root-box-shadow-loop) from
// the shared $shadows map in scss/_config.scss, which reads these two.
--shadow-color: #000,
--shadow-strength: 1,
// scss-docs-end root-box-shadow-variables

--spacer: 1rem,
Expand Down Expand Up @@ -113,7 +113,7 @@ $root-tokens: defaults(
),
$root-tokens
);
// stylelint-enable @stylistic/value-list-max-empty-lines, @stylistic/function-max-empty-lines
// stylelint-enable @stylistic/value-list-max-empty-lines, @stylistic/function-max-empty-lines, custom-property-no-missing-var-function
// scss-docs-end root-tokens

// scss-docs-start root-font-size-loop
Expand Down Expand Up @@ -169,6 +169,17 @@ $root-tokens: defaults(
$root-tokens: map.set($root-tokens, --radius-pill, 50rem);
// scss-docs-end root-radius-loop

// Generate box-shadow tokens
// scss-docs-start root-box-shadow-loop
@each $key, $value in $shadows {
@if $key == null {
$root-tokens: map.set($root-tokens, --box-shadow, $value);
} @else {
$root-tokens: map.set($root-tokens, --box-shadow-#{$key}, $value);
}
}
// scss-docs-end root-box-shadow-loop

:root {
@include tokens($root-tokens);

Expand All @@ -178,10 +189,20 @@ $root-tokens: map.set($root-tokens, --radius-pill, 50rem);
scrollbar-gutter: stable;
}

// Deepen shadows in dark mode. The shadow-strength token is a plain number, so it
// can't use light-dark() (colors only); vary it alongside color-scheme instead.
@media (prefers-color-scheme: dark) {
:root {
--shadow-strength: 2.4;
}
}

[data-bs-theme="dark"] {
--shadow-strength: 2.4;
color-scheme: dark;
}

[data-bs-theme="light"] {
--shadow-strength: 1;
color-scheme: light;
}
53 changes: 45 additions & 8 deletions scss/_utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
// update:
// - focus-ring if needed

// Shadow opacity acts as a midpoint: double the standard opacity scale so
// .shadow-opacity-50 → 1 (== the default shadow) and .shadow-opacity-100 → 2
// (twice as strong). The shadow expressions read a clean var(--shadow-opacity, 1).
$shadow-opacities: () !default;
@each $key, $value in $util-opacity {
$shadow-opacities: map.set($shadow-opacities, $key, $value * 2);
}

$utilities: () !default;
// stylelint-disable-next-line scss/dollar-variable-default
$utilities: map.merge(
Expand Down Expand Up @@ -106,15 +114,44 @@ $utilities: map.merge(
"shadow": (
property: box-shadow,
class: shadow,
values: (
null: var(--box-shadow),
xs: var(--box-shadow-xs),
sm: var(--box-shadow-sm),
lg: var(--box-shadow-lg),
none: none,
)
// Pull the literal layered expressions straight from the shared $shadows
// map (single source of truth in scss/_config.scss, also consumed by
// scss/_root.scss). Emitting the literal—rather than var(--box-shadow-*)—
// keeps the nested var(--sc, var(--shadow-color)) / var(--so, 1)
// unresolved so they resolve on the element, letting .shadow-{color} and
// .shadow-opacity-* take effect. `inset` isn't a utility, so it's excluded.
values: map.merge(map.remove($shadows, inset), (none: none))
),
// scss-docs-end utils-shadow
// scss-docs-start utils-shadow-color
"shadow-color": (
// --sc is a local override composed into the $shadows expressions
// (scss/_config.scss) as var(--sc, var(--shadow-color)); like --bc for
// border-color, it's non-inheriting so .shadow-primary doesn't tint
// nested .shadow-* descendants, which instead fall back to the
// cascading global --shadow-color token (scss/_root.scss).
property: --sc,
class: shadow,
values: map-merge-multiple(
theme-color-refs("base"),
(
"current": currentcolor,
"black": var(--black),
"white": var(--white),
)
)
),
// scss-docs-end utils-shadow-color
// scss-docs-start utils-shadow-opacity
"shadow-opacity": (
// --so is a local override composed into the $shadows expressions
// (scss/_config.scss) as var(--so, 1); same rationale as --sc above,
// it's non-inheriting so it doesn't leak to nested .shadow-* descendants.
property: --so,
class: shadow-opacity,
values: $shadow-opacities
),
// scss-docs-end utils-shadow-opacity
// scss-docs-start utils-position
"position": (
property: position,
Expand Down Expand Up @@ -225,7 +262,7 @@ $utilities: map.merge(
"border-width": (
property: border-width,
class: border,
values: $border-widths
values: map.merge($border-widths, (keyline: var(--border-width-keyline)))
),
"border-opacity": (
class: border,
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/header/Navigation.astro
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const activeSection = !layout ? 'Home' : title === 'Examples' ? 'Examples' : 'Do
{activeSection}
<svg class="bi bi-sm" aria-hidden="true"><use href="#chevron-expand"></use></svg>
</button>
<div class="menu">
<div class="menu border-keyline">
<a class:list={['menu-item', { active: !layout }]} href="/">
Home
{
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/header/Versions.astro
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if (layout === 'docs' && version === getConfig().docs_version) {
v{getConfig().docs_version}
<svg class="bi bi-sm" aria-hidden="true"><use href="#chevron-expand"></use></svg>
</button>
<div class="menu" id="bd-versions-menu">
<div class="menu border-keyline" id="bd-versions-menu">
<h6 class="menu-header">v6 releases</h6>
<a
class="menu-item active"
Expand Down
1 change: 1 addition & 0 deletions site/src/content/docs/guides/migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ Bootstrap 6 is a major release with many breaking changes to modernize our codeb
- Ratio helpers are now powered by the utility API and use simplified values without `calc()`.
- **State variants now use prefix syntax.** Pseudo-state utility classes like hover and focus variants now use a `state:class` prefix pattern (e.g., `hover:opacity-50` instead of `opacity-50-hover`), matching the responsive prefix convention.
- **Utility API cleanup.** Removed `css-var`, `css-variable-name`, and `local-vars` options from the utility API. Use the `property` map approach for CSS custom properties and `variables` for static CSS custom properties within utility classes.
- **Shadows are now layered and themeable.** `.shadow`, `.shadow-sm`, and `.shadow-lg` are multi-stop `box-shadow` declarations for a more natural falloff, plus new `.shadow-xs` and `.shadow-xl` sizes. New `.shadow-{color}` utilities (any theme color, plus `.shadow-current`, `.shadow-black`, `.shadow-white`) and `.shadow-opacity-{10-100}` utilities re-tint and scale shadows. Both set local, non-inheriting CSS variables (`--bs-sc`, `--bs-so`)—mirroring how `.border-{color}` utilities scope `--bs-bc`—so a shadow color or opacity override never leaks into nested `.shadow-*` elements. A global `--bs-shadow-color` and `--bs-shadow-strength` (deepened automatically in dark mode) drive the default, uncolored look. See the [Shadows docs]([[docsref:/utilities/shadows]]) for details.

### New components and plugins

Expand Down
3 changes: 2 additions & 1 deletion site/src/content/docs/utilities/border.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Or remove borders:

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>
<Example class="bd-example-border-utils" code={`<span class="border border-keyline"></span>
<span class="border border-1"></span>
<span class="border border-2"></span>
<span class="border border-3"></span>
<span class="border border-4"></span>
Expand Down
61 changes: 56 additions & 5 deletions site/src/content/docs/utilities/shadows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,72 @@ utility:

## Examples

While shadows on components are disabled by default in Bootstrap and can be enabled via `$enable-shadows`, you can also quickly add or remove a shadow with our `box-shadow` utility classes. Includes support for `.shadow-none` and three default sizes (which have associated variables to match).
While shadows on components are enabled by default via `$enable-shadows`, you can always manage shadows with our `box-shadow` utility classes. Includes support for `.shadow-none` and several sizes (which have associated variables to match).

<Example class="overflow-hidden" code={`<div class="shadow-none p-3 mb-5 bg-1 rounded">No shadow</div>
<div class="shadow-sm p-3 mb-5 bg-1 rounded">Small shadow</div>
<div class="shadow p-3 mb-5 bg-1 rounded">Regular shadow</div>
<div class="shadow-lg p-3 mb-5 bg-1 rounded">Larger shadow</div>`} />
For a more modern, smoother look, shadows are "layered" box-shadow declarations. More specifically:

- Each shadow size token contains multiple `box-shadow` values that provide a more natural falloff.
- Every layer derives its color from a global `--shadow-color` token by default, which `.shadow-{color}` utilities can override on a single element—without re-tinting any nested shadows—for quick, one-off accents.
- Similarly, a global `--shadow-strength` token (`1` for light mode, `2.4` for dark mode) deepens every shadow and doubles as a knob to scale all shadows at once.

<Example class="overflow-hidden vstack gap-7 lg:gap-9 p-7 lg:p-9 bg-1" code={`<div class="shadow-none p-3 bg-body rounded">No shadow</div>
<div class="shadow-sm p-3 bg-body rounded">Small shadow</div>
<div class="shadow p-3 bg-body rounded">Regular shadow</div>
<div class="shadow-lg p-3 bg-body rounded">Larger shadow</div>
<div class="shadow-xl p-3 bg-body rounded">Extra large shadow</div>`} />

### Shadow color

Change the color of a shadow with `.shadow-{color}` utilities for any theme color, plus `.shadow-current` (matches the element’s `color`), `.shadow-black`, and `.shadow-white`. Pair a color utility with a size utility. Colored shadows are intentionally subtle—use a larger size like `.shadow-xl` for a more visible tint.

<Callout>
The `--bs-sc` variable is registered with `@property` as non-inheriting (`inherits: false`), so shadow color utilities are scoped to the element they’re applied to and never cascade into nested elements or components. This is why `--bs-sc` is deliberately separate from the inheriting `--bs-shadow-color` design token that shadows resolve their default color from.
</Callout>

<Example class="overflow-hidden hstack flex-wrap gap-7 lg:gap-9 p-7 lg:p-9 bg-1" code={`<div class="shadow-xl shadow-primary p-3 bg-body rounded">Primary</div>
<div class="shadow-xl shadow-success p-3 bg-body rounded">Success</div>
<div class="shadow-xl shadow-danger p-3 bg-body rounded">Danger</div>
<div class="shadow-xl shadow-black p-3 bg-body rounded">Black</div>`} />

### Nested elements

Because shadow color and opacity utilities set the non-inheriting `--bs-sc` and `--bs-so` variables, a parent’s `.shadow-{color}` or `.shadow-opacity-*` doesn’t affect the shadows of nested elements. Below, the parent uses `.shadow-primary` while the nested `.shadow-xl` keeps the default shadow color.

<Example class="overflow-hidden p-7 lg:p-9 bg-1" code={`<div class="shadow-xl shadow-primary p-4 bg-body rounded-7">
Parent with .shadow-primary
<div class="shadow-xl p-3 mt-3 bg-body rounded-3">
Nested .shadow-xl keeps the default color.
</div>
</div>`} />

### Shadow opacity

Scale a shadow’s strength with `.shadow-opacity-{10-100}` utilities. Opacity is treated as a midpoint, not a ceiling: the default shadow matches `.shadow-opacity-50`, so lower values lighten it while higher values—up to `.shadow-opacity-100`—deepen it. This works with any size or color utility. Like `--bs-sc`, `--bs-so` is scoped to the element, so pair it with a size or color utility on the same element rather than expecting it to inherit.

<Example class="overflow-hidden hstack flex-wrap gap-7 lg:gap-9 p-7 lg:p-9 bg-1" code={`<div class="shadow-xl shadow-opacity-20 p-3 bg-1 rounded">20%</div>
<div class="shadow-xl shadow-opacity-50 p-3 bg-body rounded">50% (default)</div>
<div class="shadow-xl shadow-opacity-100 p-3 bg-body rounded">100%</div>`} />

Even works with custom shadow colors:

<Example class="overflow-hidden hstack flex-wrap gap-7 lg:gap-9 p-7 lg:p-9 bg-1" code={`<div class="shadow-xl shadow-opacity-20 shadow-primary p-3 bg-body rounded">20%</div>
<div class="shadow-xl shadow-opacity-50 shadow-primary p-3 bg-body rounded">50% (default)</div>
<div class="shadow-xl shadow-opacity-100 shadow-primary p-3 bg-body rounded">100%</div>`} />

## CSS

### Sass variables

<ScssDocs name="root-box-shadow-variables" file="scss/_root.scss" />

<ScssDocs name="box-shadow-variables" file="scss/_config.scss" />

### Sass utilities API

Shadow utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]([[docsref:/utilities/api#using-the-api]])

<ScssDocs name="utils-shadow" file="scss/_utilities.scss" />

<ScssDocs name="utils-shadow-color" file="scss/_utilities.scss" />

<ScssDocs name="utils-shadow-opacity" file="scss/_utilities.scss" />
2 changes: 1 addition & 1 deletion site/src/layouts/partials/ThemeToggler.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { layout } = Astro.props
<span class="visually-hidden">Toggle theme</span>
<svg class="bi bi-sm" aria-hidden="true"><use href="#chevron-expand"></use></svg>
</button>
<div class="menu" id="bd-theme-menu" aria-labelledby="bd-theme" style="--bs-menu-min-width: 8rem;">
<div class="menu border-keyline" id="bd-theme-menu" aria-labelledby="bd-theme" style="--bs-menu-min-width: 8rem;">
<button type="button" class="menu-item" data-bs-theme-value="light" aria-pressed="false">
<svg class="bi opacity-50" aria-hidden="true"><use href="#sun-fill"></use></svg>
Light
Expand Down
Loading