From 12dac1ccc8159b71e3c0968d0e05c7c257fb6ae8 Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Thu, 5 Mar 2026 11:18:59 -0800 Subject: [PATCH 1/3] Update placeholder sizing, docs, and animations --- scss/_placeholder.scss | 28 ++++++++++++++----- .../content/docs/components/placeholder.mdx | 24 ++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/scss/_placeholder.scss b/scss/_placeholder.scss index 706ef7f9e218..bc70b41e6cf9 100644 --- a/scss/_placeholder.scss +++ b/scss/_placeholder.scss @@ -1,18 +1,29 @@ @use "config" as *; @use "functions" as *; +@use "mixins/border-radius" as *; @use "mixins/tokens" as *; $placeholder-tokens: () !default; +$placeholder-wave-tokens: () !default; // scss-docs-start placeholder-tokens // stylelint-disable-next-line scss/dollar-variable-default $placeholder-tokens: defaults( ( - --placeholder-opacity-max: .5, - --placeholder-opacity-min: .2, + --placeholder-opacity-max: .18, + --placeholder-opacity-min: .1, ), $placeholder-tokens ); + +// stylelint-disable-next-line scss/dollar-variable-default +$placeholder-wave-tokens: defaults( + ( + --placeholder-opacity-max: .5, + --placeholder-opacity-min: .25, + ), + $placeholder-wave-tokens +); // scss-docs-end placeholder-tokens @layer components { @@ -20,10 +31,11 @@ $placeholder-tokens: defaults( @include tokens($placeholder-tokens); display: inline-block; - min-height: 1em; + min-height: 1.25em; vertical-align: middle; cursor: wait; background-color: currentcolor; + @include border-radius(var(--radius-3)); opacity: var(--placeholder-opacity-max); &.btn::before { @@ -34,15 +46,15 @@ $placeholder-tokens: defaults( // Sizing .placeholder-xs { - min-height: .6em; + min-height: .75em; } .placeholder-sm { - min-height: .8em; + min-height: 1em; } .placeholder-lg { - min-height: 1.2em; + min-height: 1.75em; } // Animation @@ -65,7 +77,9 @@ $placeholder-tokens: defaults( } .placeholder-wave { - mask-image: linear-gradient(130deg, var(--black) 55%, rgb(0 0 0 / calc(1 - var(--placeholder-opacity-min))) 75%, var(--black) 95%); + @include tokens($placeholder-wave-tokens); + + mask-image: linear-gradient(110deg, var(--black) 40%, rgb(0 0 0 / calc(1 - var(--placeholder-opacity-max))) 50%, var(--black) 60%); mask-size: 200% 100%; animation: placeholder-wave 2s linear infinite; diff --git a/site/src/content/docs/components/placeholder.mdx b/site/src/content/docs/components/placeholder.mdx index 0323beb56c3d..7da7fdc0e5fc 100644 --- a/site/src/content/docs/components/placeholder.mdx +++ b/site/src/content/docs/components/placeholder.mdx @@ -39,7 +39,7 @@ In the example below, we take a typical card component and recreate it with plac

- + `} /> @@ -82,13 +82,13 @@ We apply additional styling to `.btn`s via `::before` to ensure the `height` is

- `} /> + `} /> The use of `aria-hidden="true"` only indicates that the element should be hidden to screen readers. The *loading* behavior of the placeholder depends on how authors will actually use the placeholder styles, how they plan to update things, etc. Some JavaScript code may be needed to *swap* the state of the placeholder and inform AT users of the update. -### Width +## Width You can change the `width` through grid column classes, width utilities, or inline styles. @@ -96,26 +96,26 @@ You can change the `width` through grid column classes, width utilities, or inli `} /> -### Color +## Color By default, the `placeholder` uses `currentColor`. This can be overridden with a custom color or utility class. + ` `, - ...getData('theme-colors').map((themeColor) => ``) + ...getData('theme-colors').map((themeColor) => ``) ]} /> -### Sizing +## Sizing The size of `.placeholder`s are based on the typographic style of the parent element. Customize them with sizing modifiers: `.placeholder-lg`, `.placeholder-sm`, or `.placeholder-xs`. - + `} /> -### Animation +## Animation Animate placeholders with `.placeholder-glow` or `.placeholder-wave` to better convey the perception of something being *actively* loaded. @@ -127,6 +127,12 @@ Animate placeholders with `.placeholder-glow` or `.placeholder-wave` to better c

