Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Emotion

npm (scoped)

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.