Match the Dashboard's color scheme
Follow the Dashboard's light or dark mode in your app, panels, and tiles with Sanity UI v4 or any other styling library.
Users switch the Dashboard between light and dark mode, or have it follow their operating system, from the account menu. Your app renders inside the Dashboard's page, so match its color scheme to fit in. Read the scheme with useTopic('preferences.color-scheme') from @sanity/sdk-react/dashboard.
Requirements
@sanity/sdk-react3.9.0 or later.- An app configured with
defineApplication, running undersanity devor deployed with it.
Read the color scheme
preferences.color-scheme is 'light' or 'dark'. When the user chooses System, the Dashboard resolves it from the operating system, so your app always receives one of the two. The hook suspends until the Dashboard sends the first value, and re-renders your component whenever the user changes the setting.
Apply it to Sanity UI v4
This example uses @sanity/ui v4, which the Sanity UI template installs. Pass the scheme to the scheme prop of ThemeProvider. useTopic needs an App SDK instance, so render ThemeProvider inside SanityApp:
import '@sanity/ui/styles.css'
import {type SanityConfig} from '@sanity/sdk'
import {SanityApp} from '@sanity/sdk-react'
import {useTopic} from '@sanity/sdk-react/dashboard'
import {ThemeProvider} from '@sanity/ui'
import {buildTheme} from '@sanity/ui/theme'
import type {ReactNode} from 'react'
import {ExampleComponent} from './ExampleComponent'
const theme = buildTheme()
const config: SanityConfig[] = [
{
projectId: 'your-project-id',
dataset: 'production',
},
]
function DashboardTheme({children}: {children: ReactNode}) {
const scheme = useTopic('preferences.color-scheme')
return (
<ThemeProvider theme={theme} scheme={scheme}>
{children}
</ThemeProvider>
)
}
export default function App() {
return (
<SanityApp config={config} fallback={null}>
<DashboardTheme>
<ExampleComponent />
</DashboardTheme>
</SanityApp>
)
}SanityApp renders its fallback before ThemeProvider mounts, so don't use @sanity/ui components in the fallback.
Apps created from the Sanity UI template wrap SanityApp in a ThemeProvider in src/SanityUI.tsx. Move the ThemeProvider inside SanityApp so it can read the scheme.
Apply it to other styling libraries
The same approach works with any styling library: read the scheme from the Dashboard, then pass it to whatever your library uses to switch modes. Follow these rules:
- Read the scheme with
useTopic, not fromprefers-color-schemeor the library's own detection. The user's Dashboard setting can differ from their operating system. - Pass the scheme to the library's mode setting, such as a theme provider's mode or color scheme option.
- Scope it to your app's root element. Your app renders into the Dashboard's page rather than its own document, so don't let the library change
document.documentElement,document.body, or global styles. Turn off any global CSS reset the library applies, or use its scoped version. - Pass the value straight through instead of copying it into state.
useTopicre-renders your component whenever the user changes the setting.
Plain CSS
Put the scheme on a root element as a data attribute, and define your colors for each scheme:
import {useTopic} from '@sanity/sdk-react/dashboard'
import type {ReactNode} from 'react'
export function SchemeRoot({children}: {children: ReactNode}) {
const scheme = useTopic('preferences.color-scheme')
return (
<div className="app-root" data-scheme={scheme}>
{children}
</div>
)
}.app-root {
color-scheme: light;
--app-background: #ffffff;
--app-text: #101112;
background: var(--app-background);
color: var(--app-text);
}
.app-root[data-scheme='dark'] {
color-scheme: dark;
--app-background: #101112;
--app-text: #ffffff;
}Render SchemeRoot inside SanityApp. The color-scheme property also switches browser-drawn elements such as scrollbars and form controls.
Tailwind CSS v4
Point Tailwind's dark variant at the same data attribute, then use dark: utilities inside SchemeRoot:
@import "tailwindcss"; @custom-variant dark (&:where([data-scheme=dark], [data-scheme=dark] *));
Match the color scheme in panels and tiles
Panel components, panel titles, and tiles each render in their own island, so add the theme to the Providers component that wraps them:
import '@sanity/ui/styles.css'
import {SanityApp} from '@sanity/sdk-react'
import {useTopic} from '@sanity/sdk-react/dashboard'
import {ThemeProvider} from '@sanity/ui'
import {buildTheme} from '@sanity/ui/theme'
import type {ReactNode} from 'react'
const theme = buildTheme()
function DashboardTheme({children}: {children: ReactNode}) {
const scheme = useTopic('preferences.color-scheme')
return (
<ThemeProvider theme={theme} scheme={scheme}>
{children}
</ThemeProvider>
)
}
export function Providers({children}: {children: ReactNode}) {
return (
<SanityApp fallback={null}>
<DashboardTheme>{children}</DashboardTheme>
</SanityApp>
)
}