A package that provides React UI components for the WordPress Design System, built on themeable design tokens.
While similar in scope to @wordpress/components, there are a few key differences:
@wordpress/componentsgrew organically as a collection of unrelated UI elements for WordPress screens. In contrast, this package is an implementation of a design system that guarantees user- and developer-facing cohesion between components.- Unlike
@wordpress/components, this package is not bundled as a WordPress script available on thewindow.wpglobal and is instead distributed as an npm package that follows semantic versioning for release changes.
This is a companion to the @wordpress/theme package that provides:
- Design Tokens: A comprehensive system of design tokens for colors, spacing, typography, and more
- Theme System: A flexible theming provider for consistent theming across applications
Installation
Install using NPM:
npm install @wordpress/ui
As an implementation of the design system and companion to the @wordpress/theme package, these components depend on CSS custom properties defined by the theme package. This is managed on your behalf in a WordPress admin page context, but you will need to install and include the base theme stylesheet yourself if you’re using the components in an application outside WordPress:
npm install @wordpress/theme
import '@wordpress/theme/design-tokens.css';
Usage
Basic Component Usage
import { Stack } from '@wordpress/ui';
function MyComponent() {
return (
<Stack gap="sm">
<div>Item 1</div>
<div>Item 2</div>
</Stack>
);
}
Core Design Principles
All components in the design system follow consistent patterns for maximum flexibility and developer experience:
Custom Rendering
Every component supports the render prop for complete control over the underlying HTML element:
import { Stack } from '@wordpress/ui';
function MyComponent() {
// Render Stack as a <section /> instead of the default <div />
return <Stack render={ <section /> }>{ /* ... */ }</Stack>;
}
Ref Forwarding
All components forward refs to their underlying DOM elements:
import { useRef } from '@wordpress/element';
import { Stack } from '@wordpress/ui';
function MyComponent() {
const stackRef = useRef< HTMLDivElement >( null );
return <Stack ref={ stackRef }>{ /* ... */ }</Stack>;
}
Automatic className Merging
Components merge provided className props with their internal styles:
import { Stack } from '@wordpress/ui';
function MyComponent() {
// Your custom CSS className is merged with component styles
return <Stack className="my-custom-class">{ /* ... */ }</Stack>;
}
Controlled and Uncontrolled Modes
Interactive components that manage internal state (such as open/closed, selected value, etc.) follow a consistent prop naming convention that supports both controlled and uncontrolled usage.
Prop naming pattern
For a given state x, the convention is:
| Prop | Purpose |
|---|---|
defaultX |
Sets the initial value in uncontrolled mode. The component manages subsequent state changes internally. |
x |
Sets the current value in controlled mode. The consumer is responsible for updating the value in response to changes. |
onXChange |
Callback invoked when the state changes. Receives the new value as its first argument. Works in both controlled and uncontrolled modes. |
For example, a component with an open/closed state would expose:
defaultOpen— initial open state (uncontrolled)open— current open state (controlled)onOpenChange— called when the open state changes
And a component with a selectable value would expose:
defaultValue— initial value (uncontrolled)value— current value (controlled)onValueChange— called when the value changes
Uncontrolled usage
In uncontrolled mode, the component manages its own state. Use defaultX to set the initial value, and optionally onXChange to react to changes:
import { Tabs } from '@wordpress/ui';
function MyTabs() {
return (
<Tabs.Root
defaultValue="tab1"
onValueChange={ ( value ) => console.log( value ) }
>
<Tabs.List>
<Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="tab1">Content 1</Tabs.Panel>
<Tabs.Panel value="tab2">Content 2</Tabs.Panel>
</Tabs.Root>
);
}
Controlled usage
In controlled mode, the consumer owns the state and passes it via x. State changes are handled through onXChange:
import { useState } from '@wordpress/element';
import { CollapsibleCard } from '@wordpress/ui';
function MyCard() {
const [ isOpen, setIsOpen ] = useState( false );
return (
<CollapsibleCard.Root open={ isOpen } onOpenChange={ setIsOpen }>
<CollapsibleCard.Header>Details</CollapsibleCard.Header>
<CollapsibleCard.Content>
Collapsible content here.
</CollapsibleCard.Content>
</CollapsibleCard.Root>
);
}
When both x and defaultX are provided, x takes precedence and the component behaves in controlled mode. When neither is provided, the component uses its own internal default (typically documented via a @default JSDoc tag on the defaultX prop).
Difference from native onChange
The onXChange callback is distinct from the native DOM onChange event handler. Native onChange fires a React.ChangeEvent tied to a specific DOM element, while onXChange provides the new value directly — making it simpler to use and consistent across all components, including compound and non-form components.
Components that wrap native form elements may still support native event handlers (like onChange, onInput) for interoperability, but onXChange is the recommended approach within this package.
Guidelines for component authors
When designing props for a new component:
- Always offer both controlled and uncontrolled modes when the component has user-facing state.
- Name the uncontrolled prop
defaultX, the controlled propx, and the callbackonXChange. - In JSDoc comments, indicate which mode each prop is for and cross-reference the alternative:
“`ts
/**- Whether the panel is currently open (controlled).
- To render an uncontrolled component, use the
defaultOpenprop instead.
*/
open?: boolean;
/** - Whether the panel is initially open (uncontrolled).
- @default false
*/
defaultOpen?: boolean;
/** - Event handler called when the open state changes.
*/
onOpenChange?: ( open: boolean ) => void;
“`
- Provide a
@defaultJSDoc tag for the uncontrolled prop when there is a sensible default.
Contributing to this package
This is an individual package that’s part of the Gutenberg project. The project is organized as a monorepo. It’s made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to npm and used by WordPress as well as other software projects.
To find out more about contributing to this package or Gutenberg as a whole, please read the project’s main contributor guide.