Skip to content

Base Styles: Add wpds-var Sass helper for design token fallbacks#78698

Merged
mirka merged 14 commits into
trunkfrom
add-wpds-sass-fallbacks-base-styles
Jun 23, 2026
Merged

Base Styles: Add wpds-var Sass helper for design token fallbacks#78698
mirka merged 14 commits into
trunkfrom
add-wpds-sass-fallbacks-base-styles

Conversation

@mirka

@mirka mirka commented May 26, 2026

Copy link
Copy Markdown
Member

What?

  • Generate a Sass wpds-var() helper from the existing design token fallback map in @wordpress/theme.
  • Export it as @wordpress/theme/design-token-fallbacks.
  • Add outset-ring__focus mixin to @wordpress/base-styles that use the helper.

Why?

As @wordpress/base-styles adopts --wpds-* design tokens, it would be best if third-party Sass consumers didn't need to change their build setup with PostCSS build plugins or a loaded tokens stylesheet. Willing early adopters of design tokens can be expected to change their build setup to support it, but base-styles consumers never asked to use design tokens. So we should try not to disturb their build setup if possible.

This is kind of like the JS/CSS build-time fallback injection, but for Sass library code.

How?

  • Extend the Terrazzo ds-token-fallbacks plugin to emit src/prebuilt/scss/design-token-fallbacks.scss alongside the existing JS map.
  • Add a wpds-var($token) function that wraps var(--wpds-*, <fallback>) using the generated map.
  • Add @wordpress/theme as a dependency of @wordpress/base-styles.
  • Import the helper in _mixins.scss via @use "@wordpress/theme/src/prebuilt/scss/design-token-fallbacks" as wpds (legacy Sass resolution).
  • Consumers with NodePackageImporter can also use @use 'pkg:@wordpress/theme/design-token-fallbacks' as wpds.

Testing Instructions

Try using the new mixin in a @wordpress/components component, and see in dev tools how the fallbacks for the focus styles are present in Storybook.

Here's a handy diff for Button:

diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss
index 37ab3011955..7d11607f37d 100644
--- a/packages/components/src/button/style.scss
+++ b/packages/components/src/button/style.scss
@@ -1,5 +1,6 @@
 @use "sass:color";
 @use "@wordpress/base-styles/colors" as *;
+@use "@wordpress/base-styles/mixins";
 @use "@wordpress/base-styles/variables" as *;
 @use "../utils/theme-variables" as *;
 
@@ -11,6 +12,12 @@
  */
 
 .components-button {
+	@include mixins.outset-ring__rest();
+
+	&:focus:not(:active) {
+		@include mixins.outset-ring__focus();
+	}
+
 	display: inline-flex;
 	text-decoration: none;
 	font-family: inherit;
@@ -21,11 +28,6 @@
 	cursor: var(--wpds-cursor-control);
 	appearance: none;
 	background: none;
-
-	@media not ( prefers-reduced-motion ) {
-		transition: box-shadow 0.1s linear;
-	}
-
 	height: $button-size;
 	align-items: center;
 	box-sizing: border-box;
@@ -42,12 +44,6 @@
 		color: $components-color-accent;
 	}
 
-	// Focus.
-	// See https://github.com/WordPress/gutenberg/issues/13267 for more context on these selectors.
-	&:focus:not(:active) {
-		box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
-	}
-
 	/**
 	 * Primary button style.
 	 */
@@ -59,9 +55,6 @@
 		text-decoration: none;
 		text-shadow: none;
 
-		// Show the boundary of the button, in High Contrast Mode.
-		outline: 1px solid transparent;
-
 		&:hover:not(:disabled) {
 			background: $components-color-accent-darker-10;
 			color: $components-color-accent-inverted;
@@ -69,28 +62,21 @@
 
 		&:active:not(:disabled) {
 			background: $components-color-accent-darker-20;
-			border-color: $components-color-accent-darker-20;
 			color: $components-color-accent-inverted;
 		}
 
-		&:focus:not(:active) {
-			box-shadow: inset 0 0 0 1px $components-color-background, 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
-		}
-
 		&:disabled,
 		&:disabled:active:enabled,
 		&[aria-disabled="true"],
 		&[aria-disabled="true"]:enabled, // This catches a situation where a Button is aria-disabled, but not disabled.
 		&[aria-disabled="true"]:active:enabled {
+			&:focus:enabled {
+				@include mixins.outset-ring__focus();
+			}
+
 			// TODO: Prepare for theming (https://github.com/WordPress/gutenberg/pull/45466/files#r1030872724)
 			color: rgba($white, 0.4);
 			background: $components-color-accent;
-			border-color: $components-color-accent;
-			outline: none;
-
-			&:focus:enabled {
-				box-shadow: inset 0 0 0 1px $components-color-background, 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
-			}
 		}
 
 		&.is-busy,
@@ -107,7 +93,10 @@
 				$components-color-accent 70%
 			);
 			/* stylelint-enable */
