Repository navigation
Theme: Document accessibility responsibilities #79943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
||
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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);
}
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not sure this is necessary to highlight here — browser support expectations are the same for every other stylesheet in Gutenberg.
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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
|
||
| 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() ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to add another test with custom seed colours?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added tests for:
|
||
| 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 ); | ||
| } ); | ||
| } ); | ||
| } ); | ||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Added the link