Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8e0cf57
Add motion design tokens (duration and easing) to @wordpress/theme
jameskoster Mar 3, 2026
b17ec00
Adopt motion design tokens in Dialog, Modal, and Menu
jameskoster Mar 17, 2026
19f255d
Build tokens
jameskoster Mar 17, 2026
22936b7
Apply suggestion from @mirka
jameskoster Mar 18, 2026
ac08416
Move motion tokens story to root storybook folder
jameskoster Mar 18, 2026
ce10fc0
Update packages/components/src/utils/dropdown-motion.ts
jameskoster Mar 19, 2026
2bcddc6
Remove hardcoded ms values from duration token names
jameskoster Mar 19, 2026
6811911
Add components package changelog entry for motion token adoption
jameskoster Mar 19, 2026
c42b7f8
Add UI package changelog entry for motion token adoption
jameskoster Mar 19, 2026
ec5b305
Fix style lint: use inset-inline-start instead of left
jameskoster Mar 19, 2026
8d745f4
Rename easing tokens to use direction-first naming convention
jameskoster Mar 30, 2026
47baac9
Remove accelerate easing tokens, add gentle, use decelerate for all e…
jameskoster Mar 31, 2026
98a3dda
Use gentle easing for modal overlay and dialog backdrop
jameskoster Mar 31, 2026
39b4e87
Add motion tokens to theme package README documentation
jameskoster Apr 2, 2026
121f131
Rename easing tokens to use intent-based names
jameskoster Apr 14, 2026
2d54793
Fix Storybook TypeScript build for CSS module imports
jameskoster Apr 14, 2026
b06c288
Simplify motion easing tokens from four to three
jameskoster Apr 14, 2026
b039f89
Replace CSS module with inline styles in motion story
jameskoster Apr 14, 2026
8b5eef9
Update changelogs for motion token adoption
jameskoster Apr 14, 2026
fd9ec8a
Use style tag children instead of dangerouslySetInnerHTML
jameskoster May 6, 2026
e6198f2
Fix duration token $value format
jameskoster May 6, 2026
81c6d7e
Move motion token changelog entries to Unreleased
jameskoster May 11, 2026
09e8461
Add PR link to theme changelog motion tokens entry
jameskoster May 11, 2026
d6e208c
Document modal exit timing coupling to motion duration token
jameskoster May 11, 2026
5a15ee7
Modal: stagger backdrop fade-out before frame disappear animation
jameskoster May 11, 2026
36f6ed8
Storybook: fix motion durations caption (balanced easing token)
jameskoster May 11, 2026
fa149b6
Dialog: drop duplicate transition under [data-open]
jameskoster May 11, 2026
66945cb
Storybook: use UI primitives in Motion tokens story
jameskoster May 11, 2026
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
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Comment thread
jameskoster marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

### Internal