-			border-color: $components-color-accent;
+		}
+
+		@media (forced-colors: active) {
+			outline: 1px solid ButtonText;
 		}
 	}
 
@@ -117,9 +106,6 @@
 
 	&.is-secondary,
 	&.is-tertiary {
-		// Show the boundary of the button, in High Contrast Mode.
-		outline: 1px solid transparent;
-
 		&:active:not(:disabled) {
 			box-shadow: none;
 		}
@@ -131,6 +117,10 @@
 			background: transparent;
 			transform: none;
 		}
+
+		@media (forced-colors: active) {
+			outline: 1px solid ButtonText;
+		}
 	}
 
 	/**
@@ -138,14 +128,22 @@
 	 */
 
 	&.is-secondary {
-		box-shadow: inset 0 0 0 $border-width $components-color-accent, 0 0 0 currentColor;
-		outline: 1px solid transparent; // Shown in high contrast mode.
+		@media not ( prefers-reduced-motion ) {
+			transition:
+				box-shadow 0.1s linear,
+				background 0.1s linear,
+				color 0.1s linear;
+		}
+
+		box-shadow:
+			inset 0 0 0 $border-width var(--wpds-color-stroke-interactive-brand),
+			0 0 0 currentColor;
 		white-space: nowrap;
 		color: $components-color-accent;
 		background: transparent;
 
 		&:hover:not(:disabled, [aria-disabled="true"], .is-pressed) {
-			box-shadow: inset 0 0 0 $border-width $components-color-accent-darker-20;
+			box-shadow: inset 0 0 0 $border-width var(--wpds-color-stroke-interactive-brand-active);
 			color: $components-color-accent-darker-20;
 			background: color-mix(in srgb, $components-color-accent 4%, transparent);
 		}
@@ -153,11 +151,12 @@
 		&:disabled:not(:focus),
 		&[aria-disabled="true"]:not(:focus),
 		&[aria-disabled="true"]:hover:not(:focus) {
-			box-shadow: inset 0 0 0 $border-width $gray-300;
+			box-shadow: inset 0 0 0 $border-width var(--wpds-color-stroke-interactive-neutral-disabled);
 		}
 
-		&:focus:not(:active) {
-			box-shadow: 0 0 0 currentColor inset, 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
+		@media (forced-colors: active) {
+			// Inset box-shadow borders are not reliable in forced-colors mode.
+			box-shadow: none;
 		}
 	}
 
@@ -183,15 +182,6 @@
 		p + & {
 			margin-left: -($grid-unit-15 * 0.5);
 		}
-
-		&:disabled,
-		&[aria-disabled="true"],
-		&[aria-disabled="true"]:hover {
-			&:not(:focus) {
-				box-shadow: none;
-				outline: none;
-			}
-		}
 	}
 
 	/**
@@ -210,10 +200,6 @@
 				color: color.adjust($alert-red, $lightness: -20%);
 			}
 
-			&:focus:not(:active) {
-				box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $alert-red;
-			}
-
 			&:active:not(:disabled, [aria-disabled="true"]) {
 				background: $gray-400;
 			}
@@ -224,6 +210,16 @@
 			}
 		}
 
+		&.is-secondary {
+			box-shadow:
+				inset 0 0 0 $border-width $components-color-accent,
+				0 0 0 currentColor;
+
+			&:hover:not(:disabled, [aria-disabled="true"]) {
+				box-shadow: inset 0 0 0 $border-width $components-color-accent-darker-20;
+			}
+		}
+
 		&.is-tertiary,
 		&.is-secondary {
 			&:hover:not(:disabled, [aria-disabled="true"]) {
@@ -263,8 +259,9 @@
 
 		height: auto;
 
-		&:focus {
+		&:focus:not(:active) {
 			border-radius: $radius-small;
+			text-decoration: none;
 		}
 
 		&:disabled,
@@ -273,14 +270,6 @@
 		}
 	}
 
-	// Windows High Contrast mode will show this outline, but not the box-shadow.
-	// This rule is placed after variant outline declarations (e.g. is-primary,
-	// is-secondary, is-tertiary) to ensure it wins by source order at the same
-	// specificity, while not using `:not(:active)` to avoid outline flicker.
-	&:focus {
-		outline: 3px solid transparent;
-	}
-
 	&:not(:disabled, [aria-disabled="true"]):active {
 		color: $components-color-foreground;
 	}
@@ -383,22 +372,6 @@
 			}
 		}
 
-		&:focus:not(:active) {
-			box-shadow: inset 0 0 0 1px $components-color-background, 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
-		}
-
-		// Windows High Contrast mode will show this outline, but not the box-shadow.
-		&:focus {
-			outline: 2px solid transparent;
-		}
-	}
-
-	// Hide focus outline on :active to provide click feedback in forced-colors
-	// mode, where box-shadow is not visible. Higher specificity (0,3,0)
-	// ensures this wins over variant outline declarations. Placed after
-	// .is-pressed to also win by source order at equal specificity (0,3,0).
-	&:focus:active {
-		outline: none;
 	}
 
 	svg {
Before After
button focus styles in dev tools, before button focus styles in dev tools, after

@github-actions github-actions Bot added [Package] Base styles /packages/base-styles [Package] Theme /packages/theme labels May 26, 2026
@mirka mirka self-assigned this May 26, 2026
@mirka mirka added the [Type] Enhancement A suggestion for improvement. label May 26, 2026
@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown

Size Change: +10.5 kB (+0.14%)

Total Size: 7.52 MB

📦 View Changed
Filename Size Change
build/styles/base-styles/admin-schemes-rtl.css 1.79 kB +105 B (+6.22%) 🔍
build/styles/base-styles/admin-schemes.css 1.79 kB +105 B (+6.22%) 🔍
build/styles/block-directory/style-rtl.css 2.09 kB +100 B (+5.02%) 🔍
build/styles/block-directory/style.css 2.09 kB +100 B (+5.02%) 🔍
build/styles/block-editor/content-rtl.css 5.65 kB +100 B (+1.8%)
build/styles/block-editor/content.css 5.65 kB +98 B (+1.76%)
build/styles/block-editor/style-rtl.css 18.9 kB +109 B (+0.58%)
build/styles/block-editor/style.css 18.9 kB +109 B (+0.58%)
build/styles/block-library/audio/style-rtl.css 1.04 kB +100 B (+10.58%) ⚠️
build/styles/block-library/audio/style.css 1.04 kB +100 B (+10.58%) ⚠️
build/styles/block-library/audio/theme-rtl.css 1.07 kB +101 B (+10.44%) ⚠️
build/styles/block-library/audio/theme.css 1.07 kB +101 B (+10.44%) ⚠️
build/styles/block-library/columns/style-rtl.css 1.4 kB +100 B (+7.71%) 🔍
build/styles/block-library/columns/style.css 1.4 kB +100 B (+7.71%) 🔍
build/styles/block-library/common-rtl.css 2.67 kB +96 B (+3.73%)
build/styles/block-library/common.css 2.69 kB +97 B (+3.75%)
build/styles/block-library/editor-rtl.css 12.6 kB +106 B (+0.85%)
build/styles/block-library/editor.css 12.6 kB +105 B (+0.84%)
build/styles/block-library/embed/style-rtl.css 1.39 kB +101 B (+7.84%) 🔍
build/styles/block-library/embed/style.css 1.39 kB +101 B (+7.84%) 🔍
build/styles/block-library/embed/theme-rtl.css 1.07 kB +101 B (+10.44%) ⚠️
build/styles/block-library/embed/theme.css 1.07 kB +101 B (+10.44%) ⚠️
build/styles/block-library/freeform/editor-rtl.css 1.22 kB +101 B (+9.05%) 🔍
build/styles/block-library/freeform/editor.css 1.22 kB +101 B (+9.05%) 🔍
build/styles/block-library/gallery/editor-rtl.css 1.56 kB +102 B (+6.98%) 🔍
build/styles/block-library/gallery/editor.css 1.56 kB +103 B (+7.05%) 🔍
build/styles/block-library/gallery/style-rtl.css 2.96 kB +106 B (+3.72%)
build/styles/block-library/gallery/style.css 2.96 kB +106 B (+3.72%)
build/styles/block-library/gallery/theme-rtl.css 1.04 kB +101 B (+10.73%) ⚠️
build/styles/block-library/gallery/theme.css 1.04 kB +101 B (+10.73%) ⚠️
build/styles/block-library/html/editor-rtl.css 1.39 kB +99 B (+7.66%) 🔍
build/styles/block-library/html/editor.css 1.4 kB +99 B (+7.6%) 🔍
build/styles/block-library/image/editor-rtl.css 1.74 kB +99 B (+6.02%) 🔍
build/styles/block-library/image/editor.css 1.74 kB +99 B (+6.03%) 🔍
build/styles/block-library/image/style-rtl.css 3.01 kB +98 B (+3.36%)
build/styles/block-library/image/style.css 3.02 kB +99 B (+3.39%)
build/styles/block-library/image/theme-rtl.css 1.07 kB +100 B (+10.3%) ⚠️
build/styles/block-library/image/theme.css 1.07 kB +100 B (+10.3%) ⚠️
build/styles/block-library/latest-posts/style-rtl.css 1.46 kB +102 B (+7.52%) 🔍
build/styles/block-library/latest-posts/style.css 1.47 kB +100 B (+7.29%) 🔍
build/styles/block-library/navigation-submenu/editor-rtl.css 1.22 kB +99 B (+8.84%) 🔍
build/styles/block-library/navigation-submenu/editor.css 1.22 kB +102 B (+9.11%) 🔍
build/styles/block-library/navigation/editor-rtl.css 3.38 kB +103 B (+3.14%)
build/styles/block-library/navigation/editor.css 3.39 kB +103 B (+3.13%)
build/styles/block-library/navigation/style-rtl.css 3.69 kB +101 B (+2.81%)
build/styles/block-library/navigation/style.css 3.69 kB +104 B (+2.9%)
build/styles/block-library/page-list/editor-rtl.css 1.28 kB +99 B (+8.39%) 🔍
build/styles/block-library/page-list/editor.css 1.28 kB +99 B (+8.39%) 🔍
build/styles/block-library/post-template/style-rtl.css 1.37 kB +102 B (+8.02%) 🔍
build/styles/block-library/post-template/style.css 1.37 kB +102 B (+8.02%) 🔍
build/styles/block-library/query/editor-rtl.css 1.4 kB +100 B (+7.7%) 🔍
build/styles/block-library/query/editor.css 1.4 kB +100 B (+7.7%) 🔍
build/styles/block-library/rss/style-rtl.css 1.21 kB +101 B (+9.13%) 🔍
build/styles/block-library/rss/style.css 1.22 kB +102 B (+9.12%) 🔍
build/styles/block-library/shortcode/editor-rtl.css 1.2 kB +97 B (+8.81%) 🔍
build/styles/block-library/shortcode/editor.css 1.2 kB +97 B (+8.81%) 🔍
build/styles/block-library/style-rtl.css 21.7 kB +100 B (+0.46%)
build/styles/block-library/style.css 21.9 kB +101 B (+0.46%)
build/styles/block-library/table/editor-rtl.css 1.35 kB +100 B (+7.99%) 🔍
build/styles/block-library/table/editor.css 1.35 kB +100 B (+7.99%) 🔍
build/styles/block-library/table/theme-rtl.css 1.08 kB +100 B (+10.15%) ⚠️
build/styles/block-library/table/theme.css 1.08 kB +100 B (+10.15%) ⚠️
build/styles/block-library/template-part/editor-rtl.css 1.32 kB +97 B (+7.92%) 🔍
build/styles/block-library/template-part/editor.css 1.32 kB +97 B (+7.92%) 🔍
build/styles/block-library/theme-rtl.css 1.69 kB +98 B (+6.15%) 🔍
build/styles/block-library/theme.css 1.69 kB +98 B (+6.14%) 🔍
build/styles/block-library/video/style-rtl.css 1.13 kB +100 B (+9.72%) ⚠️
build/styles/block-library/video/style.css 1.13 kB +100 B (+9.72%) ⚠️
build/styles/block-library/video/theme-rtl.css 1.07 kB +101 B (+10.44%) ⚠️
build/styles/block-library/video/theme.css 1.07 kB +101 B (+10.44%) ⚠️
build/styles/commands/style-rtl.css 2.17 kB +101 B (+4.87%) 🔍
build/styles/commands/style.css 2.17 kB +100 B (+4.83%) 🔍
build/styles/components/style-rtl.css 18.2 kB +101 B (+0.56%)
build/styles/components/style.css 18.3 kB +100 B (+0.55%)
build/styles/customize-widgets/style-rtl.css 2.46 kB +101 B (+4.29%)
build/styles/customize-widgets/style.css 2.45 kB +101 B (+4.3%)
build/styles/edit-post/classic-rtl.css 1.39 kB +101 B (+7.85%) 🔍
build/styles/edit-post/classic.css 1.41 kB +101 B (+7.7%) 🔍
build/styles/edit-post/style-rtl.css 3.92 kB +94 B (+2.46%)
build/styles/edit-post/style.css 3.93 kB +96 B (+2.51%)
build/styles/edit-site/style-rtl.css 22.3 kB +219 B (+0.99%)
build/styles/edit-site/style.css 22.3 kB +216 B (+0.98%)
build/styles/edit-widgets/style-rtl.css 4.96 kB +104 B (+2.14%)
build/styles/edit-widgets/style.css 4.96 kB +102 B (+2.1%)
build/styles/editor/style-rtl.css 31.1 kB +302 B (+0.98%)
build/styles/editor/style.css 31.1 kB +303 B (+0.98%)
build/styles/list-reusable-blocks/style-rtl.css 1.13 kB +99 B (+9.65%) ⚠️
build/styles/list-reusable-blocks/style.css 1.13 kB +98 B (+9.54%) ⚠️
build/styles/media-utils/style-rtl.css 2.25 kB +103 B (+4.8%) 🔍
build/styles/media-utils/style.css 2.25 kB +103 B (+4.8%) 🔍
build/styles/patterns/style-rtl.css 1.54 kB +101 B (+7.03%) 🔍
build/styles/patterns/style.css 1.54 kB +99 B (+6.89%) 🔍
build/styles/preferences/style-rtl.css 1.36 kB +99 B (+7.83%) 🔍
build/styles/preferences/style.css 1.36 kB +100 B (+7.92%) 🔍
build/styles/reusable-blocks/style-rtl.css 1.21 kB +99 B (+8.94%) 🔍
build/styles/reusable-blocks/style.css 1.21 kB +99 B (+8.94%) 🔍
build/styles/widgets/style-rtl.css 2.15 kB +97 B (+4.72%) 🔍
build/styles/widgets/style.css 2.15 kB +96 B (+4.67%) 🔍

compressed-size-action

@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown

Flaky tests detected in ded1182.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28006845124
📝 Reported issues:

@mirka mirka marked this pull request as ready for review May 27, 2026 18:56
@mirka mirka requested a review from a team as a code owner May 27, 2026 18:56
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: mirka <[email protected]>
Co-authored-by: ciampo <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@ciampo ciampo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

Pre-approving since the approach is solid (and I won't be around for a few days). Feel free to merge once feedback is addressed

Comment thread packages/theme/bin/terrazzo-plugin-ds-token-fallbacks/index.ts Outdated
Comment thread packages/theme/bin/terrazzo-plugin-ds-token-fallbacks/index.ts
Comment thread packages/base-styles/_mixins.scss
Comment thread packages/theme/README.md Outdated
@mirka mirka enabled auto-merge (squash) June 23, 2026 06:36
@mirka mirka merged commit 5439c7c into trunk Jun 23, 2026
45 of 46 checks passed
@mirka mirka deleted the add-wpds-sass-fallbacks-base-styles branch June 23, 2026 07:00
@github-actions github-actions Bot added this to the Gutenberg 23.5 milestone Jun 23, 2026
mirka added a commit that referenced this pull request Jun 23, 2026
mirka added a commit that referenced this pull request Jun 26, 2026
* Reapply "Base Styles: Add wpds-var Sass helper for design token fallbacks (#78698)" (#79429)

This reverts commit 3031bb0.

* Theme: Add Sass fallback helper root entrypoint (#79471)

* Theme: Add Sass fallback helper shim

* Theme: Fix Sass fallback helper imports

* Theme: Remove fallback Sass export conditions

Co-authored-by: mirka <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: aduth <[email protected]>

* Fix focus ring docblock formatting

* Update Sass token utility entrypoint

* Add changelog

---------

Co-authored-by: ciampo <[email protected]>
Co-authored-by: aduth <[email protected]>
Co-authored-by: mirka <[email protected]>
Co-authored-by: simison <[email protected]>
SainathPoojary pushed a commit to SainathPoojary/gutenberg that referenced this pull request Jun 29, 2026
* Reapply "Base Styles: Add wpds-var Sass helper for design token fallbacks (WordPress#78698)" (WordPress#79429)

This reverts commit 3031bb0.

* Theme: Add Sass fallback helper root entrypoint (WordPress#79471)

* Theme: Add Sass fallback helper shim

* Theme: Fix Sass fallback helper imports

* Theme: Remove fallback Sass export conditions

Co-authored-by: mirka <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: aduth <[email protected]>

* Fix focus ring docblock formatting

* Update Sass token utility entrypoint

* Add changelog

---------

Co-authored-by: ciampo <[email protected]>
Co-authored-by: aduth <[email protected]>
Co-authored-by: mirka <[email protected]>
Co-authored-by: simison <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Base styles /packages/base-styles [Package] Theme /packages/theme [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants