Skip to main content
UI Kitten supports dynamic theme switching at runtime, allowing users to toggle between light and dark modes or switch to custom themes without reloading the application.

Overview

This guide demonstrates how to implement runtime theme switching using React Context and Hooks. You’ll learn how to:
  • Create a theme context to manage theme state
  • Toggle between light and dark themes
  • Persist theme preferences
  • Apply theme changes throughout your app
This guide builds upon the basic theming concepts. Make sure you’re familiar with Theming fundamentals before proceeding.

Create Theme Context

First, create a context to share theme state across your application. Create a theme-context.js file:
import React from 'react';

export const ThemeContext = React.createContext({
  theme: 'light',
  toggleTheme: () => {},
});
This context provides:
  • theme - The name of the current theme ('light' or 'dark')
  • toggleTheme - A function to switch between themes

Configure Application Provider

Update your App.js to provide theme context to your application:
import React from 'react';
import * as eva from '@eva-design/eva';
import { ApplicationProvider, IconRegistry } from '@ui-kitten/components';
import { EvaIconsPack } from '@ui-kitten/eva-icons';
import { AppNavigator } from './navigation.component';
import { ThemeContext } from './theme-context';

export default () => {
  const [theme, setTheme] = React.useState('light');

  const toggleTheme = () => {
    const nextTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(nextTheme);
  };

  return (
    <>
      <IconRegistry icons={EvaIconsPack} />
      <ThemeContext.Provider value={{ theme, toggleTheme }}>
        <ApplicationProvider {...eva} theme={eva[theme]}>
          <AppNavigator />
        </ApplicationProvider>
      </ThemeContext.Provider>
    </>
  );
};
1

Create State

Use useState to track the current theme name ('light' or 'dark')
2

Toggle Function

Implement toggleTheme to switch between light and dark themes
3

Provide Context

Wrap your app with ThemeContext.Provider to share theme state
4

Apply Theme

Pass eva[theme] to ApplicationProvider to apply the selected theme

Use Theme Context in Components

Access the theme context in any component using the useContext hook:
import React from 'react';
import { SafeAreaView } from 'react-native';
import { Button, Divider, Layout, TopNavigation } from '@ui-kitten/components';
import { ThemeContext } from './theme-context';

export const HomeScreen = ({ navigation }) => {
  const themeContext = React.useContext(ThemeContext);

  const navigateDetails = () => {
    navigation.navigate('Details');
  };

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <TopNavigation title='MyApp' alignment='center' />
      <Divider />
      <Layout style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Button style={{ marginVertical: 4 }} onPress={navigateDetails}>
          OPEN DETAILS
        </Button>
        <Button style={{ marginVertical: 4 }} onPress={themeContext.toggleTheme}>
          TOGGLE THEME
        </Button>
      </Layout>
    </SafeAreaView>
  );
};

Advanced Implementation

For production applications, consider these enhancements:

Persist Theme Preference

