---
title: Remix
description: A guide for installing Chakra UI with Remix projects
tags: ['remix']
author: noobinthisgame
category: frameworks
---

### 1. Installation

Inside your `Remix` project root directory, install Chakra UI by running either
of the following:

<PackageManagers
  command={{
    npm: 'npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @emotion/server@^11',
    yarn: 'yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @emotion/server@^11',
    pnpm: 'pnpm add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @emotion/server@^11',
    bun: 'bun add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @emotion/server@^11',
  }}
/>

### 2. Provider Setup

To prevent loss of styles we need to do some changes on the server-side and
client-side.

We’ll create a `context.tsx` in the `app` folder.

```jsx live=false
// context.tsx
import React, { createContext } from 'react'

export interface ServerStyleContextData {
  key: string
  ids: Array<string>
  css: string
}

export const ServerStyleContext = createContext<ServerStyleContextData[] | null>(null)

export interface ClientStyleContextData {
  reset: () => void
}

export const ClientStyleContext = createContext<ClientStyleContextData | null>(null)
```

Next on the agenda is to create the emotion cache file. To do that, create a new
`createEmotionCache.ts` file in the `app` folder.

```jsx live=false
// createEmotionCache.ts
import createCache from '@emotion/cache'

export const defaultCache = createEmotionCache()

export default function createEmotionCache() {
  return createCache({ key: 'cha' })
}
```

After creating the emotion cache, we need to modify the entry files for both the
client and the server. We'll use our createEmotionCache function here.

```jsx live=false
// entry.client.tsx
import React, { useState } from 'react'
import { hydrate } from 'react-dom'
import { CacheProvider } from '@emotion/react'
import { RemixBrowser } from '@remix-run/react'

import { ClientStyleContext } from './context'
import createEmotionCache, { defaultCache } from './createEmotionCache'

interface ClientCacheProviderProps {
  children: React.ReactNode;
}

function ClientCacheProvider({ children }: ClientCacheProviderProps) {
  const [cache, setCache] = useState(defaultCache)

  function reset() {
    setCache(createEmotionCache())
  }

  return (
    <ClientStyleContext.Provider value={{ reset }}>
      <CacheProvider value={cache}>{children}</CacheProvider>
    </ClientStyleContext.Provider>
  )
}

hydrate(
  <ClientCacheProvider>
    <RemixBrowser />
  </ClientCacheProvider>,
  document,
)
```

```jsx live=false
// entry.server.tsx
import { renderToString } from 'react-dom/server'
import { CacheProvider } from '@emotion/react'
import createEmotionServer from '@emotion/server/create-instance'
import { RemixServer } from '@remix-run/react'
import type { EntryContext } from '@remix-run/node' // Depends on the runtime you choose

import { ServerStyleContext } from './context'
import createEmotionCache from './createEmotionCache'

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
) {
  const cache = createEmotionCache()
  const { extractCriticalToChunks } = createEmotionServer(cache)

  const html = renderToString(
    <ServerStyleContext.Provider value={null}>
      <CacheProvider value={cache}>
        <RemixServer context={remixContext} url={request.url} />
      </CacheProvider>
    </ServerStyleContext.Provider>,
  )

  const chunks = extractCriticalToChunks(html)

  const markup = renderToString(
    <ServerStyleContext.Provider value={chunks.styles}>
      <CacheProvider value={cache}>
        <RemixServer context={remixContext} url={request.url} />
      </CacheProvider>
    </ServerStyleContext.Provider>,
  )

  responseHeaders.set('Content-Type', 'text/html')

  return new Response(`<!DOCTYPE html>${markup}`, {
    status: responseStatusCode,
    headers: responseHeaders,
  })
}
```

Inside our `root.tsx` file we'll create a `Document` wrapper and then we'll wrap
our `App` with the Document.

```jsx live=false
// root.tsx
import React, { useContext, useEffect } from 'react'
import { withEmotionCache } from '@emotion/react'
import { ChakraProvider } from '@chakra-ui/react'
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from '@remix-run/react'
import { MetaFunction, LinksFunction } from '@remix-run/node' // Depends on the runtime you choose

import { ServerStyleContext, ClientStyleContext } from './context'

export const meta: MetaFunction = () => ({
  charset: 'utf-8',
  title: 'New Remix App',
  viewport: 'width=device-width,initial-scale=1',
});

export let links: LinksFunction = () => {
  return [
    { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
    { rel: 'preconnect', href: 'https://fonts.gstatic.com' },
    {
      rel: 'stylesheet',
      href: 'https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap'
    },
  ]
}

interface DocumentProps {
  children: React.ReactNode;
}

const Document = withEmotionCache(
  ({ children }: DocumentProps, emotionCache) => {
    const serverStyleData = useContext(ServerStyleContext);
    const clientStyleData = useContext(ClientStyleContext);

    // Only executed on client
    useEffect(() => {
      // re-link sheet container
      emotionCache.sheet.container = document.head;
      // re-inject tags
      const tags = emotionCache.sheet.tags;
      emotionCache.sheet.flush();
      tags.forEach((tag) => {
        (emotionCache.sheet as any)._insertTag(tag);
      });
      // reset cache to reapply global styles
      clientStyleData?.reset();
    }, []);

    return (
      <html lang="en">
        <head>
          <Meta />
          <Links />
          {serverStyleData?.map(({ key, ids, css }) => (
            <style
              key={key}
              data-emotion={`${key} ${ids.join(' ')}`}
              dangerouslySetInnerHTML={{ __html: css }}
            />
          ))}
        </head>
        <body>
          {children}
          <ScrollRestoration />
          <Scripts />
          <LiveReload />
        </body>
      </html>
    );
  }
);
```

