diff --git a/.changeset/friendly-sloths-jam.md b/.changeset/friendly-sloths-jam.md
new file mode 100644
index 000000000..a3bad3007
--- /dev/null
+++ b/.changeset/friendly-sloths-jam.md
@@ -0,0 +1,8 @@
+---
+'@compiled/babel-plugin': minor
+'@compiled/webpack-app': minor
+'@compiled/parcel-app': minor
+'@compiled/react': minor
+---
+
+Implement the `cssMap` API to enable library users to dynamically choose a varied set of CSS rules.
diff --git a/.loki/reference/chrome_laptop_css_map_Conditional_Styles.png b/.loki/reference/chrome_laptop_css_map_Conditional_Styles.png
new file mode 100644
index 000000000..85163862e
Binary files /dev/null and b/.loki/reference/chrome_laptop_css_map_Conditional_Styles.png differ
diff --git a/.loki/reference/chrome_laptop_css_map_Dynamic_Variant.png b/.loki/reference/chrome_laptop_css_map_Dynamic_Variant.png
new file mode 100644
index 000000000..d7102c156
Binary files /dev/null and b/.loki/reference/chrome_laptop_css_map_Dynamic_Variant.png differ
diff --git a/.loki/reference/chrome_laptop_css_map_Merge_Styles.png b/.loki/reference/chrome_laptop_css_map_Merge_Styles.png
new file mode 100644
index 000000000..630d0d864
Binary files /dev/null and b/.loki/reference/chrome_laptop_css_map_Merge_Styles.png differ
diff --git a/.loki/reference/chrome_laptop_css_map_Variant_As_Prop.png b/.loki/reference/chrome_laptop_css_map_Variant_As_Prop.png
new file mode 100644
index 000000000..34213399a
Binary files /dev/null and b/.loki/reference/chrome_laptop_css_map_Variant_As_Prop.png differ
diff --git a/examples/parcel/src/app.jsx b/examples/parcel/src/app.jsx
index 518bfba66..64c80178e 100644
--- a/examples/parcel/src/app.jsx
+++ b/examples/parcel/src/app.jsx
@@ -9,6 +9,7 @@ import '@compiled/react';
import { primary } from './constants';
import Annotated from './ui/annotated';
+import CSSMap from './ui/css-map';
import {
CustomFileExtensionStyled,
customFileExtensionCss,
@@ -29,5 +30,6 @@ export const App = () => (
Loading...}>
+ CSS Map
>
);
diff --git a/examples/parcel/src/ui/css-map.jsx b/examples/parcel/src/ui/css-map.jsx
new file mode 100644
index 000000000..1c7f3bbc1
--- /dev/null
+++ b/examples/parcel/src/ui/css-map.jsx
@@ -0,0 +1,12 @@
+import { css, cssMap } from '@compiled/react';
+
+const styles = cssMap({
+ danger: {
+ color: 'red',
+ },
+ success: {
+ color: 'green',
+ },
+});
+
+export default ({ variant, children }) =>
{children}
;
diff --git a/examples/webpack/src/app.jsx b/examples/webpack/src/app.jsx
index 3933d1664..9e40ec9ee 100644
--- a/examples/webpack/src/app.jsx
+++ b/examples/webpack/src/app.jsx
@@ -4,6 +4,7 @@ import { Suspense, lazy } from 'react';
import { primary } from './common/constants';
import Annotated from './ui/annotated';
+import CSSMap from './ui/css-map';
import {
CustomFileExtensionStyled,
customFileExtensionCss,
@@ -23,5 +24,6 @@ export const App = () => (
Custom File Extension Styled
Custom File Extension CSS
+ CSS Map
>
);
diff --git a/examples/webpack/src/ui/css-map.jsx b/examples/webpack/src/ui/css-map.jsx
new file mode 100644
index 000000000..1c7f3bbc1
--- /dev/null
+++ b/examples/webpack/src/ui/css-map.jsx
@@ -0,0 +1,12 @@
+import { css, cssMap } from '@compiled/react';
+
+const styles = cssMap({
+ danger: {
+ color: 'red',
+ },
+ success: {
+ color: 'green',
+ },
+});
+
+export default ({ variant, children }) => {children}
;
diff --git a/packages/babel-plugin/src/babel-plugin.ts b/packages/babel-plugin/src/babel-plugin.ts
index 3e4635315..10d2e0a1c 100644
--- a/packages/babel-plugin/src/babel-plugin.ts
+++ b/packages/babel-plugin/src/babel-plugin.ts
@@ -8,6 +8,7 @@ import * as t from '@babel/types';
import { unique, preserveLeadingComments } from '@compiled/utils';
import { visitClassNamesPath } from './class-names';
+import { visitCssMapPath } from './css-map';
import { visitCssPropPath } from './css-prop';
import { visitStyledPath } from './styled';
import type { State } from './types';
@@ -20,6 +21,7 @@ import {
isCompiledKeyframesTaggedTemplateExpression,
isCompiledStyledCallExpression,
isCompiledStyledTaggedTemplateExpression,
+ isCompiledCSSMapCallExpression,
} from './utils/is-compiled';
import { normalizePropsUsage } from './utils/normalize-props-usage';
@@ -39,6 +41,7 @@ export default declare((api) => {
inherits: jsxSyntax,
pre() {
this.sheets = {};
+ this.cssMap = {};
let cache: Cache;
if (this.opts.cache === true) {
@@ -150,7 +153,7 @@ export default declare((api) => {
return;
}
- (['styled', 'ClassNames', 'css', 'keyframes'] as const).forEach((apiName) => {
+ (['styled', 'ClassNames', 'css', 'keyframes', 'cssMap'] as const).forEach((apiName) => {
if (
state.compiledImports &&
t.isIdentifier(specifier.node?.imported) &&
@@ -171,6 +174,11 @@ export default declare((api) => {
path: NodePath | NodePath,
state: State
) {
+ if (isCompiledCSSMapCallExpression(path.node, state)) {
+ visitCssMapPath(path, { context: 'root', state, parentPath: path });
+ return;
+ }
+
const hasStyles =
isCompiledCSSTaggedTemplateExpression(path.node, state) ||
isCompiledStyledTaggedTemplateExpression(path.node, state) ||
diff --git a/packages/babel-plugin/src/css-map/__tests__/index.test.ts b/packages/babel-plugin/src/css-map/__tests__/index.test.ts
new file mode 100644
index 000000000..d93df96f9
--- /dev/null
+++ b/packages/babel-plugin/src/css-map/__tests__/index.test.ts
@@ -0,0 +1,135 @@
+import type { TransformOptions } from '../../test-utils';
+import { transform as transformCode } from '../../test-utils';
+import { ErrorMessages } from '../index';
+
+describe('css map', () => {
+ const transform = (code: string, opts: TransformOptions = {}) =>
+ transformCode(code, { pretty: false, ...opts });
+
+ const styles = `{
+ danger: {
+ color: 'red',
+ backgroundColor: 'red'
+ },
+ success: {
+ color: 'green',
+ backgroundColor: 'green'
+ }
+ }`;
+
+ it('should transform css map', () => {
+ const actual = transform(`
+ import { cssMap } from '@compiled/react';
+
+ const styles = cssMap(${styles});
+ `);
+
+ expect(actual).toInclude(
+ 'const styles={danger:"_syaz5scu _bfhk5scu",success:"_syazbf54 _bfhkbf54"};'
+ );
+ });
+
+ it('should error out if variants are not defined at the top-most scope of the module.', () => {
+ expect(() => {
+ transform(`
+ import { cssMap } from '@compiled/react';
+
+ const styles = {
+ map1: cssMap(${styles}),
+ }
+ `);
+ }).toThrow(ErrorMessages.DEFINE_MAP);
+
+ expect(() => {
+ transform(`
+ import { cssMap } from '@compiled/react';
+
+ const styles = () => cssMap(${styles})
+ `);
+ }).toThrow(ErrorMessages.DEFINE_MAP);
+ });
+
+ it('should error out if cssMap receives more than one argument', () => {
+ expect(() => {
+ transform(`
+ import { cssMap } from '@compiled/react';
+
+ const styles = cssMap(${styles}, ${styles})
+ `);
+ }).toThrow(ErrorMessages.NUMBER_OF_ARGUMENT);
+ });
+
+ it('should error out if cssMap does not receive an object', () => {
+ expect(() => {
+ transform(`
+ import { cssMap } from '@compiled/react';
+
+ const styles = cssMap('color: red')
+ `);
+ }).toThrow(ErrorMessages.ARGUMENT_TYPE);
+ });
+
+ it('should error out if spread element is used', () => {
+ expect(() => {
+ transform(`
+ import { css, cssMap } from '@compiled/react';
+
+ const styles = cssMap({
+ ...base
+ });
+ `);
+ }).toThrow(ErrorMessages.NO_SPREAD_ELEMENT);
+ });
+
+ it('should error out if object method is used', () => {
+ expect(() => {
+ transform(`
+ import { css, cssMap } from '@compiled/react';
+
+ const styles = cssMap({
+ danger() {}
+ });
+ `);
+ }).toThrow(ErrorMessages.NO_OBJECT_METHOD);
+ });
+
+ it('should error out if variant object is dynamic', () => {
+ expect(() => {
+ transform(`
+ import { css, cssMap } from '@compiled/react';
+
+ const styles = cssMap({
+ danger: otherStyles
+ });
+ `);
+ }).toThrow(ErrorMessages.STATIC_VARIANT_OBJECT);
+ });
+
+ it('should error out if styles include runtime variables', () => {
+ expect(() => {
+ transform(`
+ import { css, cssMap } from '@compiled/react';
+
+ const styles = cssMap({
+ danger: {
+ color: canNotBeStaticallyEvulated
+ }
+ });
+ `);
+ }).toThrow(ErrorMessages.STATIC_VARIANT_OBJECT);
+ });
+
+ it('should error out if styles include conditional CSS', () => {
+ expect(() => {
+ transform(`
+ import { css, cssMap } from '@compiled/react';
+
+ const styles = cssMap({
+ danger: {
+ color: canNotBeStaticallyEvulated ? 'red' : 'blue'
+ }
+ });
+ `);
+ }).toThrow(ErrorMessages.STATIC_VARIANT_OBJECT);
+ });
+});
diff --git a/packages/babel-plugin/src/css-map/index.ts b/packages/babel-plugin/src/css-map/index.ts
new file mode 100644
index 000000000..21857c7c4
--- /dev/null
+++ b/packages/babel-plugin/src/css-map/index.ts
@@ -0,0 +1,153 @@
+import type { NodePath } from '@babel/core';
+import * as t from '@babel/types';
+
+import type { Metadata } from '../types';
+import { buildCodeFrameError } from '../utils/ast';
+import { buildCss } from '../utils/css-builders';
+import { transformCssItems } from '../utils/transform-css-items';
+
+// The messages are exported for testing.
+export enum ErrorMessages {
+ NO_TAGGED_TEMPLATE = 'cssMap function cannot be used as a tagged template expression.',
+ NUMBER_OF_ARGUMENT = 'cssMap function can only receive one argument.',
+ ARGUMENT_TYPE = 'cssMap function can only receive an object.',
+ DEFINE_MAP = 'CSS Map must be declared at the top-most scope of the module.',
+ NO_SPREAD_ELEMENT = 'Spread element is not supported in CSS Map.',
+ NO_OBJECT_METHOD = 'Object method is not supported in CSS Map.',
+ STATIC_VARIANT_OBJECT = 'The variant object must be statically defined.',
+}
+
+const createErrorMessage = (message: string): string => {
+ return `
+${message}
+To correctly implement a CSS Map, follow the syntax below:
+
+\`\`\`
+import { css, cssMap } from '@compiled/react';
+const borderStyleMap = cssMap({
+ none: { borderStyle: 'none' },
+ solid: { borderStyle: 'solid' },
+});
+const Component = ({ borderStyle }) =>
+\`\`\`
+ `;
+};
+
+/**
+ * Takes `cssMap` function expression and then transforms it to a record of class names and sheets.
+ *
+ * For example:
+ * ```
+ * const styles = cssMap({
+ * none: { color: 'red' },
+ * solid: { color: 'green' },
+ * });
+ * ```
+ * gets transformed to
+ * ```
+ * const styles = {
+ * danger: "_syaz5scu",
+ * success: "_syazbf54",
+ * };
+ * ```
+ *
+ * @param path {NodePath} The path to be evaluated.
+ * @param meta {Metadata} Useful metadata that can be used during the transformation
+ */
+export const visitCssMapPath = (
+ path: NodePath | NodePath,
+ meta: Metadata
+): void => {
+ // We don't support tagged template expressions.
+ if (t.isTaggedTemplateExpression(path.node)) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.DEFINE_MAP),
+ path.node,
+ meta.parentPath
+ );
+ }
+
+ // We need to ensure CSS Map is declared at the top-most scope of the module.
+ if (!t.isVariableDeclarator(path.parent) || !t.isIdentifier(path.parent.id)) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.DEFINE_MAP),
+ path.node,
+ meta.parentPath
+ );
+ }
+
+ // We need to ensure cssMap receives only one argument.
+ if (path.node.arguments.length !== 1) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.NUMBER_OF_ARGUMENT),
+ path.node,
+ meta.parentPath
+ );
+ }
+
+ // We need to ensure the argument is an objectExpression.
+ if (!t.isObjectExpression(path.node.arguments[0])) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.ARGUMENT_TYPE),
+ path.node,
+ meta.parentPath
+ );
+ }
+
+ const totalSheets: string[] = [];
+ path.replaceWith(
+ t.objectExpression(
+ path.node.arguments[0].properties.map((property) => {
+ if (t.isSpreadElement(property)) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.NO_SPREAD_ELEMENT),
+ property.argument,
+ meta.parentPath
+ );
+ }
+
+ if (t.isObjectMethod(property)) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.NO_OBJECT_METHOD),
+ property.key,
+ meta.parentPath
+ );
+ }
+
+ if (!t.isObjectExpression(property.value)) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.STATIC_VARIANT_OBJECT),
+ property.value,
+ meta.parentPath
+ );
+ }
+
+ const { css, variables } = buildCss(property.value, meta);
+
+ if (variables.length) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.STATIC_VARIANT_OBJECT),
+ property.value,
+ meta.parentPath
+ );
+ }
+
+ const { sheets, classNames } = transformCssItems(css, meta);
+ totalSheets.push(...sheets);
+
+ if (classNames.length !== 1) {
+ throw buildCodeFrameError(
+ createErrorMessage(ErrorMessages.STATIC_VARIANT_OBJECT),
+ property,
+ meta.parentPath
+ );
+ }
+
+ return t.objectProperty(property.key, classNames[0]);
+ })
+ )
+ );
+
+ // We store sheets in the meta state so that we can use it later to generate Compiled component.
+ meta.state.cssMap[path.parent.id.name] = totalSheets;
+};
diff --git a/packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts b/packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts
new file mode 100644
index 000000000..638a62d12
--- /dev/null
+++ b/packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts
@@ -0,0 +1,85 @@
+import type { TransformOptions } from '../../test-utils';
+import { transform as transformCode } from '../../test-utils';
+
+describe('css map behaviour', () => {
+ beforeAll(() => {
+ process.env.AUTOPREFIXER = 'off';
+ });
+
+ afterAll(() => {
+ delete process.env.AUTOPREFIXER;
+ });
+
+ const transform = (code: string, opts: TransformOptions = {}) =>
+ transformCode(code, { pretty: false, ...opts });
+
+ const styles = `
+ import { css, cssMap } from '@compiled/react';
+
+ const styles = cssMap({
+ danger: {
+ color: 'red',
+ backgroundColor: 'red'
+ },
+ success: {
+ color: 'green',
+ backgroundColor: 'green'
+ }
+ });
+ `;
+
+ it('should evaluate css map with various syntactic patterns', () => {
+ const actual = transform(
+ `
+ ${styles}
+ ;
+ `,
+ { pretty: true }
+ );
+
+ expect(actual).toMatchInlineSnapshot(`
+ "import * as React from "react";
+ import { ax, ix, CC, CS } from "@compiled/react/runtime";
+ const _5 = "._syaz13q2{color:blue}";
+ const _4 = "._bfhkbf54{background-color:green}";
+ const _3 = "._syazbf54{color:green}";
+ const _2 = "._bfhk5scu{background-color:red}";
+ const _ = "._syaz5scu{color:red}";
+ const styles = {
+ danger: "_syaz5scu _bfhk5scu",
+ success: "_syazbf54 _bfhkbf54",
+ };
+
+ {[_, _2, _3, _4, _5]}
+ {
+
+ }
+ ;
+ "
+ `);
+ });
+});
diff --git a/packages/babel-plugin/src/types.ts b/packages/babel-plugin/src/types.ts
index 0a6b2a95a..a796da317 100644
--- a/packages/babel-plugin/src/types.ts
+++ b/packages/babel-plugin/src/types.ts
@@ -98,6 +98,7 @@ export interface State extends PluginPass {
css?: string;
keyframes?: string;
styled?: string;
+ cssMap?: string;
};
importedCompiledImports?: {
@@ -141,6 +142,11 @@ export interface State extends PluginPass {
* Files that have been included in this pass.
*/
includedFiles: string[];
+
+ /**
+ * Holds a record of currently evaluated CSS Map and its sheets in the module.
+ */
+ cssMap: Record;
}
interface CommonMetadata {
diff --git a/packages/babel-plugin/src/utils/css-builders.ts b/packages/babel-plugin/src/utils/css-builders.ts
index 062f6d7e5..0691ffe79 100644
--- a/packages/babel-plugin/src/utils/css-builders.ts
+++ b/packages/babel-plugin/src/utils/css-builders.ts
@@ -28,9 +28,33 @@ import type {
CssItem,
LogicalCssItem,
SheetCssItem,
+ CssMapItem,
PartialBindingWithMeta,
} from './types';
+/**
+ * Retrieves the leftmost identity from a given expression.
+ *
+ * For example:
+ * Given a member expression "colors.primary.500", the function will return "colors".
+ *
+ * @param expression The expression to be evaluated.
+ * @returns {string} The leftmost identity in the expression.
+ */
+const findBindingIdentifier = (
+ expression: t.Expression | t.V8IntrinsicIdentifier
+): t.Identifier | undefined => {
+ if (t.isIdentifier(expression)) {
+ return expression;
+ } else if (t.isCallExpression(expression)) {
+ return findBindingIdentifier(expression.callee);
+ } else if (t.isMemberExpression(expression)) {
+ return findBindingIdentifier(expression.object);
+ }
+
+ return undefined;
+};
+
/**
* Will normalize the value of a `content` CSS property to ensure it has quotations around it.
* This is done to replicate both how Styled Components behaves,
@@ -804,6 +828,13 @@ export const buildCss = (node: t.Expression | t.Expression[], meta: Metadata): C
}
if (t.isMemberExpression(node)) {
+ const bindingIdentifier = findBindingIdentifier(node);
+ if (bindingIdentifier && meta.state.cssMap[bindingIdentifier.name]) {
+ return {
+ css: [{ type: 'map', expression: node, name: bindingIdentifier.name, css: '' }],
+ variables: [],
+ };
+ }
const { value, meta: updatedMeta } = evaluateExpression(node, meta);
return buildCss(value, updatedMeta);
}
@@ -877,6 +908,13 @@ export const buildCss = (node: t.Expression | t.Expression[], meta: Metadata): C
};
}
+ if (item.type === 'map') {
+ return {
+ ...item,
+ expression: t.logicalExpression(node.operator, expression, item.expression),
+ } as CssMapItem;
+ }
+
const logicalItem: LogicalCssItem = {
type: 'logical',
css: getItemCss(item),
diff --git a/packages/babel-plugin/src/utils/is-compiled.ts b/packages/babel-plugin/src/utils/is-compiled.ts
index 65a7ecf34..ddc022018 100644
--- a/packages/babel-plugin/src/utils/is-compiled.ts
+++ b/packages/babel-plugin/src/utils/is-compiled.ts
@@ -47,6 +47,21 @@ export const isCompiledKeyframesCallExpression = (
t.isIdentifier(node.callee) &&
node.callee.name === state.compiledImports?.keyframes;
+/**
+ * Returns `true` if the node is using `cssMap` from `@compiled/react` as a call expression
+ *
+ * @param node {t.Node} The node that is being checked
+ * @param state {State} Plugin state
+ * @returns {boolean} Whether the node is a compiled cssMap
+ */
+export const isCompiledCSSMapCallExpression = (
+ node: t.Node,
+ state: State
+): node is t.CallExpression =>
+ t.isCallExpression(node) &&
+ t.isIdentifier(node.callee) &&
+ node.callee.name === state.compiledImports?.cssMap;
+
/**
* Returns `true` if the node is using `keyframes` from `@compiled/react` as a tagged template expression
*
diff --git a/packages/babel-plugin/src/utils/transform-css-items.ts b/packages/babel-plugin/src/utils/transform-css-items.ts
index 02a1cc131..196a3e199 100644
--- a/packages/babel-plugin/src/utils/transform-css-items.ts
+++ b/packages/babel-plugin/src/utils/transform-css-items.ts
@@ -74,6 +74,12 @@ const transformCssItem = (
),
};
+ case 'map':
+ return {
+ sheets: meta.state.cssMap[item.name],
+ classExpression: item.expression,
+ };
+
default:
const css = transformCss(getItemCss(item), meta.state.opts);
const className = compressClassNamesForRuntime(
diff --git a/packages/babel-plugin/src/utils/types.ts b/packages/babel-plugin/src/utils/types.ts
index 0bbe4e1ef..f753012f3 100644
--- a/packages/babel-plugin/src/utils/types.ts
+++ b/packages/babel-plugin/src/utils/types.ts
@@ -27,7 +27,20 @@ export interface SheetCssItem {
css: string;
}
-export type CssItem = UnconditionalCssItem | ConditionalCssItem | LogicalCssItem | SheetCssItem;
+export interface CssMapItem {
+ name: string;
+ type: 'map';
+ expression: t.Expression;
+ // We can remove this once we local transform other CssItem types
+ css: string;
+}
+
+export type CssItem =
+ | UnconditionalCssItem
+ | ConditionalCssItem
+ | LogicalCssItem
+ | SheetCssItem
+ | CssMapItem;
export type Variable = {
name: string;
diff --git a/packages/react/src/css-map/__tests__/index.test.tsx b/packages/react/src/css-map/__tests__/index.test.tsx
new file mode 100644
index 000000000..34e4cd48c
--- /dev/null
+++ b/packages/react/src/css-map/__tests__/index.test.tsx
@@ -0,0 +1,52 @@
+/** @jsxImportSource @compiled/react */
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { css, cssMap } from '@compiled/react';
+import { render } from '@testing-library/react';
+
+describe('css map', () => {
+ const styles = cssMap({
+ danger: {
+ color: 'red',
+ },
+ success: {
+ color: 'green',
+ },
+ });
+
+ it('should generate css based on the selected variant', () => {
+ const Foo = ({ variant }: { variant: keyof typeof styles }) => (
+ hello world
+ );
+ const { getByText, rerender } = render();
+
+ expect(getByText('hello world')).toHaveCompiledCss('color', 'red');
+
+ rerender();
+ expect(getByText('hello world')).toHaveCompiledCss('color', 'green');
+ });
+
+ it('should statically access a variant', () => {
+ const Foo = () => hello world
;
+ const { getByText } = render();
+
+ expect(getByText('hello world')).toHaveCompiledCss('color', 'red');
+ });
+
+ it('should merge styles', () => {
+ const hover = css({ ':hover': { color: 'red' } });
+ const Foo = () => hello world
;
+ const { getByText } = render();
+
+ expect(getByText('hello world')).toHaveCompiledCss('color', 'green');
+ expect(getByText('hello world')).toHaveCompiledCss('color', 'red', { target: ':hover' });
+ });
+
+ it('should conditionally apply variant', () => {
+ const Foo = ({ isDanger }: { isDanger: boolean }) => (
+ hello world
+ );
+ const { getByText } = render();
+
+ expect(getByText('hello world')).toHaveCompiledCss('color', 'red');
+ });
+});
diff --git a/packages/react/src/css-map/index.js.flow b/packages/react/src/css-map/index.js.flow
new file mode 100644
index 000000000..3569d3776
--- /dev/null
+++ b/packages/react/src/css-map/index.js.flow
@@ -0,0 +1,25 @@
+/**
+ * Flowtype definitions for index
+ * Generated by Flowgen from a Typescript Definition
+ * Flowgen v1.20.1
+ * @flow
+ */
+import type { CSSProps, CssObject } from '../types';
+/**
+ * ## cssMap
+ *
+ * Creates a collection of named CSS rules that are statically typed and useable with other Compiled APIs.
+ * For further details [read the documentation](https://compiledcssinjs.com/docs/api-cssmap).
+ * @example ```
+ * const borderStyleMap = cssMap({
+ * none: { borderStyle: 'none' },
+ * solid: { borderStyle: 'solid' },
+ * });
+ * const Component = ({ borderStyle }) =>
+ *
+ *
+ * ```
+ */
+declare export default function cssMap(_styles: {
+ [key: T]: CssObject | CssObject[],
+}): { [key: T]: CSSProps };
diff --git a/packages/react/src/css-map/index.ts b/packages/react/src/css-map/index.ts
new file mode 100644
index 000000000..357e41457
--- /dev/null
+++ b/packages/react/src/css-map/index.ts
@@ -0,0 +1,25 @@
+import type { CSSProps, CssObject } from '../types';
+import { createSetupError } from '../utils/error';
+
+/**
+ * ## cssMap
+ *
+ * Creates a collection of named CSS rules that are statically typed and useable with other Compiled APIs.
+ * For further details [read the documentation](https://compiledcssinjs.com/docs/api-cssmap).
+ *
+ * @example
+ * ```
+ * const borderStyleMap = cssMap({
+ * none: { borderStyle: 'none' },
+ * solid: { borderStyle: 'solid' },
+ * });
+ * const Component = ({ borderStyle }) =>
+ *
+ *
+ * ```
+ */
+export default function cssMap(
+ _styles: Record | CssObject[]>
+): Record> {
+ throw createSetupError();
+}
diff --git a/packages/react/src/index.js.flow b/packages/react/src/index.js.flow
index d6183f0b2..1937810a7 100644
--- a/packages/react/src/index.js.flow
+++ b/packages/react/src/index.js.flow
@@ -10,3 +10,4 @@ declare export { keyframes } from './keyframes';
declare export { styled } from './styled';
declare export { ClassNames } from './class-names';
declare export { default as css } from './css';
+declare export { default as cssMap } from './css-map';
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 7c038675f..8b114db02 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -9,6 +9,7 @@ export { keyframes } from './keyframes';
export { styled } from './styled';
export { ClassNames } from './class-names';
export { default as css } from './css';
+export { default as cssMap } from './css-map';
// Pass through the (classic) jsx runtime.
// Compiled currently doesn't define its own and uses this purely to enable a local jsx namespace.
diff --git a/stories/css-map.tsx b/stories/css-map.tsx
new file mode 100644
index 000000000..8559d1f11
--- /dev/null
+++ b/stories/css-map.tsx
@@ -0,0 +1,62 @@
+import { cssMap } from '@compiled/react';
+import { useState } from 'react';
+
+export default {
+ title: 'css map',
+};
+
+const styles = cssMap({
+ success: {
+ color: 'green',
+ ':hover': {
+ color: 'DarkGreen',
+ },
+ '@media (max-width: 800px)': {
+ color: 'SpringGreen',
+ },
+ },
+ danger: {
+ color: 'red',
+ ':hover': {
+ color: 'DarkRed',
+ },
+ '@media (max-width: 800px)': {
+ color: 'Crimson',
+ },
+ },
+});
+
+export const DynamicVariant = (): JSX.Element => {
+ const [variant, setVariant] = useState('success');
+
+ return (
+ <>
+ *': {
+ margin: '5px',
+ },
+ }}>
+
+
+
hello world
+
+ >
+ );
+};
+
+export const VariantAsProp = (): JSX.Element => {
+ const Component = ({ variant }: { variant: keyof typeof styles }) => (
+ hello world
+ );
+ return ;
+};
+
+export const MergeStyles = (): JSX.Element => {
+ return hello world
;
+};
+
+export const ConditionalStyles = (): JSX.Element => {
+ const isDanger = true;
+ return hello world
;
+};