Save the user’s theme preference using AsyncStorage:
import React, { useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as eva from '@eva-design/eva';
import { ApplicationProvider } from '@ui-kitten/components';
import { ThemeContext } from './theme-context';

const THEME_KEY = 'app-theme';

export default () => {
  const [theme, setTheme] = React.useState('light');
  const [isLoading, setIsLoading] = React.useState(true);

  useEffect(() => {
    // Load saved theme on mount
    const loadTheme = async () => {
      try {
        const savedTheme = await AsyncStorage.getItem(THEME_KEY);
        if (savedTheme !== null) {
          setTheme(savedTheme);
        }
      } catch (error) {
        console.error('Failed to load theme:', error);
      } finally {
        setIsLoading(false);
      }
    };

    loadTheme();
  }, []);

  const toggleTheme = async () => {
    const nextTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(nextTheme);
    
    try {
      await AsyncStorage.setItem(THEME_KEY, nextTheme);
    } catch (error) {
      console.error('Failed to save theme:', error);
    }
  };

  if (isLoading) {
    return null; // Or a loading screen
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <ApplicationProvider {...eva} theme={eva[theme]}>
        {/* Your app content */}
      </ApplicationProvider>
    </ThemeContext.Provider>
  );
};

Multiple Theme Options

Support more than two themes:
import * as eva from '@eva-design/eva';
import * as material from '@eva-design/material';

const themes = {
  'eva-light': eva.light,
  'eva-dark': eva.dark,
  'material-light': material.light,
  'material-dark': material.dark,
};

export default () => {
  const [theme, setTheme] = React.useState('eva-light');

  const changeTheme = (themeName) => {
    if (themes[themeName]) {
      setTheme(themeName);
    }
  };

  return (
    <ThemeContext.Provider value={{ theme, changeTheme }}>
      <ApplicationProvider {...eva} theme={themes[theme]}>
        {/* Your app content */}
      </ApplicationProvider>
    </ThemeContext.Provider>
  );
};

Custom Hook for Theme

Create a custom hook for cleaner theme access:
// useAppTheme.js
import React from 'react';
import { ThemeContext } from './theme-context';

export const useAppTheme = () => {
  const context = React.useContext(ThemeContext);
  
  if (!context) {
    throw new Error('useAppTheme must be used within ThemeContext.Provider');
  }
  
  return context;
};

// Usage in components
import { useAppTheme } from './useAppTheme';

export const HomeScreen = () => {
  const { theme, toggleTheme } = useAppTheme();
  
  return (
    <Button onPress={toggleTheme}>
      Current theme: {theme}
    </Button>
  );
};

State Management Integration

For larger applications, integrate theme management with your state management solution:

With Redux

// themeSlice.js
import { createSlice } from '@reduxjs/toolkit';

const themeSlice = createSlice({
  name: 'theme',
  initialState: { current: 'light' },
  reducers: {
    setTheme: (state, action) => {
      state.current = action.payload;
    },
    toggleTheme: (state) => {
      state.current = state.current === 'light' ? 'dark' : 'light';
    },
  },
});

export const { setTheme, toggleTheme } = themeSlice.actions;
export default themeSlice.reducer;

// App.js
import { useSelector } from 'react-redux';
import * as eva from '@eva-design/eva';
import { ApplicationProvider } from '@ui-kitten/components';

export default () => {
  const theme = useSelector((state) => state.theme.current);

  return (
    <ApplicationProvider {...eva} theme={eva[theme]}>
      {/* Your app content */}
    </ApplicationProvider>
  );
};

With MobX

// ThemeStore.js
import { makeAutoObservable } from 'mobx';

class ThemeStore {
  theme = 'light';

  constructor() {
    makeAutoObservable(this);
  }

  toggleTheme() {
    this.theme = this.theme === 'light' ? 'dark' : 'light';
  }

  setTheme(themeName) {
    this.theme = themeName;
  }
}

export const themeStore = new ThemeStore();

// App.js
import { observer } from 'mobx-react-lite';
import * as eva from '@eva-design/eva';
import { ApplicationProvider } from '@ui-kitten/components';
import { themeStore } from './ThemeStore';

const App = observer(() => {
  return (
    <ApplicationProvider {...eva} theme={eva[themeStore.theme]}>
      {/* Your app content */}
    </ApplicationProvider>
  );
});

export default App;

Theme Toggle UI

Create a reusable theme toggle component:
import React from 'react';
import { Toggle, Icon, Layout, Text } from '@ui-kitten/components';
import { ThemeContext } from './theme-context';

const SunIcon = (props) => <Icon {...props} name='sun-outline' />;
const MoonIcon = (props) => <Icon {...props} name='moon-outline' />;

export const ThemeToggle = () => {
  const themeContext = React.useContext(ThemeContext);
  const isDarkMode = themeContext.theme === 'dark';

  return (
    <Layout style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}>
      <SunIcon style={{ width: 24, height: 24, marginRight: 8 }} />
      <Toggle
        checked={isDarkMode}
        onChange={themeContext.toggleTheme}
      />
      <MoonIcon style={{ width: 24, height: 24, marginLeft: 8 }} />
    </Layout>
  );
};
When switching themes, all UI Kitten components will re-render. This is expected behavior and ensures the theme is applied consistently throughout your app.

Testing Theme Switching

Test theme switching in your components:
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import * as eva from '@eva-design/eva';
import { ApplicationProvider } from '@ui-kitten/components';
import { ThemeContext } from './theme-context';
import { HomeScreen } from './home.component';

const renderWithTheme = (component, theme = 'light') => {
  const toggleTheme = jest.fn();
  
  return render(
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <ApplicationProvider {...eva} theme={eva[theme]}>
        {component}
      </ApplicationProvider>
    </ThemeContext.Provider>
  );
};

test('toggles theme when button is pressed', () => {
  const { getByText } = renderWithTheme(<HomeScreen />);
  const toggleButton = getByText('TOGGLE THEME');
  
  fireEvent.press(toggleButton);
  
  expect(toggleTheme).toHaveBeenCalled();
});

Next Steps

Branding

Create custom themes with your brand colors

Custom Icons

Add custom icon packages to your themed application

Build docs developers (and LLMs) love