Emotion

Installation
PNPM
pnpm add @leafygreen-ui/emotion
Yarn
yarn add @leafygreen-ui/emotion
NPM
npm install @leafygreen-ui/emotion
LeafyGreen Internal Emotion Instance
This package should be used only in LeafyGreen components (i.e. @leafygreen-ui/* packages).
For external applications, prefer using @emotion/react (or similar), or an app-specific custom instance of Emotion.
Why? If any @leafygreen-ui/* dependencies are not up to date, this could cause multiple copies of @leafygreen-ui/emotion to be installed, resulting in unpredictable styling.
Server-side Rendering
Because we use a custom instance of Emotion to allow for styles defined in LeafyGreen to be easily overwritten, there's an additional step that must be taken to use our components when performing server-side rendering.
We expose three methods as named exports that are also exposed by the base emotion-server package: renderStylesToString, renderStylesToNodeStream, and extractCritical. You can find documentation on usage of each of the methods in the official Emotion documentation.
NOTE: If you are already server-side rendering an application using Emotion, you will use the methods exposed in @leafygreen-ui/emotion instead of, NOT in addition to the methods exposed by emotion-server.
Example
import { renderToString } from 'react-dom/server';
import { renderStylesToString } from '@leafygreen-ui/emotion';
import App from './App';
const html = renderStylesToString(renderToString(<App />));
SSR Compatibility
Emotion generates styles at runtime and injects them into the DOM. For server-side rendering, styles must be extracted during rendering and inserted into the HTML before it's sent to the client. Without proper configuration, you'll see a flash of unstyled content (FOUC).
⚠️ Important:
- Emotion does not currently support React Server Components. You must use
'use client' directive in Next.js.
- Ensure you're using the latest version of any
emotion packages alongside this package.
- LeafyGreen UI components may require additional configuration beyond what's documented here.
Framework Guides
Next.js (App Router)
1. Create the Emotion Registry
Create a new file at src/app/EmotionRegistry.tsx:
'use client';
import { useServerInsertedHTML } from 'next/navigation';
import { cache, CacheProvider } from '@leafygreen-ui/emotion';
export default function EmotionRegistry({
children,
}: {
children: React.ReactNode,
}) {
useServerInsertedHTML(() => {
const names = Object.keys(cache.inserted);
if (names.length === 0) return null;
let styles = '';
for (const name of names) {
const style = cache.inserted[name];
if (typeof style === 'string') {
styles += style;
}
}
return (
<style
data-emotion={`${cache.key} ${names.join(' ')}`}
dangerouslySetInnerHTML={{ __html: styles }}
/>
);
});
return <CacheProvider value={cache}>{children}</CacheProvider>;
}
2. Add the Registry to Your Root Layout
Wrap your application in src/app/layout.tsx:
import type { Metadata } from 'next';
import EmotionRegistry from './EmotionRegistry';
export const metadata: Metadata = {
title: 'My App',
description: 'My application description',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<EmotionRegistry>{children}</EmotionRegistry>
</body>
</html>
);
}
3. Use in Client Components
The css function only works in Client Components:
'use client';
import { css } from '@leafygreen-ui/emotion';
export default function MyComponent() {
return (
<h1
className={css`
color: red;
`}
>
Hello World
</h1>
);
}
Next.js (Pages Router)
Add Emotion's critical CSS extraction to your _document file:
import { extractCritical } from '@leafygreen-ui/emotion';
export default class AppDocument extends Document {
static async getInitialProps(
ctx: DocumentContext,
): Promise<DocumentInitialProps> {
const initialProps = await Document.getInitialProps(ctx);
const { css, ids } = extractCritical(initialProps.html || '');
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style
data-emotion={`css ${ids.join(' ')}`}
dangerouslySetInnerHTML={{ __html: css }}
/>
</>
),
};
}
}
React Router v7+
This guide covers Framework mode for React Router.
1. Configure Server Entry
Update `entry.server.tsx`:
import { PassThrough } from 'node:stream';
import type { EntryContext } from 'react-router';
import { createReadableStreamFromReadable } from '@react-router/node';
import { ServerRouter } from 'react-router';
import { renderToPipeableStream } from 'react-dom/server';
import { cache, extractCritical, CacheProvider } from '@leafygreen-ui/emotion';
const ABORT_DELAY = 5_000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
) {
return new Promise<Response>((resolve, reject) => {
let statusCode = responseStatusCode;
const chunks: Buffer[] = [];
const { pipe, abort } = renderToPipeableStream(
<CacheProvider value={cache}>
<ServerRouter context={routerContext} url={request.url} />
</CacheProvider>,
);
const collectStream = new PassThrough();
collectStream.on('data', chunk => chunks.push(chunk));
collectStream.on('end', () => {
const html = Buffer.concat(chunks).toString('utf-8');
const { css, ids } = extractCritical(html);
const emotionStyleTag = `<style data-emotion="css ${ids.join(' ')}">${css}</style>`;
const htmlWithStyles = html.replace('</head>', `${emotionStyleTag}</head>`);
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set('Content-Type', 'text/html');
resolve(
new Response(stream, {
headers: responseHeaders,
status: statusCode,
}),
);
body.write(htmlWithStyles);
body.end();
});
collectStream.on('error', reject);
pipe(collectStream);
setTimeout(abort, ABORT_DELAY);
});
}
2. Configure Client Entry
Update entry.client.tsx:
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { HydratedRouter } from 'react-router/dom';
import { CacheProvider, cache } from '@leafygreen-ui/emotion';
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<CacheProvider value={cache}>
<HydratedRouter />
</CacheProvider>
</StrictMode>,
);
});
Gatsby.js
⚠️ Not Currently Supported
There is a peer dependency mismatch between @leafygreen-ui/emotion and gatsby-plugin-emotion. As a result, we do not currently support GatsbyJS projects out of the box. If you need Emotion in a Gatsby project, refer to the Gatsby Emotion documentation.