React Native Theme TransitionReact Native Theme Transition
API

createThemeTransition

Factory function that creates a typed theme transition system from your theme definitions.

Validates configuration at initialization and returns a self-contained { ThemeTransitionProvider, useTheme } API. No singletons — multiple theme scopes can coexist.

TypeScript infers theme names and color tokens from the themes object. No manual generics needed.

const { ThemeTransitionProvider, useTheme } =
  createThemeTransition({
    themes: { light, dark },
    duration: 350,
    onTransitionStart: (name) => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium),
    onThemeChange: (name) => analytics.track('theme_switch', { theme: name }),
  });

Config

OptionTypeDefaultDescription
themesRecord<string, ThemeDefinition>requiredObject of theme definitions. All themes must share the same color token keys. The name 'system' is reserved.
durationnumber350Cross-fade animation duration in milliseconds.
darkThemesName[][systemThemeMap.dark] or ['dark']Theme names that use a dark color scheme. The library calls Appearance.setColorScheme internally to keep native UI (alerts, date pickers) in sync.
systemThemeMap{ light: Name, dark: Name }Maps OS appearance to theme names. Required when your themes are not named 'light'/'dark' and you want system mode.
onTransitionStart(name: Name) => voidCalled when an animated transition begins, before screenshot capture. Does not fire for instant switches.
onTransitionEnd(name: Name) => voidCalled after an animated transition completes. Does not fire for instant switches or capture failures.
onThemeChange(name: Name) => voidCalled on every theme change — animated, instant, or system-driven. The only callback guaranteed to fire.

Theme definitions

Every theme must share the exact same token keys. Mismatched keys throw at initialization.

const light = { bg: '#fff', text: '#000', primary: '#007AFF' };

// Type-safe enforcement of matching keys on secondary themes:
const dark: Record<keyof typeof light, string> = {
  bg: '#000', text: '#fff', primary: '#0A84FF',
};

Rules:

  • At least one theme required
  • 'system' is reserved — cannot be a theme key
  • Keys must be identical across all themes (order doesn't matter)

System theme mapping

Maps OS appearance ('light' / 'dark') to your theme names. Required when themes aren't named 'light'/'dark' and you want system mode.

createThemeTransition({
  themes: { sunrise, midnight, ocean },
  systemThemeMap: { light: 'sunrise', dark: 'midnight' },
});

Both light and dark must be provided. Values must reference existing theme names.

Dark themes

The library automatically calls Appearance.setColorScheme to keep native UI elements (alerts, date pickers, keyboards) in sync. darkThemes tells it which themes use a dark color scheme.

createThemeTransition({
  themes: { light, dark, ocean, rose },
  darkThemes: ['dark', 'ocean'],
  systemThemeMap: { light: 'light', dark: 'dark' },
});

Defaults to [systemThemeMap.dark] if systemThemeMap is provided, otherwise ['dark']. In system mode the library sets Appearance.setColorScheme('unspecified') so the OS drives native appearance.

Type inference

You never need to pass generic types manually:

const light = { background: '#fff', text: '#000' };
const dark  = { background: '#000', text: '#fff' };

const { useTheme } = createThemeTransition({ themes: { light, dark } });

const { colors, name, setTheme } = useTheme();

colors.background  // type: string, autocomplete: 'background' | 'text'
colors.foo         // TypeScript error
name               // type: 'light' | 'dark'
setTheme('dark')   // ok
setTheme('ocean')  // TypeScript error
setTheme('system') // always valid

On this page