Recipes

Sub-themes per route

To theme one route differently from the rest of the app, wrap its element in a <Theme> boundary. For a whole section that should also respond to light/dark, register a theme chain and let the names compose.

Updated 3 days agoEdit on GitHubWeb & native

One route, one theme

The app sits under a <ThemeProvider> with its catalogue of themes. A single route opts into a different one by wrapping its element in <Theme>.

routes.tsx
import { Theme } from 'usemotif';
import { Route, Routes } from 'react-router';
 
<Routes>
<Route path="/" element={<Home />} />
<Route
  path="/marketing"
  element={
    <Theme name="marketing">
      <MarketingLanding />
    </Theme>
  }
/>
</Routes>;

marketing has to be in the provider's themes array. <Theme> sets data-theme="marketing" on its wrapper; everything inside resolves $colors.* against that theme, everything outside is untouched. No extra provider, no context juggling.

A route group with a layout route

Repeating <Theme> on every route in a section is noise. A layout route applies the boundary once for all of its children.

routes.tsx
<Routes>
<Route element={<ThemedSection name="admin" />}>
  <Route path="/admin" element={<AdminHome />} />
  <Route path="/admin/users" element={<AdminUsers />} />
  <Route path="/admin/billing" element={<AdminBilling />} />
</Route>
</Routes>;
 
// ThemedSection.tsx
import { Theme } from 'usemotif';
import { Outlet } from 'react-router';
 
export function ThemedSection({ name }: { name: string }) {
return (
  <Theme name={name}>
    <Outlet />
  </Theme>
);
}

Every route nested under the layout route renders inside the admin theme. Add a route, it inherits the theme; the wiring lives in one place.

Sections that still follow light/dark

A fixed <Theme name="admin"> ignores the user's light/dark choice — admin is admin whether the app is light or dark. When a section needs its own palette and should still flip with the rest of the app, use a theme chain.

Inside a dark provider, <Theme name="admin"> composes the names into a dark_admin lookup. Register the cross-product and each combination activates automatically.

App.tsx
import { ThemeProvider } from 'usemotif';
import {
light, dark,
lightAdmin, darkAdmin,
} from './themes';
 
<ThemeProvider
themes={[light, dark, lightAdmin, darkAdmin]}
active={resolved}
>
<AppRoutes />
</ThemeProvider>;

Name the chained themes light_admin and dark_admin so the composed lookup finds them. Now <Theme name="admin"> under a light app resolves to light_admin, and the same boundary under dark resolves to dark_admin — the section keeps its identity and still tracks the toggle. If a combination is missing, motif falls back to the inner name alone, then the parent.