From 'dark' to 'light' mode
Allowing users to switch between a ‘light’ and ‘dark’ mode on your website is something most people expect from a modern web application. It also helps improve accessibility for the site, allowing users to settle on a theme that suits them best.
The only problem is that implementing a light and dark mode is surprisingly difficult. Luckily, the Epic Stack has most of the code for this already included.
All we need to do is implement it 🚀.
Trigger ‘light’ and ‘dark’ mode with code
Section titled “Trigger ‘light’ and ‘dark’ mode with code”Open app/root.tsx
and look carefully at the App
function being exported:
4 collapsed lines
import { type LinksFunction } from '@remix-run/node'import Document from '~/components/shared-layout/Document.tsx'import rootLinkElements from '~/utils/providers/rootLinkElements'import { useNonce } from '~/utils/nonce-provider.ts'
export const links: LinksFunction = () => { return rootLinkElements}export { headers, meta } from './__root.client.tsx'export { action, loader } from './__root.server.tsx'
export default function App() { const nonce = useNonce()
return ( <Document nonce={nonce}> <div className="flex h-screen flex-col justify-between"> <div className="flex-1"> <main className="grid h-full place-items-center"> <h1 className="text-mega">Welcome to Epic News!</h1> </main> </div> </div> </Document> )}
The Document
component
Section titled “The Document component”Notice the Document
component that wraps everything being returned from the App
function?
import { type LinksFunction } from '@remix-run/node'import Document from '~/components/shared-layout/Document.tsx'import rootLinkElements from '~/utils/providers/rootLinkElements'import { useNonce } from '~/utils/nonce-provider.ts'
5 collapsed lines
export const links: LinksFunction = () => { return rootLinkElements}export { headers, meta } from './__root.client.tsx'export { action, loader } from './__root.server.tsx'
export default function App() { const nonce = useNonce()
return ( <Document nonce={nonce}> <div className="flex h-screen flex-col justify-between"> <div className="flex-1"> <main className="grid h-full place-items-center"> <h1 className="text-mega">Welcome to Epic News!</h1> </main> </div> </div> </Document> )}
Right-click the opening Document
tag, and select ‘Go to Source Definition’ from the option menu:
This will open the file where the Document
component code is held:
interface DocumentProps { children: React.ReactNode nonce: string theme?: Theme env?: Record<string, string> allowIndexing?: boolean}
export default function Document({ children, nonce, theme = 'dark', env = {}, allowIndexing = true,}: DocumentProps) { return ( <html lang="en" className={`${theme} h-full overflow-x-hidden`}> <head> <ClientHintCheck nonce={nonce} /> <Meta /> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> {allowIndexing ? null : ( <meta name="robots" content="noindex, nofollow" /> )} <Links /> </head> <body className="bg-background text-foreground"> {children} <script nonce={nonce} dangerouslySetInnerHTML={{ __html: `window.ENV = ${JSON.stringify(env)}`, }} /> <ScrollRestoration nonce={nonce} /> <Scripts nonce={nonce} /> </body> </html> )}
Manually updating from ‘light’ to ‘dark’ themes
Section titled “Manually updating from ‘light’ to ‘dark’ themes”Head back over to app/components/shared-layout/Document.tsx
if you are not in it already.
Find the line that reads theme = 'dark'
, around line 16.
Change this to theme = 'light'
and save the file:
export default function Document({ children, nonce, theme = 'dark', theme = 'light', env = {},}: DocumentProps)
You should see the website in the browser looks completely different:
Why? Tailwind classes and string interpolation
Section titled “Why? Tailwind classes and string interpolation”Take a closer look at the opening html
component being returned from the Document
function:
export default function Document({ children, nonce, theme = 'light', env = {}, allowIndexing = true,}: DocumentProps) { return ( <html lang="en" className={`${theme} h-full overflow-x-hidden`}>21 collapsed lines
<head> <ClientHintCheck nonce={nonce} /> <Meta /> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> {allowIndexing ? null : ( <meta name="robots" content="noindex, nofollow" /> )} <Links /> </head> <body className="bg-background text-foreground"> {children} <script nonce={nonce} dangerouslySetInnerHTML={{ __html: `window.ENV = ${JSON.stringify(env)}`, }} /> <ScrollRestoration nonce={nonce} /> <Scripts nonce={nonce} /> </body> </html> )}
Can you see how the value of the theme
variable has been slotted into the className
property (or ‘prop’)?
This is called interpolation, and is a way of inserting the value of a variable into a string in JavaScript.
Summary
Section titled “Summary”In this tutorial, we’ve learned:
- How to switch between ‘light’ and ‘dark’ themes in the Epic Stack.
- How to manually update the theme from ‘dark’ to ‘light’ in the
Document
component. - How to use string interpolation to insert the value of a variable into a string.
- How to use TypeScript interfaces to define the shape of an object.
- How to use React props to pass data into a component.
What’s next?
Section titled “What’s next?”What we need now is a way for users to control the value of theme
dynamically from the front-end, so they can switch easily between both.
In our next tutorial, we add a button that users can click to toggle between the two states.