Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions packages/theme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

### Documentation

- Document design token accessibility responsibilities and browser support expectations ([#79943](https://github.com/WordPress/gutenberg/pull/79943)).
- Document that `ThemeProvider` does not accept wrapper customization props ([#79763](https://github.com/WordPress/gutenberg/pull/79763)).
- Clarify the design token documentation entry points and keep the generated token guidance source internal ([#79829](https://github.com/WordPress/gutenberg/pull/79829)).
- Document intentional design token omissions in the design system tokens reference ([#79770](https://github.com/WordPress/gutenberg/pull/79770)).
Expand Down
11 changes: 11 additions & 0 deletions packages/theme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ For the best development experience, we recommend configuring the [Stylelint rul

If you reference `--wpds-*` tokens in CSS or JS/TS source, use the [build plugins](#build-plugins) to inject fallback values at build time so components render correctly even when the tokens stylesheet is not loaded. If you use `@wordpress/build`, these plugins are already enabled by default when `@wordpress/theme` is installed.

### Accessibility and Browser Support

The semantic color tokens are designed so the default foreground/background pairs used by the design system meet WCAG AA text contrast. Components still need to choose the correct semantic pair for the UI state they render, and must not rely on color alone to convey information.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"Worth linking WCAG AA text contrast" somewhere definitive, like this doc? I'd prefer something easier to digest, but it's non-profit/authorative so good in that way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍 Added the link


Design tokens do not replace component-level accessibility handling:

- **Forced colors:** components are still responsible for `forced-colors` overrides where native high-contrast rendering would otherwise hide borders, icons, focus rings, or state indicators.
- **Reduced motion:** motion tokens provide shared durations and easing curves, but consuming components are still responsible for respecting `prefers-reduced-motion` for non-essential animations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

WDYT about adding a code example here to educate folks better? Not everyone knows how to do this or cares to seek it, but if we just show it, it might become a habit.

@media (prefers-reduced-motion: no-preference) {
	.example {
		transition: opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-subtle);
	}
}

or

@media not (prefers-reduced-motion) {
	.example {
		transition: opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-subtle);
	}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Example added


The raw CSS tokens stylesheet targets modern evergreen browsers and may rely on modern CSS features such as `color-mix()`. For source processed by the provided build plugins, fallback values are injected at build time for configured CSS and JS/TS sources.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The raw CSS tokens stylesheet targets modern evergreen browsers

Not sure this is necessary to highlight here — browser support expectations are the same for every other stylesheet in Gutenberg.

For source processed by the provided build plugins, fallback values are injected at build time

This doesn't really fit within the "Accessibility and Browser Support" section heading, and I think it's already documented elsewhere in this readme? Maybe we can make this section just about accessibility.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the section about browser support and renamed section to "Accessibility"


## Theme Provider

The `ThemeProvider` is a React component that should wrap your application to provide design tokens and theme context to the child UI components. It accepts a set of customizable seed values and automatically generates a set of design tokens, which are exposed as CSS custom properties for use throughout the application.
Expand Down
118 changes: 118 additions & 0 deletions packages/theme/src/test/semantic-color-contrast.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { renderHook } from '@testing-library/react';
import { getContrast } from '../color-ramps/lib/color-utils';
import { useThemeProviderStyles } from '../use-theme-provider-styles';

const MINIMUM_TEXT_CONTRAST = 4.5;

const CONTRAST_PAIRS = [
{
background: '--wpds-color-background-surface-neutral',
foreground: '--wpds-color-foreground-content-neutral',
},
{
background: '--wpds-color-background-surface-neutral-strong',
foreground: '--wpds-color-foreground-content-neutral',
},
{
background: '--wpds-color-background-surface-neutral-weak',
foreground: '--wpds-color-foreground-content-neutral',
},
{
background: '--wpds-color-background-surface-neutral',
foreground: '--wpds-color-foreground-content-neutral-weak',
},
{
background: '--wpds-color-background-surface-info',
foreground: '--wpds-color-foreground-content-info',
},
{
background: '--wpds-color-background-surface-info-weak',
foreground: '--wpds-color-foreground-content-info-weak',
},
{
background: '--wpds-color-background-surface-success',
foreground: '--wpds-color-foreground-content-success',
},
{
background: '--wpds-color-background-surface-success-weak',
foreground: '--wpds-color-foreground-content-success-weak',
},
{
background: '--wpds-color-background-surface-warning',
foreground: '--wpds-color-foreground-content-warning',
},
{
background: '--wpds-color-background-surface-warning-weak',
foreground: '--wpds-color-foreground-content-warning-weak',
},
{
background: '--wpds-color-background-surface-caution',
foreground: '--wpds-color-foreground-content-caution',
},
{
background: '--wpds-color-background-surface-caution-weak',
foreground: '--wpds-color-foreground-content-caution-weak',
},
{
background: '--wpds-color-background-surface-error',
foreground: '--wpds-color-foreground-content-error',
},
{
background: '--wpds-color-background-surface-error-weak',
foreground: '--wpds-color-foreground-content-error-weak',
},
{
background: '--wpds-color-background-interactive-brand-strong',
foreground: '--wpds-color-foreground-interactive-brand-strong',
},
{
background: '--wpds-color-background-interactive-brand-strong-active',
foreground: '--wpds-color-foreground-interactive-brand-strong-active',
},
{
background: '--wpds-color-background-interactive-error-strong',
foreground: '--wpds-color-foreground-interactive-error-strong',
},
{
background: '--wpds-color-background-interactive-error-strong-active',
foreground: '--wpds-color-foreground-interactive-error-strong-active',
},
{
background: '--wpds-color-background-interactive-neutral-strong',
foreground: '--wpds-color-foreground-interactive-neutral-strong',
},
{
background: '--wpds-color-background-interactive-neutral-strong-active',
foreground: '--wpds-color-foreground-interactive-neutral-strong-active',
},
] as const;

function readToken(
styles: Record< string, string | number | undefined >,
token: string
) {
const value = styles[ token ];
if ( typeof value !== 'string' || value === '' ) {
throw new Error( `Missing semantic color token: ${ token }` );
}
return value;
}

describe( 'semantic color contrast', () => {
it( 'keeps critical foreground/background pairs above WCAG AA text contrast', () => {
const { result } = renderHook( () => useThemeProviderStyles() );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to add another test with custom seed colours?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added tests for:

  • default ThemeProvider seed colors
  • custom opaque primary / background seed colors

const styles = result.current.themeProviderStyles as Record<
string,
string | number | undefined
>;

CONTRAST_PAIRS.forEach( ( { foreground, background } ) => {
const foregroundValue = readToken( styles, foreground );
const backgroundValue = readToken( styles, background );

expect(
getContrast( foregroundValue, backgroundValue )
).toBeGreaterThanOrEqual( MINIMUM_TEXT_CONTRAST );
} );
} );
} );
Loading