Dark mode
Dark is a mode, not a preset: Rime Default and Rime Flat both have a dark mode. Press D anywhere on this site to try it.
How it works
tokens.css repaints every colour under data-mode="dark" or the .dark class, on <html> or any wrapper. The light channel (--rgb-white) points at the raised surface colour, so every translucent fill darkens with it; Flat stays opaque. Portalled overlays follow because the selectors also match :root:has(...).
Next.js setup
Set the attributes before paint so a reload never flashes: a tiny inline script in <head> that reads the stored choice, falling back to the system preference.
const THEME_SCRIPT = `try{var d=document.documentElement,m=localStorage.getItem("mode");
if(m!=="light"&&m!=="dark")m=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";
if(m==="dark"){d.classList.add("dark");d.setAttribute("data-mode","dark")}d.style.colorScheme=m}catch(e){}`;
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: THEME_SCRIPT }} />
</head>
<body>{children}</body>
</html>
);
}To toggle: add or remove the dark class (and data-mode), store the choice, and switch transitions off for one frame so colours don't tween.
With next-themes
Rime accepts the .dark class, so shadcn's setup works unchanged:
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>