And then we'll wrap the App just like so:

```jsx live=false
export default function App() {
  return (
    <Document>
      <ChakraProvider>
        <Outlet />
      </ChakraProvider>
    </Document>
  )
}
```

#### ChakraProvider Props

| Name             | Type             | Default               | Description                                         |
| ---------------- | ---------------- | --------------------- | --------------------------------------------------- |
| resetCSS         | `boolean`        | `true`                | automatically includes `<CSSReset />`               |
| theme            | `Theme`          | `@chakra-ui/theme`    | optional custom theme                               |
| colorModeManager | `StorageManager` | `localStorageManager` | manager to persist a users color mode preference in |
| portalZIndex     | `number`         | `undefined`           | common z-index to use for `Portal`                  |

> Boom! You're good to go with steps 1 and 2 🚀🚀🚀 However, if you'd love to
> take it a step further, check out step 3.

### 3. Optional Setup

#### Customizing Theme

If you intend to customise the default theme object to match your design
requirements, you can extend the `theme` from `@chakra-ui/react`.

Chakra UI provides an `extendTheme` function that deep merges the default theme
with your customizations.

```jsx live=false
import { extendTheme, ChakraProvider } from '@chakra-ui/react'

const colors = {
  brand: {
    900: '#1a365d',
    800: '#153e75',
    700: '#2a69ac',
  },
}

const theme = extendTheme({ colors })

export default function App() {
  return (
    <Document>
      <ChakraProvider theme={theme}>
        <Outlet />
      </ChakraProvider>
    </Document>
  )
}
```

#### Add colorModeManager

Remix is server-side rendered, so there will be color mode `flashing`, because
chakra stores color mode in `localstorage` by default.

We will store color mode value in `cookie` to tell our app to render it in user
color mode.

Here's how to fix it:

1. Create loader in your `root.tsx`

```tsx live=false
// Typescript
// This will return cookies
export const loader: LoaderFunction = async ({ request }) => {
  // first time users will not have any cookies and you may not return
  // undefined here, hence ?? is necessary
  return request.headers.get('cookie') ?? ''
}
```

2. Give `ChakraProvider` cookies from loader

```jsx live=false
// root.tsx
// In your App function

function getColorMode (cookies: string) {
  const match = cookies.match(new RegExp(`(^| )${CHAKRA_COOKIE_COLOR_KEY}=([^;]+)`));
  return match == null ? void 0 : match[2];
}

// here we can set the default color mode. If we set it to null,
// there's no way for us to know what is the the user's preferred theme
// so the cient will have to figure out and maybe there'll be a flash the first time the user visits us.
const DEFAULT_COLOR_MODE: "dark" | "light" | null = 'dark';

const CHAKRA_COOKIE_COLOR_KEY = "chakra-ui-color-mode";

let cookies = useLoaderData()

// the client get the cookies from the document
// because when we do a client routing, the loader can have stored an outdated value
if (typeof document !== "undefined") {
  cookies = document.cookie;
}

// get and store the color mode from the cookies.
// It'll update the cookies if there isn't any and we have set a default value
let colorMode = useMemo(() => {
  let color = getColorMode(cookies)

  if (!color && DEFAULT_COLOR_MODE) {
    cookies += ` ${CHAKRA_COOKIE_COLOR_KEY}=${DEFAULT_COLOR_MODE}`;
    color = DEFAULT_COLOR_MODE;
  }

  return color
}, [cookies]);

[...]

// Add classes to html and body and add colorModeManager to ChakraProvider

return (
  <html
    lang="en"
    {...colorMode
      && {
          "data-theme": colorMode,
          "style": { colorScheme: colorMode },
        }
      }
  >
    <head>
      <Meta />
      <Links />
      {serverStyleData?.map(({ key, ids, css }) => (
        <style
          key={key}
          data-emotion={`${key} ${ids.join(" ")}`}
          dangerouslySetInnerHTML={{ __html: css }}
        />
      ))}
    </head>
    <body
      {...colorMode && {
        className: `chakra-ui-${colorMode}`
      }}
    >
      <ChakraProvider
        colorModeManager={cookieStorageManagerSSR(cookies)}
        theme={theme}
      >
        {children}
      </ChakraProvider>

      <ScrollRestoration />

      <Scripts />

      <LiveReload />
    </body>
  </html>
)
```

That's it! Now the theme is correctly server side rendered. You can test it
deleting the cookies and disabling JavaScript in the browser.

#### Notes on TypeScript 🚨

Please note that when adding Chakra UI to a TypeScript project, a minimum
TypeScript version of `4.1.0` is required.

## Template

If you're starting a new project and would like to cut down on configuration
time, you can use the
[official template](https://github.com/remix-run/examples/tree/main/chakra-ui)
on how to use Chakra UI with Remix:

<iframe
  src='https://codesandbox.io/embed/github/remix-run/examples/tree/main/chakra-ui?fontsize=14&theme=dark'
  style={{
    width: '100%',
    height: '500px',
    border: '0',
    borderRadius: '4px',
    overflow: 'hidden',
  }}
  allow='accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking'
  sandbox='allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts'
></iframe>
