diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index d643b9b7ab4a1d..f4496cd49cbc42 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -7,9 +7,10 @@
- `ExternalLink`: No longer sets the `rel` attribute by default. Consumers relying on the previous behavior should pass `rel` explicitly ([#79743](https://github.com/WordPress/gutenberg/pull/79743)).
- `View`: The legacy Emotion `css` prop no longer applies styles and is now accepted as a no-op for compatibility. Use `style` for inline styles or `className` for CSS-based styling instead ([#79443](https://github.com/WordPress/gutenberg/pull/79443)).
- Components that compose Emotion style fragments with `cx()` should pass source-order-dependent fragments in a single `css()` call. Passing separate fragments can change override order after the following components stopped rendering styles through Emotion:
- - `Truncate` ([#79446](https://github.com/WordPress/gutenberg/pull/79446))
- - `Divider` ([#79444](https://github.com/WordPress/gutenberg/pull/79444))
+ - `Flex` ([#79450](https://github.com/WordPress/gutenberg/pull/79450))
- `Surface` ([#79445](https://github.com/WordPress/gutenberg/pull/79445))
+ - `Theme` ([#79447](https://github.com/WordPress/gutenberg/pull/79447))
+ - `Truncate` ([#79446](https://github.com/WordPress/gutenberg/pull/79446))
- `View` ([#79443](https://github.com/WordPress/gutenberg/pull/79443))
- The `__next40pxDefaultSize` prop is now true by default. The prop can be safely removed from the following:
- `BorderBoxControl` ([#79420](https://github.com/WordPress/gutenberg/pull/79420))
@@ -49,6 +50,7 @@
- Update `@ariakit/react` to `0.4.32` ([#79860](https://github.com/WordPress/gutenberg/pull/79860)).
- `Flex`: Migrate styles from Emotion to SCSS Modules ([#79450](https://github.com/WordPress/gutenberg/pull/79450)).
- `Surface`: Migrate styles from Emotion to SCSS Modules and use WPDS tokens for migrated visual values ([#79445](https://github.com/WordPress/gutenberg/pull/79445)).
+- `Theme`: Migrate styles from Emotion to SCSS Modules ([#79447](https://github.com/WordPress/gutenberg/pull/79447)).
- `Truncate`: Migrate styles from Emotion to SCSS Modules ([#79446](https://github.com/WordPress/gutenberg/pull/79446)).
- `View`: Migrate away from Emotion while preserving polymorphic `as` behavior and style cascade order ([#79443](https://github.com/WordPress/gutenberg/pull/79443)).
@@ -58,6 +60,8 @@
### Breaking Changes
+- Components that compose Emotion style fragments with `cx()` should pass source-order-dependent fragments in a single `css()` call. Passing separate fragments can change override order after the following components stopped rendering styles through Emotion:
+ - `Divider` ([#79444](https://github.com/WordPress/gutenberg/pull/79444))
- The `__next40pxDefaultSize` prop is now true by default. The prop can be safely removed from the following:
- `BoxControl` ([#79419](https://github.com/WordPress/gutenberg/pull/79419))
- `TextControl` ([#79386](https://github.com/WordPress/gutenberg/pull/79386))
diff --git a/packages/components/src/theme/index.tsx b/packages/components/src/theme/index.tsx
index fbb92f59278d1b..f75cbff2933753 100644
--- a/packages/components/src/theme/index.tsx
+++ b/packages/components/src/theme/index.tsx
@@ -1,16 +1,31 @@
-/**
- * WordPress dependencies
- */
+import clsx from 'clsx';
+import type { CSSProperties } from 'react';
+
import { useMemo } from '@wordpress/element';
-/**
- * Internal dependencies
- */
-import type { ThemeProps } from './types';
+import type { ThemeOutputValues, ThemeProps } from './types';
import type { WordPressComponentProps } from '../context';
-import { colorVariables, Wrapper } from './styles';
import { generateThemeVariables } from './color-algorithms';
-import { useCx } from '../utils';
+import styles from './style.module.scss';
+import { PolymorphicElement } from '../utils/polymorphic-element';
+
+const getColorVariables = ( {
+ colors,
+}: ThemeOutputValues ): CSSProperties => ( {
+ '--wp-components-color-accent': colors.accent,
+ '--wp-components-color-accent-darker-10': colors.accentDarker10,
+ '--wp-components-color-accent-darker-20': colors.accentDarker20,
+ '--wp-components-color-accent-inverted': colors.accentInverted,
+ '--wp-components-color-background': colors.background,
+ '--wp-components-color-foreground': colors.foreground,
+ '--wp-components-color-foreground-inverted': colors.foregroundInverted,
+ ...Object.fromEntries(
+ Object.entries( colors.gray ?? {} ).map( ( [ key, value ] ) => [
+ `--wp-components-color-gray-${ key }`,
+ value,
+ ] )
+ ),
+} );
/**
* `Theme` allows defining theme variables for components in the `@wordpress/components` package.
@@ -35,21 +50,31 @@ function Theme( {
accent,
background,
className,
+ style,
...props
}: WordPressComponentProps< ThemeProps, 'div', true > ) {
- const cx = useCx();
- const classes = useMemo(
+ const themeVariables = useMemo(
() =>
- cx(
- ...colorVariables(
- generateThemeVariables( { accent, background } )
- ),
- className
+ getColorVariables(
+ generateThemeVariables( { accent, background } )
),
- [ accent, background, className, cx ]
+ [ accent, background ]
+ );
+ const wrapperStyle = useMemo(
+ () => ( {
+ ...themeVariables,
+ ...style,
+ } ),
+ [ style, themeVariables ]
);
- return ;
+ return (
+
+ );
}
export default Theme;
diff --git a/packages/components/src/theme/style.module.scss b/packages/components/src/theme/style.module.scss
new file mode 100644
index 00000000000000..7dbb3487c7f9ca
--- /dev/null
+++ b/packages/components/src/theme/style.module.scss
@@ -0,0 +1,4 @@
+.wrapper {
+ /* stylelint-disable-next-line declaration-property-value-disallowed-list -- Preserve Theme's currentColor fallback when no foreground variable is generated. */
+ color: var(--wp-components-color-foreground, currentColor);
+}
diff --git a/packages/components/src/theme/styles.ts b/packages/components/src/theme/styles.ts
deleted file mode 100644
index 948c2d586649fb..00000000000000
--- a/packages/components/src/theme/styles.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * External dependencies
- */
-import styled from '@emotion/styled';
-import { css } from '@emotion/react';
-
-/**
- * Internal dependencies
- */
-import type { ThemeOutputValues } from './types';
-
-export const colorVariables = ( { colors }: ThemeOutputValues ) => {
- const shades = Object.entries( colors.gray || {} )
- .map( ( [ k, v ] ) => `--wp-components-color-gray-${ k }: ${ v };` )
- .join( '' );
-
- return [
- css`
- --wp-components-color-accent: ${ colors.accent };
- --wp-components-color-accent-darker-10: ${ colors.accentDarker10 };
- --wp-components-color-accent-darker-20: ${ colors.accentDarker20 };
- --wp-components-color-accent-inverted: ${ colors.accentInverted };
-
- --wp-components-color-background: ${ colors.background };
- --wp-components-color-foreground: ${ colors.foreground };
- --wp-components-color-foreground-inverted: ${ colors.foregroundInverted };
-
- ${ shades }
- `,
- ];
-};
-
-export const Wrapper = styled.div`
- color: var( --wp-components-color-foreground, currentColor );
-`;
diff --git a/packages/components/src/theme/test/index.tsx b/packages/components/src/theme/test/index.tsx
index 1b32fa1dfa2d66..18efa4e2d443ae 100644
--- a/packages/components/src/theme/test/index.tsx
+++ b/packages/components/src/theme/test/index.tsx
@@ -1,12 +1,6 @@
-/**
- * External dependencies
- */
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
-/**
- * Internal dependencies
- */
import Theme from '../';
type MyThemableComponentProps = {
@@ -25,6 +19,29 @@ const MyThemableComponent = ( props: MyThemableComponentProps ) => {
};
describe( 'Theme', () => {
+ it( 'should support the as prop', () => {
+ render( );
+
+ expect( screen.getByTestId( 'theme' ) ).toHaveProperty(
+ 'tagName',
+ 'SECTION'
+ );
+ } );
+
+ it( 'lets user styles override generated theme variables', () => {
+ render(
+
+ );
+
+ expect( screen.getByTestId( 'theme' ) ).toHaveStyle( {
+ '--wp-components-color-accent': '#654321',
+ } );
+ } );
+
describe( 'accent color', () => {
it( 'does not define the accent color (and its variations) as a CSS variable when the `accent` prop is undefined', () => {
render(
diff --git a/tools/eslint/suppressions.json b/tools/eslint/suppressions.json
index a561536ec018ed..3784567c512942 100644
--- a/tools/eslint/suppressions.json
+++ b/tools/eslint/suppressions.json
@@ -892,11 +892,6 @@
"count": 2
}
},
- "packages/components/src/theme/styles.ts": {
- "no-restricted-imports": {
- "count": 2
- }
- },
"packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts": {
"no-restricted-imports": {
"count": 2