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.gstaticom' },{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>)}
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. 👇
- https://github.com/aacevski/chakra-remix-boilerplate
- https://github.com/NoQuarterTeam/boilerplate-remix