Dark Mode
The scheduler ships two theme modes, light and dark. The active mode drives every surface: the timeline, resource sidebar, event blocks, dialogs, and context menus.
How the Theme Is Applied
Setting the theme writes data-df-theme="light" or data-df-theme="dark" onto the scheduler's wrapper element. The --df-color-* tokens are defined for that attribute, so everything inside re-themes from a single switch.
The same tokens are also defined for a .dark ancestor class, which means a host application that already toggles .dark on <html> re-themes the scheduler without any configuration.
Setting the Initial Theme
const app = useSchedulerApp({
resources,
events,
theme: 'dark',
});const app = useSchedulerApp({
resources,
events,
theme: 'dark',
});app = new SchedulerApp({
resources: this.resources,
events: this.events,
theme: 'dark',
});const app = createSchedulerApp({
resources,
events,
theme: 'dark',
});theme defaults to 'light'.
Switching at Runtime
| Method | Description |
|---|---|
app.getTheme() | Returns the current mode. |
app.setTheme('light' | 'dark') | Switches the active mode. |
function ThemeToggle({ app }) {
const { theme } = app.getState();
return (
<button onClick={() => app.setTheme(theme === 'light' ? 'dark' : 'light')}>
{theme === 'dark' ? 'Light' : 'Dark'}
</button>
);
}Following the System Preference
There is no built-in auto mode: theme accepts only 'light' or 'dark'. Read prefers-color-scheme yourself and forward it:
import { useEffect } from 'react';
function useSystemTheme(app) {
useEffect(() => {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const apply = (e: MediaQueryListEvent | MediaQueryList) => {
app.setTheme(e.matches ? 'dark' : 'light');
};
apply(mql);
mql.addEventListener('change', apply);
return () => mql.removeEventListener('change', apply);
}, [app]);
}Following a Host .dark Class
If your app already toggles .dark on <html>, the scheduler's colors follow automatically through the ancestor selector. Mirror it into app state as well when your own code reads app.getTheme():
useEffect(() => {
const sync = () =>
app.setTheme(
document.documentElement.classList.contains('dark') ? 'dark' : 'light'
);
sync();
const observer = new MutationObserver(sync);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
return () => observer.disconnect();
}, [app]);Portals and Dark Mode
Dialogs, context menus, and drag tooltips render into document.body under .df-portal, outside the scheduler wrapper. They inherit a host .dark class from <html>, but not the wrapper's data-df-theme attribute.
That distinction only matters for custom token overrides: target both selectors so portaled UI matches:
.df-scheduler-react,
.df-portal {
--df-color-primary: #6366f1;
}Per-resource Dark Colors
Resources can carry a separate palette for dark mode, which is used instead of colors when the theme is dark:
const resources = [
{
id: 'engineering',
name: 'Engineering',
style: {
colors: { color: '#0ea5e9', textColor: '#082f49' },
darkColors: { color: '#38bdf8', textColor: '#e0f2fe' },
},
},
];Related Documentation
- Theme Customization: overriding the
--df-color-*tokens. - useSchedulerApp: the
themeoption andsetTheme. - Events:
ColorValueaccepts a{ light, dark }pair.