Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 4 additions & 0 deletions packages/base-styles/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
- `.dataviews-view-grid__card .dataviews-selection-checkbox`
- `.dataviews-view-table thead`

### Enhancements

- Add `outset-ring__rest` and `outset-ring__focus` mixins for outline-based focus rings using `--wpds-*` design tokens ([#78646](https://github.com/WordPress/gutenberg/pull/78646)).

## 8.0.0 (2026-05-14)

### Breaking Changes
Expand Down
23 changes: 23 additions & 0 deletions packages/base-styles/_mixins.scss

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hopefully we can gradually phase out the other focus style mixins in this file.

Comment thread
mirka marked this conversation as resolved.
Resolved

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.

Follow-up idea: use the new mixins in the @wordpress/components 's ExternalLink ?

Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,29 @@
outline-offset: 2px;
}

/**
* Outset focus ring.
*
* Standard focus rings for the WordPress Design System.
* Apply `outset-ring__rest` on the element and `outset-ring__focus` inside the
* relevant pseudo-class at the call site, e.g. `&:focus { @include outset-ring__focus(); }`.
*/

@mixin outset-ring__rest() {
@media not (prefers-reduced-motion) {
transition: outline 0.1s ease-out;
}

// Outline width must be kept at 0 even with a transparent color,
// or else the outline will be visible in forced-colors mode.
outline: 0 solid transparent;
outline-offset: 1px;
}

@mixin outset-ring__focus() {
outline: var(--wpds-border-width-focus) solid var(--wpds-color-stroke-focus-brand);
}


/**
* Applies editor left position to the selector passed as argument
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### Enhancements

- `Button`: Align focus rings with the design system ([#78646](https://github.com/WordPress/gutenberg/pull/78646)).
- `Tabs`, `TabPanel`: Align selected tab colors and indicators with `@wordpress/ui` `Tabs` ([#78418](https://github.com/WordPress/gutenberg/pull/78418)).
- `NoticeList`: Add vertical spacing between notices in a list.
- `Draggable`: Render the drag clone inside the `@wordpress/ui` compat overlay slot so it shares stacking with `@wordpress/ui` overlays opened mid-drag. Auto-enabled in WordPress environments; other hosts can opt in via `useEnableWpCompatOverlaySlot()` ([#78183](https://github.com/WordPress/gutenberg/pull/78183), [#78354](https://github.com/WordPress/gutenberg/pull/78354)).
Expand Down
119 changes: 46 additions & 73 deletions packages/components/src/button/style.scss
Original file line number Diff line number Diff line change
@@ -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 *;

Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -42,12 +44,6 @@
color: $components-color-accent;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll replace the tokens in a separate PR.

}

// Focus.
// See https://github.com/WordPress/gutenberg/issues/13267 for more context on these selectors.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed this issue link because they're no longer relevant.

&:focus:not(:active) {
box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
}

/**
* Primary button style.
*/
Expand All @@ -59,38 +55,28 @@
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;
}

&: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();
}
Comment thread
mirka marked this conversation as resolved.
Resolved

// 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;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

border-color was a no-op here. .components-button already sets border: 0, and primary never gives itself a non-zero border width, so border-color does not affect layout or appearance in normal mode.

Primary styling is background + color, not a border stroke (secondary uses inset box-shadow for that).

The forced-colors boundary did not come from border-color either. Trunk used outline: 1px solid transparent on primary (and outline: none on disabled). This PR moves that to @media (forced-colors: active) { outline: 1px solid ButtonText } on .is-primary. Removing border-color is unrelated to that change.

Same reasoning applies to the other removed border-color lines on primary (:active, is-busy): leftovers from when buttons may have used real borders, not something the current styles depend on.

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,
Expand All @@ -107,7 +93,10 @@
$components-color-accent 70%
);
/* stylelint-enable */
border-color: $components-color-accent;
}

@media (forced-colors: active) {
outline: 1px solid ButtonText;
}
}

Expand All @@ -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;
}
Expand All @@ -131,33 +117,46 @@
background: transparent;
transform: none;
}

@media (forced-colors: active) {
outline: 1px solid ButtonText;
}
}

/**
* Secondary button style.
*/

&.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;
}
Comment thread
mirka marked this conversation as resolved.
Resolved

box-shadow:
inset 0 0 0 $border-width var(--wpds-color-stroke-interactive-brand),
0 0 0 currentColor;
Comment on lines +125 to +131

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

box-shadow is now only for the border for the secondary variant.

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);
}

&: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;
}
}

Expand All @@ -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;
}
}
}

/**
Expand All @@ -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;
}
Expand All @@ -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"]) {
Expand Down Expand Up @@ -263,8 +259,9 @@

height: auto;

&:focus {
&:focus:not(:active) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Omitting active states to match other variants.

border-radius: $radius-small;
text-decoration: none;
}

&:disabled,
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down
Loading