- `Modal`, `Menu`, `DropdownMenu`: Adopt `--wpds-motion-easing-*` and `--wpds-motion-duration-*` design tokens for animation timing ([#76097](https://github.com/WordPress/gutenberg/pull/76097)).
- Update `date-fns` dependency to `v4.1.0` ([#78057](https://github.com/WordPress/gutenberg/pull/78057)).
- Update code to comply with `eslint-plugin-react-hooks` v7 ([#69962](https://github.com/WordPress/gutenberg/pull/69962)).
- `SlotFill`: Add dependencies to `updateFill` effect ([#77907](https://github.com/WordPress/gutenberg/pull/77907)).
Expand Down
8 changes: 2 additions & 6 deletions packages/components/src/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ function UnforwardedModal(
};
}, [ bodyOpenClassName ] );

const { closeModal, frameRef, frameStyle, overlayClassname } =
useModalExitAnimation();
const { closeModal, frameRef, overlayClassname } = useModalExitAnimation();

// Calls the isContentScrollable callback when the Modal children container resizes.
useLayoutEffect( () => {
Expand Down Expand Up @@ -259,10 +258,7 @@ function UnforwardedModal(
sizeClass,
className
) }
style={ {
...frameStyle,
...style,
} }
style={ style }
ref={ useMergeRefs( [
frameRef,
constrainedTabbingRef,
Expand Down
10 changes: 5 additions & 5 deletions packages/components/src/modal/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
z-index: z-index(".components-modal__screen-overlay");
display: flex;
// This animates the appearance of the backdrop.
@include animation__fade-in();
@include animation__fade-in($speed: var(--wpds-motion-duration-sm), $easing: var(--wpds-motion-easing-subtle));

&.is-animating-out {
// Note: it's important that the fade-out animation doesn't end after the
// modal frame's disappear animation, because the component will be removed
// from the DOM when that animation ends.
@include animation__fade-out($delay: 80ms);
@include animation__fade-out($speed: var(--wpds-motion-duration-sm), $delay: var(--wpds-motion-duration-xs), $easing: var(--wpds-motion-easing-subtle));
}
}

Expand All @@ -47,7 +47,7 @@
// Animate the modal frame/contents appearing on the page.
animation-name: components-modal__appear-animation;
animation-fill-mode: forwards;
animation-timing-function: cubic-bezier(0.29, 0, 0, 1);
animation-timing-function: var(--wpds-motion-easing-expressive);

// Ensure all headings use the proper editor text color, overriding wp-admin common.css
h1,
Expand All @@ -57,12 +57,12 @@
}

@media not (prefers-reduced-motion) {
animation-duration: var(--modal-frame-animation-duration);
animation-duration: var(--wpds-motion-duration-md);
}

.components-modal__screen-overlay.is-animating-out & {
animation-name: components-modal__disappear-animation;
animation-timing-function: cubic-bezier(1, 0, 0.2, 1);
animation-timing-function: var(--wpds-motion-easing-expressive);
}

// Show a centered modal on bigger screens.
Expand Down
17 changes: 10 additions & 7 deletions packages/components/src/modal/use-modal-exit-animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ import warning from '@wordpress/warning';
*/
import { CONFIG } from '../utils';

// Animation duration (ms) extracted to JS in order to be used on a setTimeout.
const FRAME_ANIMATION_DURATION = CONFIG.transitionDuration;
const FRAME_ANIMATION_DURATION_NUMBER = Number.parseInt(
/**
* Milliseconds used as a fallback when racing `animationend` against a timeout.
*
* Sourced from `CONFIG.transitionDuration`. This value is implicitly coupled to
* the modal frame’s CSS `animation-duration` in `style.scss`, which uses
* `var(--wpds-motion-duration-md)`. If either the token, the SCSS, or
* `CONFIG.transitionDuration` changes, keep them aligned so exit timing stays correct.
*/
const FRAME_ANIMATION_DURATION_MS = Number.parseInt(
Comment thread
jameskoster marked this conversation as resolved.
CONFIG.transitionDuration
);

Expand Down Expand Up @@ -68,7 +74,7 @@ export function useModalExitAnimation() {
// Allow an extra 20% of the animation duration for the
// animationend event to fire, in case the animation frame is
// slightly delayes by some other events in the event loop.
FRAME_ANIMATION_DURATION_NUMBER * 1.2
FRAME_ANIMATION_DURATION_MS * 1.2
);
} );

Expand All @@ -91,9 +97,6 @@ export function useModalExitAnimation() {
return {
overlayClassname: isAnimatingOut ? 'is-animating-out' : undefined,
frameRef,
frameStyle: {
'--modal-frame-animation-duration': `${ FRAME_ANIMATION_DURATION }`,
},
closeModal,
};
}
3 changes: 3 additions & 0 deletions packages/components/src/utils/config-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export default Object.assign( {}, CONTROL_PROPS, {
surfaceBorderSubtleColor: 'rgba(0, 0, 0, 0.05)',
surfaceBackgroundTertiaryColor: COLORS.white,
surfaceColor: COLORS.white,
// Modal exit animation: `use-modal-exit-animation` parses this for the
// `animationend` timeout race; keep the numeric duration equal to
// `--wpds-motion-duration-md` on `.components-modal__frame` in modal/style.scss.
transitionDuration: '200ms',
transitionDurationFast: '160ms',
transitionDurationFaster: '120ms',
Expand Down
8 changes: 6 additions & 2 deletions packages/components/src/utils/dropdown-motion.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Motion configuration for dropdown-like popovers.
// Keep constants in sync with: packages/ui/src/utils/css/dropdown-motion.module.css
// Values should stay in sync with --wpds-motion-* design tokens.

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.

The duplication of values across tokens and various JS/TS implementations is something they should ideally tackle as a follow-up. Maybe the best choice for dropdown (and other typescript-based component styles) is to migrate away from Emotion, so that we can consume the CSS variables directly?


export const DROPDOWN_MOTION = Object.freeze( {
SLIDE_DISTANCE: 4,
SLIDE_DURATION: 200,
SLIDE_EASING: {
function: 'cubic-bezier',
args: [ 0, 0, 0, 1 ] as const,
args: [ 0.25, 0, 0, 1 ] as const,
},
FADE_DURATION: 80,
FADE_DURATION: 100,
FADE_EASING: {
function: 'linear',
},
Expand All @@ -24,6 +25,9 @@ const convertEasingToString = ( easing: {
return easing.function;
};

// Note: --wpds-* tokens can't be used here directly because the build-time
// fallback injection is not compatible with Emotion. This file is consumed
// by Menu's Emotion styles.
export const DROPDOWN_MOTION_CSS = Object.freeze( {
SLIDE_DISTANCE: `${ DROPDOWN_MOTION.SLIDE_DISTANCE }px`,
SLIDE_DURATION: `${ DROPDOWN_MOTION.SLIDE_DURATION }ms`,
Expand Down
4 changes: 4 additions & 0 deletions packages/theme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- Add `--wpds-motion-duration-*` and `--wpds-motion-easing-*` design tokens for standardizing animation timing across components. Easing tokens use intent-based names: `subtle`, `balanced`, and `expressive` ([#76097](https://github.com/WordPress/gutenberg/pull/76097)).

## 0.12.0 (2026-04-29)

## 0.11.0 (2026-04-15)
Expand Down
4 changes: 4 additions & 0 deletions packages/theme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The design system follows the [Design Tokens Community Group (DTCG)](https://des
| `typography.json` | Font family stacks, font sizes, and line heights |
| `border.json` | Border radius and width values |
| `elevation.json` | Shadow definitions for creating depth and layering |
| `motion.json` | Animation durations and easing curves |

Each JSON file contains both primitive and semantic token definitions in a hierarchical structure. These files are the source of truth for the design system and are processed during the build step to generate CSS custom properties and other output formats in `/src/prebuilt`.

Expand All @@ -88,6 +89,7 @@ Semantic tokens follow a consistent naming pattern:
| `dimension` | Spacing, sizing, and other measurable lengths (e.g., padding, margins, widths) |
| `border` | Border properties like radius and width |
| `elevation` | Shadow definitions for layering and depth |
| `motion` | Animation durations and easing curves |
| `typography` | Typography properties like font family, font size, and line-height |

**Property** is the specific design property being defined.
Expand All @@ -101,6 +103,8 @@ Semantic tokens follow a consistent naming pattern:
| `gap` | Spacing between elements |
| `radius` | Border radius for rounded corners |
| `width` | Border width |
| `duration` | Animation duration |
| `easing` | Animation easing curve |
| `font-size` | Font size |
| `font-family` | Font family |
| `font-weight` | Font weight |
Expand Down
13 changes: 13 additions & 0 deletions packages/theme/docs/tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ Do not edit directly.
| `--wpds-elevation-md` | For components that offer additional actions. Example: Menus, Command Palette |
| `--wpds-elevation-lg` | For components that confirm decisions or handle necessary interruptions. Example: Modals. |

### Motion

| Variable name | Description |
| --------------------------------- | --------------------------------------------------------------------------------------------------- |
| `--wpds-motion-duration-xs` | Micro-delays and transition offsets |
| `--wpds-motion-duration-sm` | Micro-interactions like focus rings and state changes |
| `--wpds-motion-duration-md` | Standard transitions like menus and popovers |
| `--wpds-motion-duration-lg` | Deliberate animations like slides and reveals |
| `--wpds-motion-duration-xl` | Extended animations like complex or multi-step transitions |
| `--wpds-motion-easing-subtle` | Nearly imperceptible easing for hover, color, and background transitions |
| `--wpds-motion-easing-balanced` | General-purpose easing for on-screen movement like resizing, morphing, and layout shifts |
| `--wpds-motion-easing-expressive` | Noticeable easing for enter/exit and spatial transitions like menus, popovers, dialogs, and drawers |

### Typography

| Variable name | Description |
Expand Down
16 changes: 16 additions & 0 deletions packages/theme/src/prebuilt/css/design-tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,22 @@
/* For components that confirm decisions or handle necessary interruptions. Example: Modals. */
--wpds-elevation-lg: 0 5px 15px 0 #00000014, 0 15px 27px 0 #00000012,
0 30px 36px 0 #0000000a, 0 50px 43px 0 #00000005;
/* Micro-delays and transition offsets */
--wpds-motion-duration-xs: 50ms;
/* Micro-interactions like focus rings and state changes */
--wpds-motion-duration-sm: 100ms;
/* Standard transitions like menus and popovers */
--wpds-motion-duration-md: 200ms;
/* Deliberate animations like slides and reveals */
--wpds-motion-duration-lg: 300ms;
/* Extended animations like complex or multi-step transitions */
--wpds-motion-duration-xl: 400ms;
/* Nearly imperceptible easing for hover, color, and background transitions */
--wpds-motion-easing-subtle: cubic-bezier( 0.15, 0, 0.15, 1 );
/* General-purpose easing for on-screen movement like resizing, morphing, and layout shifts */
--wpds-motion-easing-balanced: cubic-bezier( 0.4, 0, 0.2, 1 );
/* Noticeable easing for enter/exit and spatial transitions like menus, popovers, dialogs, and drawers */
--wpds-motion-easing-expressive: cubic-bezier( 0.25, 0, 0, 1 );
/* Headings font family */
--wpds-typography-font-family-heading: -apple-system, system-ui, 'Segoe UI',
'Roboto', 'Oxygen-Sans', 'Ubuntu', 'Cantarell', 'Helvetica Neue',
Expand Down
8 changes: 8 additions & 0 deletions packages/theme/src/prebuilt/js/design-token-fallbacks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ export default {
'0 1px 2px 0 #0000000d, 0 2px 3px 0 #0000000a, 0 6px 6px 0 #00000008, 0 8px 8px 0 #00000005',
'--wpds-elevation-xs':
'0 1px 1px 0 #00000008, 0 1px 2px 0 #00000005, 0 3px 3px 0 #00000005, 0 4px 4px 0 #00000003',
'--wpds-motion-duration-lg': '300ms',
'--wpds-motion-duration-md': '200ms',
'--wpds-motion-duration-sm': '100ms',
'--wpds-motion-duration-xl': '400ms',
'--wpds-motion-duration-xs': '50ms',
'--wpds-motion-easing-balanced': 'cubic-bezier(0.4, 0, 0.2, 1)',
'--wpds-motion-easing-expressive': 'cubic-bezier(0.25, 0, 0, 1)',
'--wpds-motion-easing-subtle': 'cubic-bezier(0.15, 0, 0.15, 1)',
'--wpds-typography-font-family-body':
'-apple-system, system-ui, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif',
'--wpds-typography-font-family-heading':
Expand Down
8 changes: 8 additions & 0 deletions packages/theme/src/prebuilt/js/design-tokens.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ export default [
'--wpds-elevation-sm',
'--wpds-elevation-md',
'--wpds-elevation-lg',
'--wpds-motion-duration-xs',
'--wpds-motion-duration-sm',
'--wpds-motion-duration-md',
'--wpds-motion-duration-lg',
'--wpds-motion-duration-xl',
'--wpds-motion-easing-subtle',
'--wpds-motion-easing-balanced',
'--wpds-motion-easing-expressive',
'--wpds-typography-font-family-heading',
'--wpds-typography-font-family-body',
'--wpds-typography-font-family-mono',
Expand Down
10 changes: 10 additions & 0 deletions packages/theme/src/prebuilt/ts/token-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export type GapSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
*/
export type SurfaceWidthSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';

/**
* Size scale for duration tokens.
*/
export type DurationSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';

/**
* Easing curve variants.
*/
export type Easing = 'subtle' | 'balanced' | 'expressive';

/**
* Size scale for border radius tokens.
*/
Expand Down
11 changes: 11 additions & 0 deletions packages/theme/terrazzo.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const config: Config = {
'./tokens/cursor.json',
'./tokens/dimension.json',
'./tokens/elevation.json',
'./tokens/motion.json',
'./tokens/typography.json',
],
outDir: './src/prebuilt',
Expand Down Expand Up @@ -126,6 +127,16 @@ const config: Config = {
description: 'Size scale for surface width tokens.',
patterns: [ /^wpds-dimension\.surface-width\.([^.]+)$/ ],
},
{
name: 'DurationSize',
description: 'Size scale for duration tokens.',
patterns: [ /^wpds-motion\.duration\.([^.]+)$/ ],
},
{
name: 'Easing',
description: 'Easing curve variants.',
patterns: [ /^wpds-motion\.easing\.([^.]+)$/ ],
},
{
name: 'BorderRadiusSize',
description: 'Size scale for border radius tokens.',
Expand Down
42 changes: 42 additions & 0 deletions packages/theme/tokens/motion.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"wpds-motion": {
"duration": {
"$type": "duration",
"xs": {
"$value": { "value": 50, "unit": "ms" },
"$description": "Micro-delays and transition offsets"
},
"sm": {
"$value": { "value": 100, "unit": "ms" },
"$description": "Micro-interactions like focus rings and state changes"
},
"md": {
"$value": { "value": 200, "unit": "ms" },
"$description": "Standard transitions like menus and popovers"
},
"lg": {
"$value": { "value": 300, "unit": "ms" },
"$description": "Deliberate animations like slides and reveals"
},
"xl": {
"$value": { "value": 400, "unit": "ms" },
"$description": "Extended animations like complex or multi-step transitions"
}
},
"easing": {
"$type": "cubicBezier",
"subtle": {
"$value": [ 0.15, 0, 0.15, 1 ],
"$description": "Nearly imperceptible easing for hover, color, and background transitions"
},
"balanced": {
"$value": [ 0.4, 0, 0.2, 1 ],
"$description": "General-purpose easing for on-screen movement like resizing, morphing, and layout shifts"
},
"expressive": {
"$value": [ 0.25, 0, 0, 1 ],
"$description": "Noticeable easing for enter/exit and spatial transitions like menus, popovers, dialogs, and drawers"
}
}
}
}
4 changes: 4 additions & 0 deletions packages/ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
- `Drawer`: Fade the popup elevation shadow alongside the slide ([#77800](https://github.com/WordPress/gutenberg/pull/77800)).
- `Drawer`: Allow mouse-drag swipe-dismiss in the popup-edge padding gutter ([#77800](https://github.com/WordPress/gutenberg/pull/77800)).

### Internal

- `Dialog`: Use `--wpds-motion-*` design tokens for animation duration and easing ([#76097](https://github.com/WordPress/gutenberg/pull/76097)).

## 0.12.0 (2026-04-29)

### Breaking Changes
Expand Down
12 changes: 3 additions & 9 deletions packages/ui/src/dialog/style.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}

@media not (prefers-reduced-motion) {
transition: opacity 0.2s ease-out;
transition: opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-subtle);
}
}

Expand Down Expand Up @@ -60,14 +60,8 @@

@media not (prefers-reduced-motion) {
transition:
opacity 0.2s cubic-bezier(1, 0, 0.2, 1),
transform 0.2s cubic-bezier(1, 0, 0.2, 1);

&[data-open] {
transition:
opacity 0.2s cubic-bezier(0.29, 0, 0, 1),
transform 0.2s cubic-bezier(0.29, 0, 0, 1);
}
opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-expressive),
transform var(--wpds-motion-duration-md) var(--wpds-motion-easing-expressive);
}

@media (min-width: 480px) {
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/utils/css/dropdown-motion.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
@layer wp-ui-utilities {
.dropdown-motion {
--wp-ui-dropdown-slide-distance: 4px;
--wp-ui-dropdown-slide-duration: 200ms;
--wp-ui-dropdown-slide-easing: cubic-bezier(0, 0, 0, 1);
--wp-ui-dropdown-fade-duration: 80ms;
--wp-ui-dropdown-slide-duration: var(--wpds-motion-duration-md);
--wp-ui-dropdown-slide-easing: var(--wpds-motion-easing-expressive);
--wp-ui-dropdown-fade-duration: var(--wpds-motion-duration-sm);
--wp-ui-dropdown-fade-easing: linear;

@media not ( prefers-reduced-motion ) {
Expand Down
Loading
Loading