Remix
1. Installation#
Inside your Remix
project root directory, install Chakra UI by running either
of the following:
npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @emotion/server@^11
yarn 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.
// context.tsximport React, { createContext } from 'react'export interface ServerStyleContextData {key: stringids: 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.
// createEmotionCache.tsimport createCache from '@emotion/cache'export default function createEmotionCache() {return createCache({ key: 'css' })}
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.
// entry.client.tsximport 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 from './createEmotionCache'interface ClientCacheProviderProps {children: React.ReactNode;}function ClientCacheProvider({ children }: ClientCacheProviderProps) {const [cache, setCache] = useState(createEmotionCache())function reset() {setCache(createEmotionCache())}return (<ClientStyleContext.Provider value={{ reset }}><CacheProvider value={cache}>{children}</CacheProvider></ClientStyleContext.Provider>)}hydrate(<ClientCacheProvider><RemixBrowser /></ClientCacheProvider>,document,)
// entry.server.tsximport { 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 chooseimport { 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.
// root.tsximport 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 chooseimport { 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 clientuseEffect(() => {// re-link sheet containeremotionCache.sheet.container = document.head;// re-inject tagsconst tags = emotionCache.sheet.tags;emotionCache.sheet.flush();tags.forEach((tag) => {(emotionCache.sheet as any)._insertTag(tag);});// reset cache to reapply global stylesclientStyleData?.reset();}, []);return (<html lang="en"><head><Meta /><Links />{serverStyleData?.map(({ key, ids, css }) => (<stylekey={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:
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.
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:
- Create loader in your
root.tsx
// Typescript// This will return cookiesexport const loader: LoaderFunction = async ({ request }) => {// first time users will not have any cookies and you may not return// undefined here, hence ?? is necessaryreturn request.headers.get("cookie") ?? ''};
- Give
ChakraProvider
cookies from loader
// root.tsx// In your App functionconst cookies = useLoaderData()[...]// Add colorModeManager to ChakraProviderreturn (<Document><ChakraProvider theme={theme}colorModeManager={typeof cookies === 'string'? cookieStorageManagerSSR(cookies): localStorageManager}>[...]</ChakraProvider></Document>)
That's it!
Notes on TypeScript 🚨#
Please note that when adding Chakra UI to a TypeScript project, a minimum
TypeScript version of 4.1.0
is required.
4. Community boilerplates#
If you're starting a new project and would like to cut down on configuration time, you can use some of these community boilerplates. 👇