Subframe has built-in dark mode support. Enable it in your theme to define light and dark values side by side, then sync to get a fully configured Tailwind setup.
Enable dark mode
Open Theme under Design System in the left sidebar
At the top of the theme page, click Add dark mode
Each token now shows light and dark values — edit the dark values to define your dark palette
Preview your components and pages in both light and dark mode using the sun/moon toggle in the editor toolbar
Dark mode colors typically invert the scale: light mode’s lightest shade becomes dark mode’s darkest, and vice versa.
To remove dark mode, click ⋯ in the theme header and select Remove dark mode . This deletes all dark overrides. You can undo this using version history.
How the generated code works
When dark mode is enabled, Subframe generates theme tokens as CSS variables so light and dark values can switch at runtime.
The CLI syncs two files:
tailwind.config.js — references CSS variables instead of hardcoded values, with darkMode: 'selector' enabled
theme.css — defines :root variables for light mode and .dark overrides for dark mode
module . exports = {
darkMode: 'selector' ,
theme: {
extend: {
colors: {
"brand-primary" : "var(--color-brand-primary)" ,
// ... all your color tokens
},
},
},
}
:root {
--color-brand-primary : rgb ( 26 26 26 );
--color-default-background : rgb ( 252 252 252 );
/* ... light mode values */
}
.dark {
--color-brand-primary : rgb ( 212 212 212 );
--color-default-background : rgb ( 10 10 10 );
/* ... dark mode overrides */
}
Import theme.css in your global stylesheet or entry point: @import "./subframe/theme.css" ;
The generated theme.css includes a @custom-variant for dark mode and a .dark block with overrides: @custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-brand-primary: rgb(26 26 26);
--color-default-background: rgb(252 252 252);
/* ... light mode values */
}
.dark {
--color-brand-primary : rgb ( 212 212 212 );
--color-default-background : rgb ( 10 10 10 );
/* ... dark mode overrides */
}
Sync to code
Run the CLI to sync your theme (including dark mode) to your codebase:
npx @subframe/cli@latest sync --all
yarn dlx @subframe/cli@latest sync --all
pnpx @subframe/cli@latest sync --all
bunx @subframe/cli@latest sync --all
Enable dark mode in your app
To activate dark mode, set the dark class on the <html> element. Here are a few ways to accomplish that:
Next.js with next-themes
import { ThemeProvider } from "next-themes"
export default function RootLayout ({ children }) {
return (
< html suppressHydrationWarning >
< body >
< ThemeProvider attribute = "class" defaultTheme = "system" >
{ children }
</ ThemeProvider >
</ body >
</ html >
)
}
React with context
import { createContext , useContext , useEffect , useState } from "react"
const ThemeContext = createContext ({ theme: "light" , toggleTheme : () => {} })
export function ThemeProvider ({ children }) {
const [ theme , setTheme ] = useState ( "light" )
useEffect (() => {
const root = window . document . documentElement
root . classList . remove ( "light" , "dark" )
root . classList . add ( theme )
}, [ theme ])
const toggleTheme = () => setTheme ( theme === "light" ? "dark" : "light" )
return < ThemeContext.Provider value = { { theme , toggleTheme } } > { children } </ ThemeContext.Provider >
}
export const useTheme = () => useContext ( ThemeContext )
import { useTheme } from "next-themes"
export function ThemeToggle () {
const { theme , setTheme } = useTheme ()
return < button onClick = { () => setTheme ( theme === "dark" ? "light" : "dark" ) } > { theme === "dark" ? "☀️" : "🌙" } </ button >
}
Best practices
Always test your application in both light and dark modes. Check for:
Sufficient contrast ratios (use browser DevTools)
Readability of all text
Visibility of borders and dividers
Proper styling of interactive states
Respect system preferences
Use the user’s system preference as the default: < ThemeProvider attribute = "class" defaultTheme = "system" >