`} /> +### Text + +Add `placeholder-wave` to an inline element with text to apply the placeholder animations to the text. + +Working on it…`} /> + ## CSS ### Variables From 7c3154c7bfbb3ae79e5790739980a1cfcc1c871b Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Fri, 25 Sep 2026 20:49:55 -0700 Subject: [PATCH 2/3] feat(placeholders): favor shimmer for loading states Use the wave animation for loading skeletons and inline status text. Move the old opacity effect to placeholder-pulse while keeping the placeholder-glow class as a compatibility alias. Update the component docs, migration guidance, migration skill, and docs search loader to use the new recommendation. --- scss/_placeholder.scss | 20 +++++++++++-------- site/src/assets/search.js | 2 +- .../content/docs/components/placeholder.mdx | 16 +++++++-------- site/src/content/docs/guides/migration.mdx | 1 + skills/bootstrap-v5-v6-migration/SKILL.md | 4 ++++ 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/scss/_placeholder.scss b/scss/_placeholder.scss index bc70b41e6cf9..1a83e9a9d621 100644 --- a/scss/_placeholder.scss +++ b/scss/_placeholder.scss @@ -19,8 +19,8 @@ $placeholder-tokens: defaults( // stylelint-disable-next-line scss/dollar-variable-default $placeholder-wave-tokens: defaults( ( - --placeholder-opacity-max: .5, - --placeholder-opacity-min: .25, + --placeholder-wave-opacity-max: 1, + --placeholder-wave-opacity-min: .4, ), $placeholder-wave-tokens ); @@ -58,9 +58,10 @@ $placeholder-wave-tokens: defaults( } // Animation + .placeholder-pulse, .placeholder-glow { .placeholder { - animation: placeholder-glow 2s ease-in-out infinite; + animation: placeholder-pulse 2s ease-in-out infinite; @if $enable-reduced-motion { @media (prefers-reduced-motion: reduce) { @@ -70,7 +71,7 @@ $placeholder-wave-tokens: defaults( } } - @keyframes placeholder-glow { + @keyframes placeholder-pulse { 50% { opacity: var(--placeholder-opacity-min); } @@ -79,12 +80,15 @@ $placeholder-wave-tokens: defaults( .placeholder-wave { @include tokens($placeholder-wave-tokens); - mask-image: linear-gradient(110deg, var(--black) 40%, rgb(0 0 0 / calc(1 - var(--placeholder-opacity-max))) 50%, var(--black) 60%); - mask-size: 200% 100%; - animation: placeholder-wave 2s linear infinite; + mask-image: linear-gradient(110deg, rgb(0 0 0 / var(--placeholder-wave-opacity-min)) 35%, rgb(0 0 0 / var(--placeholder-wave-opacity-max)) 50%, rgb(0 0 0 / var(--placeholder-wave-opacity-min)) 65%); + mask-repeat: no-repeat; + mask-position: 100% 0%; + mask-size: 250% 100%; + animation: placeholder-wave 1.6s linear infinite; @if $enable-reduced-motion { @media (prefers-reduced-motion: reduce) { + mask-image: none; animation: none; } } @@ -92,7 +96,7 @@ $placeholder-wave-tokens: defaults( @keyframes placeholder-wave { 100% { - mask-position: -200% 0%; + mask-position: 0% 0%; } } } diff --git a/site/src/assets/search.js b/site/src/assets/search.js index a6a012afaa6c..5814012e13f9 100644 --- a/site/src/assets/search.js +++ b/site/src/assets/search.js @@ -320,7 +320,7 @@ class BdSearchResults extends HTMLElement { _paintLoading() { this.innerHTML = ` -
+
${escapeHtml(instance.translate('searching') || 'Searching…')} ${Array.from({ length: 3 }, () => `
diff --git a/site/src/content/docs/components/placeholder.mdx b/site/src/content/docs/components/placeholder.mdx index 7da7fdc0e5fc..9d0a0795f196 100644 --- a/site/src/content/docs/components/placeholder.mdx +++ b/site/src/content/docs/components/placeholder.mdx @@ -29,10 +29,10 @@ In the example below, we take a typical card component and recreate it with plac