Theme Hooks
Share theme state across a custom admin shell in React or Vue, while keeping the same resolved light and dark behavior the built-in admin uses.
If your UI needs a theme switcher or a shared light and dark mode, this is the page you want.
You usually need theme state when you are building:
- a custom admin shell
- a dashboard around Dyrected-powered UI
- a settings page with theme controls
- a themed wrapper that should stay in sync with Dyrected light and dark mode
The goal is simple: every component should agree on the same theme state instead of guessing independently.
That includes:
- the saved theme preference
- the current system theme
- the resolved theme after applying the preference
- the CSS class that should be applied to the themed root
The theme APIs give you that shared state without forcing you to wire it yourself.
By the end of this page you should know what the theme API owns, how to mount it, and how to use it in your own shell or dashboard.
What the theme API owns
The public theme API is intentionally small. It exposes:
themesystemThemeresolvedThemethemeClassNamesetTheme
That is enough to build:
- a custom theme switcher
- a themed layout shell
- a custom root wrapper that stays in sync with Dyrected light and dark mode
Why use the shared theme API
Theme state looks simple until different parts of the app start making different assumptions.
Using the shared theme API means:
- every component reads the same current theme
- theme preference and resolved theme stay in one place
- your custom shell stays aligned with Dyrected light and dark mode
- you do not need to write your own preference and system-theme sync logic
Typical setups
In React, import the theme API from @dyrected/react. The highest-level entry is AdminThemeProvider, and the easiest way to apply the resolved theme class is AdminThemedRoot.
import {
AdminThemeProvider,
AdminThemedRoot,
useAdminTheme,
} from "@dyrected/react";
function ThemeSwitcher() {
const { theme, setTheme } = useAdminTheme();
return (
<select
value={theme}
onChange={(event) => setTheme(event.target.value as "system" | "light" | "dark")}
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}
export function CustomAdminShell({ children }: { children: React.ReactNode }) {
return (
<AdminThemeProvider>
<AdminThemedRoot>
<ThemeSwitcher />
{children}
</AdminThemedRoot>
</AdminThemeProvider>
);
}In Vue, the equivalent setup is provideAdminTheme() plus useAdminTheme().
<script setup lang="ts">
import { provideAdminTheme, useAdminTheme } from "@dyrected/vue";
provideAdminTheme();
const theme = useAdminTheme();
</script>
<template>
<div :class="theme.themeClassName.value" :data-theme="theme.resolvedTheme.value">
<select
:value="theme.theme.value"
@change="theme.setTheme(($event.target as HTMLSelectElement).value as 'system' | 'light' | 'dark')"
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<slot />
</div>
</template>In Vue, the same semantic contract exists, but the state fields are refs.
Fallback behavior
Both frameworks allow useAdminTheme() to return a usable fallback even if no provider is mounted yet.
That fallback is intentionally limited:
- you still get a resolved theme
- you still get a theme class name
setThemebecomes a no-op- there is no real controller instance behind the result
That behavior is useful for rendering consistency, but it should not replace mounting the provider in a real custom shell.
Recommended path
Mount one theme boundary near the root of your custom admin shell:
- React:
AdminThemeProvider - Vue:
provideAdminTheme()
Then use useAdminTheme() anywhere below it for toggles, themed roots, and theme-aware UI.
There is a lower-level controller layer behind this, but most readers can ignore it.
If your product app already has its own theme state and the admin is only one embedded surface inside it, do not promote useAdminTheme() to become the app-wide source of truth. Keep the preference at the host-app level and pass it into DyrectedAdmin through its theme props instead.
Media Hooks & Composables
Build custom uploaders, URL import flows, and media pickers with `@dyrected/react` and `@dyrected/vue` without reimplementing the media pipeline yourself.
Audit History
Turn on audit logging with one flag to record every create, update, and delete on a collection — with a before-and-after snapshot and the acting user — then read that history from the SDK or the admin.