omverse-ui

Menu

ComponentsExamplesGitHub

Getting started

IntroductionInstallationThemingDesign tokensDark mode

Form

ButtonInputSelectCheckboxRadioSwitchSlidernewDatePickernew

Display

AvatarBadgeCardChipAccordionProgressDivider

Navigation

NavbarBreadcrumbTabsPaginationStepper

Overlay

DialogTooltipToast

Other

IconIconButtonSpinner
DocsDark mode

Dark mode

omverse-ui supports dark mode via CSS media query or a class-based toggle — no extra config required.

All omverse-ui colors are CSS custom properties, so dark mode is a matter of overriding those variables. There are two standard approaches — choose whichever fits your app.

Option 1 — System preference (media query)

Override tokens inside a prefers-color-scheme: dark media query. The browser switches automatically based on the OS setting — no JavaScript needed.

index.css
tsx
1/* Automatically follow the OS preference */
2@media (prefers-color-scheme: dark) {
3 :root {
4 --color-background: #0A0A0F;
5 --color-surface: #111118;
6 --color-surface-variant: #1A1A24;
7 --color-text-primary: #F1F5F9;
8 --color-text-secondary: #94A3B8;
9 --color-text-tertiary: #64748B;
10 --color-outline: #334155;
11 --color-outline-variant: #1E293B;
12 }
13}

Option 2 — Class-based toggle

Scope overrides to a .dark class on <html>. This lets you toggle dark mode programmatically and persist the user's preference.

index.css
tsx
1/* Class-based — toggle .dark on <html> */
2.dark {
3 --color-background: #0A0A0F;
4 --color-surface: #111118;
5 --color-surface-variant: #1A1A24;
6 --color-text-primary: #F1F5F9;
7 --color-text-secondary: #94A3B8;
8 --color-text-tertiary: #64748B;
9 --color-outline: #334155;
10 --color-outline-variant: #1E293B;
11}

Use a small hook to toggle the class and save the preference to localStorage:

useDarkMode.ts
tsx
1import { useEffect, useState } from 'react'
2 
3export function useDarkMode() {
4 const [dark, setDark] = useState(
5 () => document.documentElement.classList.contains('dark')
6 )
7 
8 useEffect(() => {
9 document.documentElement.classList.toggle('dark', dark)
10 localStorage.setItem('theme', dark ? 'dark' : 'light')
11 }, [dark])
12 
13 return [dark, setDark] as const
14}

Preventing flash of wrong theme

When using class-based dark mode, the page can briefly flash the wrong theme before JavaScript runs. Fix this by injecting an inline script in <head> that reads localStorage and applies the class synchronously.

The script must be inline (not a deferred or async script) so it runs before the browser paints anything.

Standalone anti-flash snippet

index.html / _document.tsx
tsx
1// Prevent flash of wrong theme — add to <head> before JS loads
2<script dangerouslySetInnerHTML={{
3 __html: `
4 const saved = localStorage.getItem('theme')
5 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
6 if (saved === 'dark' || (!saved && prefersDark)) {
7 document.documentElement.classList.add('dark')
8 }
9 `,
10}} />

In a Next.js App Router layout

app/layout.tsx
tsx
1// app/layout.tsx
2import type { Metadata } from 'next'
3 
4export const metadata: Metadata = { title: 'My App' }
5 
6export default function RootLayout({ children }: { children: React.ReactNode }) {
7 return (
8 <html lang="en" suppressHydrationWarning>
9 <head>
10 {/* Anti-flash script — must be inline, before any JS */}
11 <script dangerouslySetInnerHTML={{
12 __html: `
13 const t = localStorage.getItem('theme')
14 const d = window.matchMedia('(prefers-color-scheme: dark)').matches
15 if (t === 'dark' || (!t && d)) document.documentElement.classList.add('dark')
16 `,
17 }} />
18 </head>
19 <body>{children}</body>
20 </html>
21 )
22}

Tips

  • Add suppressHydrationWarning to <html> in Next.js to silence the class mismatch hydration warning.
  • Both approaches can be combined — use the media query as the default and class override for an explicit user toggle.
  • See the Design tokens page for a full list of color variables you can override.