Skip to content

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

FixDevs · (Updated: )

Part of:  JavaScript & TypeScript Errors

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.

The deeper reason most “unstyled” problems happen is that Paper components do not have any default visual style of their own at the component level — every color, elevation, and font scale is read from theme context on render. If context is missing, theme is undefined, and the StyleSheet derived from theme falls back to either transparent or empty strings, which React Native silently ignores. The component still mounts and lays out, so there’s no error, just a flat, unstyled element. This is by design (theming is mandatory in Material 3), but it means a missing provider produces no warning in development.

Icon failures have a similar root cause. Paper renders icons by looking up a glyph name in a font file. If the font isn’t bundled into the native binary, the lookup returns an empty box that takes up the icon’s width but draws nothing. With Expo SDK 49+ the font is pre-registered automatically, but a bare React Native project needs an explicit font link via npx react-native-asset or react-native.config.js, plus a rebuild. Hot reload does not pick up new native assets; you must rebuild the iOS/Android binary.

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
      },
    },
  };
};

How Other Tools Handle This

Material Design is one philosophy among several in the React Native UI ecosystem. The choice affects bundle size, theming model, and how closely components track each platform’s native look.

NativeBase (now in maintenance) used a utility-prop API on top of styled-system, modeled after Chakra. Theming was JSON-based and any property could be overridden inline, which made customization fast but caused performance issues on large lists because each style was recomputed on every render. Paper recomputes theme-derived styles only when the theme object identity changes, which is why Paper works well with FlatList virtualization while NativeBase did not.

Tamagui takes the opposite approach: themes are compiled at build time via a Babel plugin, producing flat StyleSheets with zero runtime cost. Paper themes are pure runtime — they live in React context and re-render every consumer when the theme changes. Tamagui is faster on cold start and on theme-heavy screens, but Paper’s runtime model lets you swap themes (light/dark, brand variants) without a rebuild and without coordinating a compiler. See Fix: Tamagui Not Working for compiler-side issues that don’t apply to Paper.

UI Kitten is Eva Design rather than Material. It splits “mapping” (component-to-style rules) from theme (color tokens), which is more flexible but has a steeper learning curve. Paper’s MD3 theme is opinionated — you can change colors and roundness but not the underlying design language. If your app needs strict Material Design 3 fidelity for a Google Play submission, Paper is the only mainstream choice.

gluestack-ui (the NativeBase successor) embraces utility classes via NativeWind and aims for cross-platform parity with Tailwind. It’s the closest React Native equivalent to shadcn/ui — primitives you copy into your codebase. Paper ships as a black-box library; gluestack ships as source. If you want to fork a Button to behave differently, gluestack is friendlier. See Fix: NativeWind Not Working for the styling pipeline that backs it.

Material vs platform-native is the strategic question. Paper draws Material on iOS too, which can look out of place. If you need each platform to feel native, use platform-conditional imports (Platform.select) and pair Paper with @react-native-community/segmented-control or the new react-native core components for iOS-specific UI. Or pick a library like React Native Elements that aims for platform-aware defaults.

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.

Dialogs and Snackbars don’t appear on screen — they require a <Portal.Host> ancestor. PaperProvider includes one by default, but if you nest providers (for example a per-screen modal stack) the Portal can be mounted into the wrong tree. Wrap the offending screen with an explicit <Portal.Host> or check that only one PaperProvider lives at the root.

Metro fails to resolve react-native-vector-icons — in bare projects, you also need the font copied into the iOS/Android bundle. Run npx react-native-asset after configuring react-native.config.js, then do a clean rebuild (npx react-native run-ios --reset-cache or cd android && ./gradlew clean). See Fix: React Native Metro Bundler Failed for cache-related symptoms that look similar.

Theme switches flicker on app launchuseColorScheme() returns null for one frame before the system value resolves. Wrap the provider with Appearance.getColorScheme() as a synchronous fallback, or set an initial theme in a top-level state initializer so the first render is already correct.

For related mobile UI issues, see Fix: Expo Not Working and Fix: Tamagui 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