Skip to content

Fix: React Native Paper Not Working — Theme Not Applying, Icons Missing, or Components Unstyled

FixDevs ·

Quick Answer

How to fix React Native Paper issues — PaperProvider setup, Material Design 3 theming, custom color schemes, icon configuration, dark mode, and Expo integration.

The Problem

Paper components render but look plain and unstyled:

import { Button, Text } from 'react-native-paper';

function App() {
  return (
    <Button mode="contained">Press me</Button>
  );
}
// Button renders as plain text — no Material Design styling

Or icons don’t show up:

<Button icon="camera">Take Photo</Button>
// Button renders without the camera icon

Or the custom theme doesn’t apply:

const theme = { colors: { primary: '#6200ee' } };
<PaperProvider theme={theme}>
  <Button mode="contained">Themed Button</Button>
</PaperProvider>
// Button still uses default colors

Why This Happens

React Native Paper implements Material Design for React Native. Common issues:

  • PaperProvider must wrap the entire app — all Paper components read theme values from the provider context. Without it, components fall back to unstyled defaults or crash.
  • Vector icons must be loaded — Paper uses @expo/vector-icons (MaterialCommunityIcons). In bare React Native (non-Expo), react-native-vector-icons must be installed and linked separately.
  • Paper v5 uses Material Design 3 — the theming API changed from v4 to v5. MD3LightTheme and MD3DarkTheme replace the v4 DefaultTheme and DarkTheme. Mixing versions causes styling issues.
  • Custom themes must extend the base theme — passing a partial theme object overrides the entire theme, losing default values. Use MD3LightTheme as the base and spread your customizations.

Fix 1: Setup with Expo

npx expo install react-native-paper react-native-safe-area-context
// App.tsx or app/_layout.tsx
import { PaperProvider, MD3LightTheme, MD3DarkTheme } from 'react-native-paper';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useColorScheme } from 'react-native';

// Custom theme extending Material Design 3
const lightTheme = {
  ...MD3LightTheme,
  colors: {
    ...MD3LightTheme.colors,
    primary: '#6750A4',
    secondary: '#625B71',
    tertiary: '#7D5260',
    // Custom colors
    brand: '#3b82f6',
  },
  roundness: 12,  // Border radius for components
};

const darkTheme = {
  ...MD3DarkTheme,
  colors: {
    ...MD3DarkTheme.colors,
    primary: '#D0BCFF',
    secondary: '#CCC2DC',
    tertiary: '#EFB8C8',
    brand: '#60a5fa',
  },
  roundness: 12,
};

export default function App() {
  const colorScheme = useColorScheme();
  const theme = colorScheme === 'dark' ? darkTheme : lightTheme;

  return (
    <SafeAreaProvider>
      <PaperProvider theme={theme}>
        <AppContent />
      </PaperProvider>
    </SafeAreaProvider>
  );
}

// Type the custom theme
type AppTheme = typeof lightTheme;

declare global {
  namespace ReactNativePaper {
    interface Theme extends AppTheme {}
  }
}
// babel.config.js — optional: reduce bundle size
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    env: {
      production: {
        plugins: ['react-native-paper/babel'],  // Tree-shaking unused components
      },
    },
  };
};

Fix 2: Core Components

import {
  Appbar, Button, Card, Text, TextInput, FAB, Chip, Avatar,
  Badge, Banner, Divider, List, Menu, Searchbar, SegmentedButtons,
  Snackbar, Surface, Switch, ToggleButton, IconButton,
  ProgressBar, ActivityIndicator, Dialog, Portal,
} from 'react-native-paper';
import { View, ScrollView } from 'react-native';
import { useState } from 'react';

function HomeScreen({ navigation }) {
  const [searchQuery, setSearchQuery] = useState('');

  return (
    <View style={{ flex: 1 }}>
      {/* App bar */}
      <Appbar.Header>
        <Appbar.BackAction onPress={() => navigation.goBack()} />
        <Appbar.Content title="Home" />
        <Appbar.Action icon="magnify" onPress={() => {}} />
        <Appbar.Action icon="dots-vertical" onPress={() => {}} />
      </Appbar.Header>

      <ScrollView style={{ flex: 1, padding: 16 }}>
        {/* Search */}
        <Searchbar
          placeholder="Search"
          value={searchQuery}
          onChangeText={setSearchQuery}
          style={{ marginBottom: 16 }}
        />

        {/* Card */}
        <Card style={{ marginBottom: 16 }}>
          <Card.Cover source={{ uri: 'https://picsum.photos/700' }} />
          <Card.Title
            title="Card Title"
            subtitle="Card Subtitle"
            left={(props) => <Avatar.Icon {...props} icon="account" />}
          />
          <Card.Content>
            <Text variant="bodyMedium">
              This is a Material Design 3 card with cover image.
            </Text>
          </Card.Content>
          <Card.Actions>
            <Button>Cancel</Button>
            <Button mode="contained">OK</Button>
          </Card.Actions>
        </Card>

        {/* Button variants */}
        <View style={{ gap: 8, marginBottom: 16 }}>
          <Button mode="contained" onPress={() => {}}>Contained</Button>
          <Button mode="outlined" onPress={() => {}}>Outlined</Button>
          <Button mode="text" onPress={() => {}}>Text</Button>
          <Button mode="elevated" onPress={() => {}}>Elevated</Button>
          <Button mode="contained-tonal" onPress={() => {}}>Tonal</Button>
          <Button mode="contained" icon="camera" onPress={() => {}}>With Icon</Button>
          <Button mode="contained" loading onPress={() => {}}>Loading</Button>
        </View>

        {/* Chips */}
        <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 16 }}>
          <Chip icon="tag" onPress={() => {}}>Tag</Chip>
          <Chip selected onPress={() => {}}>Selected</Chip>
          <Chip icon="close" onClose={() => {}}>Removable</Chip>
        </View>

        {/* Text input */}
        <TextInput
          label="Email"
          mode="outlined"
          placeholder="[email protected]"
          left={<TextInput.Icon icon="email" />}
          style={{ marginBottom: 16 }}
        />

        <TextInput
          label="Password"
          mode="outlined"
          secureTextEntry
          right={<TextInput.Icon icon="eye" />}
          style={{ marginBottom: 16 }}
        />
      </ScrollView>

      {/* FAB */}
      <FAB
        icon="plus"
        style={{ position: 'absolute', right: 16, bottom: 16 }}
        onPress={() => {}}
      />
    </View>
  );
}

Fix 3: Dialogs and Modals

import { Portal, Dialog, Button, Text, TextInput } from 'react-native-paper';
import { useState } from 'react';

function DialogExample() {
  const [visible, setVisible] = useState(false);
  const [input, setInput] = useState('');

  return (
    <>
      <Button onPress={() => setVisible(true)}>Show Dialog</Button>

      <Portal>
        <Dialog visible={visible} onDismiss={() => setVisible(false)}>
          <Dialog.Title>Create Item</Dialog.Title>
          <Dialog.Content>
            <Text variant="bodyMedium" style={{ marginBottom: 12 }}>
              Enter a name for your new item.
            </Text>
            <TextInput
              label="Name"
              mode="outlined"
              value={input}
              onChangeText={setInput}
            />
          </Dialog.Content>
          <Dialog.Actions>
            <Button onPress={() => setVisible(false)}>Cancel</Button>
            <Button onPress={() => { handleCreate(input); setVisible(false); }}>
              Create
            </Button>
          </Dialog.Actions>
        </Dialog>
      </Portal>
    </>
  );
}

// Snackbar (toast-like notification)
function SnackbarExample() {
  const [visible, setVisible] = useState(false);

  return (
    <>
      <Button onPress={() => setVisible(true)}>Show Snackbar</Button>
      <Snackbar
        visible={visible}
        onDismiss={() => setVisible(false)}
        duration={3000}
        action={{
          label: 'Undo',
          onPress: () => { /* undo action */ },
        }}
      >
        Item deleted successfully
      </Snackbar>
    </>
  );
}

Fix 4: List Components

import { List, Divider, Switch } from 'react-native-paper';
import { ScrollView } from 'react-native';
import { useState } from 'react';

function SettingsList() {
  const [notifications, setNotifications] = useState(true);
  const [darkMode, setDarkMode] = useState(false);

  return (
    <ScrollView>
      <List.Section>
        <List.Subheader>Account</List.Subheader>
        <List.Item
          title="Profile"
          description="Edit your profile information"
          left={(props) => <List.Icon {...props} icon="account" />}
          right={(props) => <List.Icon {...props} icon="chevron-right" />}
          onPress={() => {}}
        />
        <Divider />
        <List.Item
          title="Security"
          description="Password and two-factor authentication"
          left={(props) => <List.Icon {...props} icon="shield-lock" />}
          right={(props) => <List.Icon {...props} icon="chevron-right" />}
          onPress={() => {}}
        />
      </List.Section>

      <List.Section>
        <List.Subheader>Preferences</List.Subheader>
        <List.Item
          title="Notifications"
          description="Push and email notifications"
          left={(props) => <List.Icon {...props} icon="bell" />}
          right={() => (
            <Switch value={notifications} onValueChange={setNotifications} />
          )}
        />
        <Divider />
        <List.Item
          title="Dark Mode"
          left={(props) => <List.Icon {...props} icon="brightness-6" />}
          right={() => (
            <Switch value={darkMode} onValueChange={setDarkMode} />
          )}
        />
      </List.Section>

      {/* Expandable list */}
      <List.AccordionGroup>
        <List.Accordion title="Advanced" id="1" left={(props) => <List.Icon {...props} icon="cog" />}>
          <List.Item title="Cache" description="Clear app cache" onPress={() => {}} />
          <List.Item title="Data" description="Export your data" onPress={() => {}} />
          <List.Item title="Logs" description="View debug logs" onPress={() => {}} />
        </List.Accordion>
      </List.AccordionGroup>
    </ScrollView>
  );
}

Fix 5: Dynamic Theme with useTheme

import { useTheme, MD3Theme } from 'react-native-paper';
import { View, StyleSheet } from 'react-native';

function ThemedComponent() {
  const theme = useTheme<MD3Theme>();

  return (
    <View style={[styles.container, { backgroundColor: theme.colors.surface }]}>
      <View style={[styles.card, {
        backgroundColor: theme.colors.surfaceVariant,
        borderRadius: theme.roundness,
      }]}>
        <Text style={{ color: theme.colors.onSurfaceVariant }}>
          Themed card using hook
        </Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  card: { padding: 16 },
});

Fix 6: Custom Colors with Material You

// Generate a full Material Design 3 color scheme from a seed color
import { MD3LightTheme, configureFonts } from 'react-native-paper';

// Use Material Theme Builder: https://m3.material.io/theme-builder
// Export your color scheme and apply it:

const customColors = {
  primary: '#6750A4',
  onPrimary: '#FFFFFF',
  primaryContainer: '#EADDFF',
  onPrimaryContainer: '#21005D',
  secondary: '#625B71',
  onSecondary: '#FFFFFF',
  secondaryContainer: '#E8DEF8',
  onSecondaryContainer: '#1D192B',
  // ... full MD3 color palette
  surface: '#FFFBFE',
  onSurface: '#1C1B1F',
  surfaceVariant: '#E7E0EC',
  onSurfaceVariant: '#49454F',
  error: '#B3261E',
  onError: '#FFFFFF',
};

const theme = {
  ...MD3LightTheme,
  colors: {
    ...MD3LightTheme.colors,
    ...customColors,
  },
};

Still Not Working?

Components are unstyledPaperProvider must wrap your entire app. Every Paper component reads theme values from context. Without the provider, components render with browser/system defaults.

Icons are missing (empty space where icon should be) — Paper uses MaterialCommunityIcons. In Expo, @expo/vector-icons is pre-installed. In bare React Native, install react-native-vector-icons and link it. Check the icon name at materialdesignicons.com.

Custom theme colors don’t apply — don’t pass a partial theme. Always spread the base theme: { ...MD3LightTheme, colors: { ...MD3LightTheme.colors, primary: '#xxx' } }. A partial object replaces all colors, leaving most undefined.

v4 code doesn’t work in v5DefaultTheme and DarkTheme were replaced with MD3LightTheme and MD3DarkTheme. The colors shape changed completely for Material Design 3. Check the migration guide for renamed properties.

For related mobile UI issues, see Fix: Tamagui Not Working and Fix: NativeWind Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles