Claude Skill

mobile-ui-components-react-native-paper

React Native Paper v5+ - Material Design 3 theming, PaperProvider, key components, dynamic color, accessibility, tree-shaking, custom fonts, navigation integration

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_mobile-ui-components-react-native-paper_skills_mobile-ui-components-react-native-paper-3a51ef5.zip · 16 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/mobile-ui-components-react-native-paper/skills/mobile-ui-components-react-native-paper
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

React Native Paper Patterns

Quick Guide: React Native Paper v5+ provides Material Design 3 (Material You) components for React Native. Wrap your app in PaperProvider (MD3 is the default). Use useTheme() to access colors/fonts. Wrap Dialogs in Portal. Use the babel plugin for production bundle optimization. For BottomNavigation with React Navigation, use BottomNavigation.Bar as a custom tabBar (the old createMaterialBottomTabNavigator is deprecated). On Android 12+, use expo-material3-theme for system dynamic colors.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST wrap your app root in PaperProvider - all Paper components require the provider context to function)

(You MUST wrap Dialog, Menu, and similar overlay components in Portal - without it they render inline instead of above other content)

(You MUST use useTheme() to access theme colors and fonts in components - never hardcode Material Design color values)

(You MUST add react-native-paper/babel to production plugins for bundle optimization - without it the entire library is included)

</critical_requirements>


Auto-detection: React Native Paper, react-native-paper, PaperProvider, MD3LightTheme, MD3DarkTheme, useTheme, Portal, FAB, Appbar, BottomNavigation, SegmentedButtons, configureFonts, adaptNavigationTheme, Card, Dialog, Snackbar, TextInput, Button mode, Surface, Chip, Searchbar, ActivityIndicator, Banner, Divider, ProgressBar, Switch, RadioButton, Checkbox, DataTable, Tooltip, Badge, Menu, Drawer.Item

When to use:

  • Setting up PaperProvider with custom MD3 themes (light, dark, dynamic color)
  • Using Paper components (Button, Card, TextInput, FAB, Appbar, Dialog, Snackbar, SegmentedButtons)
  • Integrating Paper's BottomNavigation.Bar with React Navigation bottom tabs
  • Configuring custom fonts with configureFonts for MD3 typography
  • Implementing Android 12+ dynamic color theming
  • Bridging Paper and React Navigation themes with adaptNavigationTheme

Key patterns covered:

  • PaperProvider setup and MD3 theming (light, dark, custom, dynamic color)
  • Component usage: Button modes, Card variants, TextInput modes, FAB sizes/variants, Dialog with Portal
  • BottomNavigation.Bar integration with React Navigation v7 bottom tabs
  • Custom fonts with configureFonts and TypeScript custom text variants
  • Bundle optimization with react-native-paper/babel plugin
  • Accessibility patterns (built-in a11y props, screen reader support)

When NOT to use:

  • General React Native component architecture (not Paper-specific)
  • Navigation patterns beyond BottomNavigation integration (not Paper-specific)
  • Custom animations or gestures (not a UI component library concern)

Detailed Resources:




<decision_framework>

Decision Framework

Component Selection

What type of action does the user perform?
├─ Primary action on screen → FAB (icon="plus" or extended with label)
├─ High-emphasis button → Button mode="contained"
├─ Medium-emphasis button → Button mode="outlined" or "contained-tonal"
├─ Low-emphasis button → Button mode="text"
└─ Segmented choice → SegmentedButtons

What type of content container?
├─ Tappable entry point → Card mode="elevated" with onPress
├─ Informational group → Card mode="outlined" (non-interactive)
├─ Elevated surface → Surface elevation={2}
└─ Dismissable message → Snackbar (with optional action)

What type of text input?
├─ Dense form (many fields) → TextInput mode="flat"
├─ Prominent input (few fields) → TextInput mode="outlined"
└─ Search → Searchbar component

What type of navigation bar?
├─ Top of screen → Appbar.Header with Appbar.Content + Appbar.Action
├─ Bottom tabs (with React Navigation) → BottomNavigation.Bar as tabBar
└─ Screen header with back → Appbar.Header with Appbar.BackAction

Theming Decision

Do you need custom brand colors?
├─ YES → Extend MD3LightTheme/MD3DarkTheme with your colors
└─ NO → Use default PaperProvider (MD3 applied automatically)

Do you need Android 12+ system colors?
├─ YES → Use expo-material3-theme's useMaterial3Theme hook
└─ NO → Use your static custom theme

Do you need custom fonts?
├─ YES → Use configureFonts({ config: fontConfig })
│   └─ Need custom text variants? → Use customText<'myVariant'>()
└─ NO → Default MD3 typography (Roboto/System/sans-serif) is applied

Do you need to bridge Paper + React Navigation themes?
├─ YES → Use adaptNavigationTheme() to unify color schemes
└─ NO → Configure each provider's theme independently

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Missing PaperProvider at app root - All Paper components silently fall back to unstyled defaults without the provider. Symptoms: wrong colors, missing ripples, broken Dialogs.
  • Dialog without Portal wrapper - Dialog renders inline in the component tree and gets clipped by parent containers or hidden behind navigation headers.
  • Hardcoding MD3 color values - Use theme.colors.primary, theme.colors.surface, etc. via useTheme(). Hardcoded hex values break when the theme changes (dark mode, dynamic color, brand update).
  • Missing babel plugin in production - Without react-native-paper/babel, the entire library is bundled regardless of which components you import.
  • Using deprecated createMaterialBottomTabNavigator - Deprecated since v5.14. Use @react-navigation/bottom-tabs with BottomNavigation.Bar as custom tabBar instead.

Medium Priority Issues:

  • Using require() instead of ES2015 import for Paper components - the babel plugin only optimizes import statements
  • Not providing onDismiss callback on Dialog - users cannot close the dialog with back button or outside tap
  • Overriding component accessibilityRole without reason - Paper components have correct roles by default
  • Using mode="contained" for all buttons - use appropriate emphasis levels (text, outlined, contained-tonal, contained)
  • Not switching theme based on system color scheme - leads to always-light or always-dark UI regardless of user preference

Gotchas & Edge Cases:

  • useTheme() returns the default theme if called outside PaperProvider - no error thrown, just wrong colors
  • TextInput label floats over outline in some edge cases (known issue) - apply outlineStyle padding workaround
  • Snackbar renders at bottom of its parent by default - wrap in Portal to display as a true overlay above tab bars
  • Dialog does not support scrollable content by default - use Dialog.ScrollArea for long content instead of wrapping in ScrollView
  • Card.Cover images have no default aspect ratio - set explicit height or use resizeMode
  • Android StatusBar does not automatically adapt to Paper theme - manually set StatusBar backgroundColor / barStyle
  • Variable fonts may not render correctly on all platforms - install each weight as a separate .ttf file
  • FAB.Group visibility: set visible={true} explicitly - it defaults to true but can silently become false if parent re-renders with stale props
  • MD2 mode (version: 2) and MD3 cannot coexist in the same provider - choose one per PaperProvider
  • react-native-safe-area-context is a required peer dependency since v5 - install it even if not using SafeAreaView directly

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST wrap your app root in PaperProvider - all Paper components require the provider context to function)

(You MUST wrap Dialog, Menu, and similar overlay components in Portal - without it they render inline instead of above other content)

(You MUST use useTheme() to access theme colors and fonts in components - never hardcode Material Design color values)

(You MUST add react-native-paper/babel to production plugins for bundle optimization - without it the entire library is included)

Failure to follow these rules will cause unstyled components, invisible dialogs, broken dark mode, and bloated bundles.

</critical_reminders>

Files (skills)
  • examples
    • components.md 9.4 KB
      # React Native Paper - Component Patterns
      
      > Key component usage patterns. See [SKILL.md](../SKILL.md) for component selection guidance and red flags. See [core.md](core.md) for theming setup.
      
      ---
      
      ## Pattern 1: Card with Sub-Components
      
      Card supports three modes: `elevated` (default, with shadow), `outlined` (border, no shadow), `contained` (flat, no border or shadow).
      
      ```typescript
      import { Card, Text, Button, Avatar } from "react-native-paper";
      
      const COVER_HEIGHT = 200;
      
      function ArticleCard({ article, onRead }: ArticleCardProps) {
        return (
          <Card mode="elevated" onPress={() => onRead(article.id)}>
            <Card.Title
              title={article.title}
              subtitle={article.author}
              left={(props) => <Avatar.Icon {...props} icon="account" />}
            />
            <Card.Cover
              source={{ uri: article.imageUrl }}
              style={{ height: COVER_HEIGHT }}
            />
            <Card.Content style={{ paddingTop: 12 }}>
              <Text variant="bodyMedium">{article.summary}</Text>
            </Card.Content>
            <Card.Actions>
              <Button onPress={() => onRead(article.id)}>Read More</Button>
            </Card.Actions>
          </Card>
        );
      }
      ```
      
      **Why good:** Sub-components (Title, Cover, Content, Actions) handle spacing and layout automatically. Avatar in `left` prop renders correctly sized. `mode="elevated"` adds MD3 elevation shadow.
      
      ---
      
      ## Pattern 2: TextInput with Error State and Adornments
      
      ```typescript
      import { useState } from "react";
      import { TextInput, HelperText } from "react-native-paper";
      
      const MIN_PASSWORD_LENGTH = 8;
      
      function LoginForm() {
        const [email, setEmail] = useState("");
        const [password, setPassword] = useState("");
        const [passwordVisible, setPasswordVisible] = useState(false);
      
        const emailError = email.length > 0 && !email.includes("@");
        const passwordError = password.length > 0 && password.length < MIN_PASSWORD_LENGTH;
      
        return (
          <>
            <TextInput
              mode="outlined"
              label="Email"
              value={email}
              onChangeText={setEmail}
              error={emailError}
              keyboardType="email-address"
              autoCapitalize="none"
              left={<TextInput.Icon icon="email" />}
            />
            <HelperText type="error" visible={emailError}>
              Please enter a valid email address
            </HelperText>
      
            <TextInput
              mode="outlined"
              label="Password"
              value={password}
              onChangeText={setPassword}
              error={passwordError}
              secureTextEntry={!passwordVisible}
              right={
                <TextInput.Icon
                  icon={passwordVisible ? "eye-off" : "eye"}
                  onPress={() => setPasswordVisible((v) => !v)}
                />
              }
            />
            <HelperText type="error" visible={passwordError}>
              {`Password must be at least ${MIN_PASSWORD_LENGTH} characters`}
            </HelperText>
          </>
        );
      }
      ```
      
      **Why good:** `error` prop applies MD3 error color to outline and label automatically. `HelperText` with `type="error"` renders in error color and animates visibility. `TextInput.Icon` in `right` prop positions the toggle correctly inside the input.
      
      ---
      
      ## Pattern 3: FAB Variants and FAB.Group
      
      FAB supports sizes (`small`, `medium`, `large`) and variants (`primary`, `secondary`, `tertiary`, `surface`).
      
      ```typescript
      import { useState } from "react";
      import { StyleSheet } from "react-native";
      import { FAB, Portal } from "react-native-paper";
      
      const FAB_BOTTOM_OFFSET = 16;
      const FAB_RIGHT_OFFSET = 16;
      
      // Simple FAB
      function CreateButton({ onPress }: { onPress: () => void }) {
        return (
          <FAB
            icon="plus"
            onPress={onPress}
            style={styles.fab}
          />
        );
      }
      
      // Extended FAB with label
      function ComposeButton({ onPress }: { onPress: () => void }) {
        return (
          <FAB
            icon="pencil"
            label="Compose"
            onPress={onPress}
            style={styles.fab}
          />
        );
      }
      
      // FAB.Group - expandable speed dial
      function ActionMenu() {
        const [open, setOpen] = useState(false);
      
        return (
          <Portal>
            <FAB.Group
              open={open}
              visible
              icon={open ? "close" : "plus"}
              actions={[
                { icon: "camera", label: "Photo", onPress: () => {} },
                { icon: "file-document", label: "Document", onPress: () => {} },
                { icon: "map-marker", label: "Location", onPress: () => {} },
              ]}
              onStateChange={({ open }) => setOpen(open)}
            />
          </Portal>
        );
      }
      
      const styles = StyleSheet.create({
        fab: {
          position: "absolute",
          bottom: FAB_BOTTOM_OFFSET,
          right: FAB_RIGHT_OFFSET,
        },
      });
      ```
      
      **Why good:** FAB.Group wrapped in Portal renders the speed dial above all content. `visible` controls show/hide animation. `onStateChange` manages open state. Extended FAB with `label` provides context for the action.
      
      ---
      
      ## Pattern 4: Appbar.Header Modes
      
      Appbar supports four modes: `small` (default, 64px), `medium` (112px), `large` (152px), `center-aligned` (64px, centered title).
      
      ```typescript
      import { Appbar } from "react-native-paper";
      
      // Standard top bar
      function ScreenHeader({ navigation, title }: HeaderProps) {
        return (
          <Appbar.Header mode="small">
            <Appbar.BackAction onPress={() => navigation.goBack()} />
            <Appbar.Content title={title} />
            <Appbar.Action icon="magnify" onPress={onSearch} />
            <Appbar.Action icon="dots-vertical" onPress={onMenu} />
          </Appbar.Header>
        );
      }
      
      // Large header for home/landing screens
      function HomeHeader() {
        return (
          <Appbar.Header mode="large" elevated>
            <Appbar.Content title="Inbox" />
            <Appbar.Action icon="magnify" onPress={onSearch} />
          </Appbar.Header>
        );
      }
      ```
      
      **Why good:** `mode="large"` provides the MD3 large top app bar with animated collapsing behavior. `elevated` adds subtle background tint. `Appbar.BackAction` uses the platform-appropriate back icon.
      
      ---
      
      ## Pattern 5: Dialog with Portal (Complete Pattern)
      
      ```typescript
      import { useState } from "react";
      import { Portal, Dialog, Button, Text, RadioButton } from "react-native-paper";
      
      function SortDialog({ visible, onDismiss, onSelect, currentSort }: SortDialogProps) {
        const [selected, setSelected] = useState(currentSort);
      
        return (
          <Portal>
            <Dialog visible={visible} onDismiss={onDismiss}>
              <Dialog.Icon icon="sort" />
              <Dialog.Title style={{ textAlign: "center" }}>Sort By</Dialog.Title>
              <Dialog.Content>
                <RadioButton.Group
                  value={selected}
                  onValueChange={(value) => setSelected(value)}
                >
                  <RadioButton.Item label="Date (newest)" value="date-desc" />
                  <RadioButton.Item label="Date (oldest)" value="date-asc" />
                  <RadioButton.Item label="Name (A-Z)" value="name-asc" />
                </RadioButton.Group>
              </Dialog.Content>
              <Dialog.Actions>
                <Button onPress={onDismiss}>Cancel</Button>
                <Button onPress={() => onSelect(selected)}>Apply</Button>
              </Dialog.Actions>
            </Dialog>
          </Portal>
        );
      }
      ```
      
      **Why good:** `Dialog.Icon` renders above the title (MD3 pattern). `Dialog.ScrollArea` would replace `Dialog.Content` for long scrollable content. Portal ensures the dialog floats above everything.
      
      ---
      
      ## Pattern 6: Snackbar with Action and Portal
      
      ```typescript
      import { useState } from "react";
      import { Portal, Snackbar } from "react-native-paper";
      
      const SNACKBAR_DURATION = 4000;
      
      function ItemList() {
        const [snackbarVisible, setSnackbarVisible] = useState(false);
        const [deletedItem, setDeletedItem] = useState<Item | null>(null);
      
        const handleDelete = (item: Item) => {
          setDeletedItem(item);
          deleteItem(item.id);
          setSnackbarVisible(true);
        };
      
        const handleUndo = () => {
          if (deletedItem) {
            restoreItem(deletedItem);
          }
          setSnackbarVisible(false);
        };
      
        return (
          <>
            {/* List content here */}
            <Portal>
              <Snackbar
                visible={snackbarVisible}
                onDismiss={() => setSnackbarVisible(false)}
                duration={SNACKBAR_DURATION}
                action={{ label: "Undo", onPress: handleUndo }}
              >
                Item deleted
              </Snackbar>
            </Portal>
          </>
        );
      }
      ```
      
      **Why good:** Portal wrapping ensures Snackbar renders above bottom tabs and other content. `action` with undo follows MD3 pattern. Named `SNACKBAR_DURATION` constant avoids magic number. `onDismiss` is required and must update the `visible` state.
      
      ---
      
      ## Pattern 7: SegmentedButtons (Single and Multi-Select)
      
      ```typescript
      import { useState } from "react";
      import { SegmentedButtons } from "react-native-paper";
      
      // Single select (value is a string)
      function ViewModeSelector() {
        const [viewMode, setViewMode] = useState("list");
      
        return (
          <SegmentedButtons
            value={viewMode}
            onValueChange={setViewMode}
            buttons={[
              { value: "list", icon: "view-list", label: "List" },
              { value: "grid", icon: "view-grid", label: "Grid" },
              { value: "compact", icon: "view-compact", label: "Compact" },
            ]}
          />
        );
      }
      
      // Multi-select (value is an array of strings)
      function FilterSelector() {
        const [filters, setFilters] = useState<string[]>([]);
      
        return (
          <SegmentedButtons
            multiSelect
            value={filters}
            onValueChange={setFilters}
            buttons={[
              { value: "photos", icon: "image", label: "Photos" },
              { value: "videos", icon: "video", label: "Videos" },
              { value: "audio", icon: "music-note", label: "Audio" },
            ]}
          />
        );
      }
      ```
      
      **Why good:** `multiSelect` toggles between string (single) and string[] (multi) value types. `density` prop (`"regular"` | `"small"` | `"medium"` | `"high"`) controls vertical size for space-constrained UIs.
      
    • core.md 8 KB
      # React Native Paper - Core Patterns
      
      > PaperProvider setup, theming, custom fonts, dynamic color, babel plugin. See [SKILL.md](../SKILL.md) for decision guidance and red flags.
      
      **Prerequisites**: React Native project with `react-native-paper` and `react-native-safe-area-context` installed.
      
      ---
      
      ## Pattern 1: PaperProvider with Light/Dark Theme
      
      ```typescript
      import { useColorScheme } from "react-native";
      import {
        MD3LightTheme,
        MD3DarkTheme,
        PaperProvider,
      } from "react-native-paper";
      import { SafeAreaProvider } from "react-native-safe-area-context";
      
      // Extend the default themes with your brand colors
      const lightTheme = {
        ...MD3LightTheme,
        colors: {
          ...MD3LightTheme.colors,
          primary: "#6750A4",
          primaryContainer: "#EADDFF",
          secondary: "#625B71",
          secondaryContainer: "#E8DEF8",
          tertiary: "#7D5260",
          tertiaryContainer: "#FFD8E4",
          surface: "#FFFBFE",
          error: "#B3261E",
        },
      };
      
      const darkTheme = {
        ...MD3DarkTheme,
        colors: {
          ...MD3DarkTheme.colors,
          primary: "#D0BCFF",
          primaryContainer: "#4F378B",
          secondary: "#CCC2DC",
          secondaryContainer: "#4A4458",
          tertiary: "#EFB8C8",
          tertiaryContainer: "#633B48",
          surface: "#1C1B1F",
          error: "#F2B8B5",
        },
      };
      
      export function AppRoot() {
        const colorScheme = useColorScheme();
        const theme = colorScheme === "dark" ? darkTheme : lightTheme;
      
        return (
          <SafeAreaProvider>
            <PaperProvider theme={theme}>
              <App />
            </PaperProvider>
          </SafeAreaProvider>
        );
      }
      ```
      
      **Why good:** Extends default themes (preserves all ~50 MD3 color roles you didn't override), switches automatically on system preference, SafeAreaProvider wraps PaperProvider for proper inset handling
      
      ---
      
      ## Pattern 2: Typed Custom Theme with useAppTheme
      
      When adding custom properties to the theme, create a typed hook to preserve TypeScript inference throughout your app.
      
      ```typescript
      import {
        MD3LightTheme,
        useTheme,
        type MD3Theme,
      } from "react-native-paper";
      
      // Add custom properties to the theme
      const theme = {
        ...MD3LightTheme,
        colors: {
          ...MD3LightTheme.colors,
          brandPrimary: "#1A73E8",
          brandSecondary: "#174EA6",
          success: "#0F9D58",
          warning: "#F9AB00",
        },
      };
      
      // Derive the type from the actual theme object
      export type AppTheme = typeof theme;
      
      // Create a typed hook - use this instead of useTheme() throughout the app
      export function useAppTheme() {
        return useTheme<AppTheme>();
      }
      
      // Usage in components:
      function StatusBadge({ status }: { status: "success" | "warning" | "error" }) {
        const { colors } = useAppTheme();
        // colors.brandPrimary, colors.success, colors.warning are all typed
        const backgroundColor = colors[status]; // TypeScript-safe lookup
        return <View style={{ backgroundColor }} />;
      }
      ```
      
      **Why good:** Custom color properties are type-checked at every call site. Adding `brandPrimary` to the theme means `useAppTheme().colors.brandPrimary` auto-completes and type-checks.
      
      ---
      
      ## Pattern 3: Dynamic Color with Android 12+ System Theme
      
      Use `expo-material3-theme` to pull the user's wallpaper-derived Material You colors on Android 12+. Falls back gracefully on iOS and older Android.
      
      ```typescript
      import { useColorScheme } from "react-native";
      import { MD3LightTheme, MD3DarkTheme, PaperProvider } from "react-native-paper";
      import { useMaterial3Theme } from "@pchmn/expo-material3-theme";
      
      export function AppRoot() {
        const colorScheme = useColorScheme();
        const { theme: material3Theme } = useMaterial3Theme();
      
        // material3Theme.light and material3Theme.dark contain system-derived colors
        // On iOS / older Android, a sensible fallback palette is returned
        const paperTheme =
          colorScheme === "dark"
            ? { ...MD3DarkTheme, colors: material3Theme.dark }
            : { ...MD3LightTheme, colors: material3Theme.light };
      
        return (
          <PaperProvider theme={paperTheme}>
            <App />
          </PaperProvider>
        );
      }
      ```
      
      **Why good:** Users on Android 12+ see their wallpaper-derived colors automatically. The library provides a complete fallback palette for iOS and older Android, so no conditional logic is needed. Material3Theme colors follow the same structure as Paper's MD3 color roles.
      
      ---
      
      ## Pattern 4: Custom Fonts with configureFonts
      
      Use `configureFonts` to apply custom font families across all MD3 typography variants (display, headline, title, label, body).
      
      ```typescript
      import {
        configureFonts,
        MD3LightTheme,
        PaperProvider,
      } from "react-native-paper";
      import { Platform } from "react-native";
      
      // Global font override - applies to ALL MD3 variants
      const fontConfig = {
        fontFamily: Platform.select({
          ios: "Inter",
          android: "Inter",
          web: "Inter, sans-serif",
          default: "Inter",
        }),
      };
      
      const theme = {
        ...MD3LightTheme,
        fonts: configureFonts({ config: fontConfig }),
      };
      
      // Override specific variants if needed:
      const headlineConfig = {
        headlineLarge: {
          fontFamily: "PlayfairDisplay-Bold",
          fontWeight: "700" as const,
          fontSize: 32,
          lineHeight: 40,
          letterSpacing: 0,
        },
      };
      
      const themeWithCustomHeadline = {
        ...MD3LightTheme,
        fonts: configureFonts({ config: headlineConfig }),
      };
      ```
      
      **Why good:** `configureFonts` applies your font family to all 15 MD3 variants (displaySmall through bodyLarge) in one call. Override individual variants only when their specific typography needs to differ.
      
      **Important:** Install each font weight as a separate `.ttf` file. Variable fonts cause rendering issues on some platforms (especially Android).
      
      ---
      
      ## Pattern 5: Custom Text Variants with TypeScript
      
      When you need typography variants beyond MD3's built-in 15 (like "caption" or "overline"), use `customText` for type-safe custom variants.
      
      ```typescript
      import { customText } from "react-native-paper";
      
      // Create a typed Text component that accepts your custom variants
      export const Text = customText<"caption" | "overline">();
      
      // Usage:
      <Text variant="caption">Small helper text</Text>
      <Text variant="headlineMedium">Standard MD3 variant still works</Text>
      ```
      
      **Why good:** Custom variants are type-checked. Using an undefined variant (e.g., `variant="foo"`) is a compile error. Built-in MD3 variants remain available.
      
      ---
      
      ## Pattern 6: Bridging Paper and React Navigation Themes
      
      Use `adaptNavigationTheme` to unify Paper and React Navigation color schemes so both systems use the same palette.
      
      ```typescript
      import {
        MD3LightTheme,
        MD3DarkTheme,
        adaptNavigationTheme,
        PaperProvider,
      } from "react-native-paper";
      import {
        NavigationContainer,
        DefaultTheme as NavigationDefaultTheme,
        DarkTheme as NavigationDarkTheme,
      } from "@react-navigation/native";
      import { useColorScheme } from "react-native";
      
      const { LightTheme: adaptedLight, DarkTheme: adaptedDark } = adaptNavigationTheme({
        reactNavigationLight: NavigationDefaultTheme,
        reactNavigationDark: NavigationDarkTheme,
        materialLight: MD3LightTheme,
        materialDark: MD3DarkTheme,
      });
      
      export function AppRoot() {
        const colorScheme = useColorScheme();
        const paperTheme = colorScheme === "dark" ? MD3DarkTheme : MD3LightTheme;
        const navTheme = colorScheme === "dark" ? adaptedDark : adaptedLight;
      
        return (
          <PaperProvider theme={paperTheme}>
            <NavigationContainer theme={navTheme}>
              <App />
            </NavigationContainer>
          </PaperProvider>
        );
      }
      ```
      
      **Why good:** Without `adaptNavigationTheme`, Paper components and React Navigation headers/tabs use different color palettes. The adapter maps Paper's MD3 color roles to React Navigation's theme structure, ensuring consistent colors across both systems.
      
      **Note:** React Navigation 7.0.0+ automatically picks up Paper's typography when both themes are adapted.
      
      ---
      
      ## Pattern 7: Babel Plugin for Bundle Optimization
      
      ```javascript
      // babel.config.js
      module.exports = {
        presets: ["module:metro-react-native-babel-preset"],
        env: {
          production: {
            plugins: ["react-native-paper/babel"],
          },
        },
      };
      ```
      
      **Why good:** Without the plugin, `import { Button } from "react-native-paper"` bundles the entire library. The plugin rewrites to per-component paths. Only works with ES2015 `import` syntax, not `require()`.
      
    • navigation-integration.md 6.8 KB
      # React Native Paper - Navigation Integration
      
      > BottomNavigation.Bar with React Navigation, adaptNavigationTheme, Drawer integration. See [SKILL.md](../SKILL.md) for decision guidance. See [core.md](core.md) for theme bridging setup.
      
      ---
      
      ## Pattern 1: BottomNavigation.Bar with React Navigation v7
      
      The old `createMaterialBottomTabNavigator` is deprecated since v5.14. Use `@react-navigation/bottom-tabs` with `BottomNavigation.Bar` as a custom `tabBar`.
      
      ```typescript
      import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
      import { CommonActions } from "@react-navigation/native";
      import { BottomNavigation } from "react-native-paper";
      import Icon from "@react-native-vector-icons/material-design-icons";
      
      const ICON_SIZE = 24;
      
      const Tab = createBottomTabNavigator();
      
      export function MainTabs() {
        return (
          <Tab.Navigator
            screenOptions={{ headerShown: false }}
            tabBar={({ navigation, state, descriptors, insets }) => (
              <BottomNavigation.Bar
                navigationState={state}
                safeAreaInsets={insets}
                onTabPress={({ route, preventDefault }) => {
                  const event = navigation.emit({
                    type: "tabPress",
                    target: route.key,
                    canPreventDefault: true,
                  });
      
                  if (event.defaultPrevented) {
                    preventDefault();
                  } else {
                    navigation.dispatch({
                      ...CommonActions.navigate(route.name, route.params),
                      target: state.key,
                    });
                  }
                }}
                renderIcon={({ route, focused, color }) => {
                  const { options } = descriptors[route.key];
                  if (options.tabBarIcon) {
                    return options.tabBarIcon({ focused, color, size: ICON_SIZE });
                  }
                  return null;
                }}
                getLabelText={({ route }) => {
                  const { options } = descriptors[route.key];
                  const label =
                    typeof options.tabBarLabel === "string"
                      ? options.tabBarLabel
                      : options.title ?? route.name;
                  return label;
                }}
              />
            )}
          >
            <Tab.Screen
              name="Home"
              component={HomeScreen}
              options={{
                tabBarLabel: "Home",
                tabBarIcon: ({ color }) => (
                  <Icon name="home" color={color} size={ICON_SIZE} />
                ),
              }}
            />
            <Tab.Screen
              name="Search"
              component={SearchScreen}
              options={{
                tabBarLabel: "Search",
                tabBarIcon: ({ color }) => (
                  <Icon name="magnify" color={color} size={ICON_SIZE} />
                ),
              }}
            />
            <Tab.Screen
              name="Profile"
              component={ProfileScreen}
              options={{
                tabBarLabel: "Profile",
                tabBarIcon: ({ color }) => (
                  <Icon name="account" color={color} size={ICON_SIZE} />
                ),
                tabBarBadge: 3, // Shows badge with number
              }}
            />
          </Tab.Navigator>
        );
      }
      ```
      
      **Why good:** Uses React Navigation's native bottom-tabs API (latest, maintained) with Paper's MD3-styled bar. `CommonActions.navigate` ensures the correct tab reset behavior. `canPreventDefault` allows listeners to intercept tab presses (e.g., unsaved changes confirmation). Badge support via `tabBarBadge` in screen options.
      
      ---
      
      ## Pattern 2: Drawer Content with Paper Components
      
      Use Paper's `Drawer.Item`, `Drawer.Section`, and `Drawer.CollapsedItem` inside React Navigation's drawer for MD3-styled drawer content.
      
      ```typescript
      import { View, StyleSheet } from "react-native";
      import {
        DrawerContentScrollView,
        type DrawerContentComponentProps,
      } from "@react-navigation/drawer";
      import { Drawer, Avatar, Text, Switch, useTheme } from "react-native-paper";
      import { useState } from "react";
      
      const AVATAR_SIZE = 48;
      const DRAWER_PADDING = 16;
      
      export function CustomDrawerContent(props: DrawerContentComponentProps) {
        const theme = useTheme();
        const [isDarkMode, setIsDarkMode] = useState(false);
      
        return (
          <DrawerContentScrollView {...props}>
            {/* User info header */}
            <View style={styles.header}>
              <Avatar.Image size={AVATAR_SIZE} source={{ uri: user.avatarUrl }} />
              <Text variant="titleMedium" style={styles.userName}>
                {user.name}
              </Text>
              <Text variant="bodySmall" style={{ color: theme.colors.onSurfaceVariant }}>
                {user.email}
              </Text>
            </View>
      
            {/* Main navigation */}
            <Drawer.Section title="Navigation">
              <Drawer.Item
                label="Home"
                icon="home"
                active={props.state.index === 0}
                onPress={() => props.navigation.navigate("Home")}
              />
              <Drawer.Item
                label="Settings"
                icon="cog"
                active={props.state.index === 1}
                onPress={() => props.navigation.navigate("Settings")}
              />
            </Drawer.Section>
      
            {/* Preferences section */}
            <Drawer.Section title="Preferences">
              <View style={styles.preference}>
                <Text variant="bodyLarge">Dark Mode</Text>
                <Switch value={isDarkMode} onValueChange={setIsDarkMode} />
              </View>
            </Drawer.Section>
          </DrawerContentScrollView>
        );
      }
      
      const styles = StyleSheet.create({
        header: {
          paddingHorizontal: DRAWER_PADDING,
          paddingVertical: DRAWER_PADDING,
        },
        userName: {
          marginTop: 8,
        },
        preference: {
          flexDirection: "row",
          justifyContent: "space-between",
          alignItems: "center",
          paddingHorizontal: DRAWER_PADDING,
          paddingVertical: 12,
        },
      });
      ```
      
      **Why good:** `DrawerContentScrollView` handles safe area insets. `Drawer.Section` with `title` groups items with an MD3 divider. `Drawer.Item` with `active` prop shows the MD3 active indicator. Paper's `Switch` and `Avatar` integrate seamlessly.
      
      ---
      
      ## Pattern 3: Appbar.Header as Navigation Header
      
      Replace React Navigation's default header with Paper's Appbar.Header for MD3 styling.
      
      ```typescript
      import { Appbar } from "react-native-paper";
      import type { NativeStackHeaderProps } from "@react-navigation/native-stack";
      
      export function CustomNavigationBar({
        navigation,
        route,
        options,
        back,
      }: NativeStackHeaderProps) {
        const title = options.headerTitle ?? options.title ?? route.name;
      
        return (
          <Appbar.Header elevated>
            {back ? <Appbar.BackAction onPress={navigation.goBack} /> : null}
            <Appbar.Content title={typeof title === "string" ? title : route.name} />
            {options.headerRight
              ? options.headerRight({ canGoBack: !!back })
              : null}
          </Appbar.Header>
        );
      }
      
      // Usage in navigator:
      // <Stack.Navigator screenOptions={{ header: (props) => <CustomNavigationBar {...props} /> }}>
      ```
      
      **Why good:** Replaces React Navigation's default header with MD3-styled Appbar. `elevated` adds the MD3 surface tint. Respects `headerTitle`, `title`, and `headerRight` from screen options. `Appbar.BackAction` uses the correct platform back icon.
      
  • reference.md 7.9 KB
    # React Native Paper Quick Reference
    
    > Decision frameworks, MD3 color roles, typography scale. See [SKILL.md](SKILL.md) for red flags and anti-patterns.
    
    ---
    
    ## Component Decision Table
    
    | Need                    | Component              | Mode/Variant                             | Notes                                   |
    | ----------------------- | ---------------------- | ---------------------------------------- | --------------------------------------- |
    | Primary screen action   | `FAB`                  | `variant="primary"`                      | Position absolute bottom-right          |
    | High-emphasis button    | `Button`               | `mode="contained"`                       | Filled background                       |
    | Medium-emphasis button  | `Button`               | `mode="outlined"` or `"contained-tonal"` | Border or tonal fill                    |
    | Low-emphasis button     | `Button`               | `mode="text"`                            | Text only, no background                |
    | Segmented choice        | `SegmentedButtons`     | Single or `multiSelect`                  | 2-5 options                             |
    | Tappable card           | `Card`                 | `mode="elevated"`                        | With `onPress`                          |
    | Informational card      | `Card`                 | `mode="outlined"`                        | No `onPress`                            |
    | Text input (dense form) | `TextInput`            | `mode="flat"`                            | Underline style                         |
    | Text input (prominent)  | `TextInput`            | `mode="outlined"`                        | Border style                            |
    | Search                  | `Searchbar`            | -                                        | Built-in clear button                   |
    | Dialog / confirmation   | `Dialog`               | Wrap in `Portal`                         | Sub-components: Title, Content, Actions |
    | Temporary message       | `Snackbar`             | Wrap in `Portal`                         | With optional `action`                  |
    | Top bar                 | `Appbar.Header`        | `mode="small"` / `"large"`               | With BackAction, Content, Action        |
    | Bottom tabs             | `BottomNavigation.Bar` | Custom `tabBar`                          | With React Navigation bottom-tabs       |
    | Menu / dropdown         | `Menu`                 | Wrap in `Portal`                         | Anchored to trigger element             |
    | Loading                 | `ActivityIndicator`    | -                                        | Reads theme color                       |
    | Toggle                  | `Switch`               | -                                        | MD3 styled                              |
    | Selection (single)      | `RadioButton.Group`    | -                                        | With RadioButton.Item                   |
    | Selection (multi)       | `Checkbox`             | -                                        | Checkbox.Item for label                 |
    | Chip / tag              | `Chip`                 | `mode="flat"` / `"outlined"`             | With optional icon, onClose             |
    | Progress                | `ProgressBar`          | -                                        | Determinate or indeterminate            |
    | Data display            | `DataTable`            | -                                        | With Header, Row, Cell, Pagination      |
    | Info banner             | `Banner`               | -                                        | With actions and optional icon          |
    
    ---
    
    ## MD3 Color Roles Reference
    
    | Role                 | Light Usage                  | Dark Usage                   |
    | -------------------- | ---------------------------- | ---------------------------- |
    | `primary`            | Primary buttons, FAB         | Primary buttons, FAB         |
    | `onPrimary`          | Text/icons on primary        | Text/icons on primary        |
    | `primaryContainer`   | Tonal buttons, chips         | Tonal buttons, chips         |
    | `onPrimaryContainer` | Text on primaryContainer     | Text on primaryContainer     |
    | `secondary`          | Less prominent elements      | Less prominent elements      |
    | `secondaryContainer` | Selected states, active tabs | Selected states, active tabs |
    | `tertiary`           | Accent elements              | Accent elements              |
    | `surface`            | Card backgrounds, sheets     | Card backgrounds, sheets     |
    | `surfaceVariant`     | Input backgrounds, dividers  | Input backgrounds, dividers  |
    | `onSurface`          | Primary text color           | Primary text color           |
    | `onSurfaceVariant`   | Secondary text, icons        | Secondary text, icons        |
    | `outline`            | Borders, dividers            | Borders, dividers            |
    | `outlineVariant`     | Subtle borders               | Subtle borders               |
    | `error`              | Error states                 | Error states                 |
    | `errorContainer`     | Error backgrounds            | Error backgrounds            |
    | `elevation.level0-5` | Surface elevation overlays   | Surface elevation overlays   |
    | `inverseSurface`     | Snackbar background          | Snackbar background          |
    | `inverseOnSurface`   | Text on snackbar             | Text on snackbar             |
    
    ---
    
    ## MD3 Typography Scale
    
    | Variant          | Default Size | Default Weight | Use Case          |
    | ---------------- | ------------ | -------------- | ----------------- |
    | `displayLarge`   | 57px         | 400            | Hero text         |
    | `displayMedium`  | 45px         | 400            | Large headers     |
    | `displaySmall`   | 36px         | 400            | Section headers   |
    | `headlineLarge`  | 32px         | 400            | Page titles       |
    | `headlineMedium` | 28px         | 400            | Section titles    |
    | `headlineSmall`  | 24px         | 400            | Card titles       |
    | `titleLarge`     | 22px         | 400            | App bar title     |
    | `titleMedium`    | 16px         | 500            | List item primary |
    | `titleSmall`     | 14px         | 500            | List item label   |
    | `labelLarge`     | 14px         | 500            | Button text       |
    | `labelMedium`    | 12px         | 500            | Tab labels        |
    | `labelSmall`     | 11px         | 500            | Badge text        |
    | `bodyLarge`      | 16px         | 400            | Body text         |
    | `bodyMedium`     | 14px         | 400            | Secondary text    |
    | `bodySmall`      | 12px         | 400            | Caption, helper   |
    
    Usage: `<Text variant="headlineMedium">Title</Text>`
    
    ---
    
    ## FAB Size Reference
    
    | Size     | Height | Use Case                    |
    | -------- | ------ | --------------------------- |
    | `small`  | 40pt   | Secondary actions, toolbars |
    | `medium` | 56pt   | Primary action (default)    |
    | `large`  | 96pt   | Prominent primary action    |
    
    ---
    
    ## Button Mode Emphasis Levels
    
    ```
    Low emphasis ←──────────────────────── High emphasis
       text    outlined    contained-tonal    elevated    contained
    ```
    
    - **text**: Lowest emphasis. Cancel, dismiss, tertiary actions.
    - **outlined**: Medium-low. Secondary actions alongside contained.
    - **contained-tonal**: Medium. Important but not primary (save draft vs submit).
    - **elevated**: Medium-high. Same as contained but with shadow.
    - **contained**: Highest. Primary, most important action on screen.
    
    ---
    
    ## Setup Checklist
    
    - [ ] `react-native-paper` installed
    - [ ] `react-native-safe-area-context` installed (required peer dependency)
    - [ ] `@react-native-vector-icons/material-design-icons` installed (bare RN) or using Expo icons
    - [ ] `PaperProvider` wrapping app root
    - [ ] `SafeAreaProvider` wrapping `PaperProvider`
    - [ ] Custom theme extending `MD3LightTheme` / `MD3DarkTheme` (if needed)
    - [ ] `react-native-paper/babel` added to production plugins in `babel.config.js`
    - [ ] `adaptNavigationTheme` used if combining with React Navigation
    
  • SKILL.md 15.5 KB
    ---
    name: mobile-ui-components-react-native-paper
    description: React Native Paper v5+ - Material Design 3 theming, PaperProvider, key components, dynamic color, accessibility, tree-shaking, custom fonts, navigation integration
    ---
    
    # React Native Paper Patterns
    
    > **Quick Guide:** React Native Paper v5+ provides Material Design 3 (Material You) components for React Native. Wrap your app in `PaperProvider` (MD3 is the default). Use `useTheme()` to access colors/fonts. Wrap Dialogs in `Portal`. Use the babel plugin for production bundle optimization. For BottomNavigation with React Navigation, use `BottomNavigation.Bar` as a custom `tabBar` (the old `createMaterialBottomTabNavigator` is deprecated). On Android 12+, use `expo-material3-theme` for system dynamic colors.
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST wrap your app root in `PaperProvider` - all Paper components require the provider context to function)**
    
    **(You MUST wrap Dialog, Menu, and similar overlay components in `Portal` - without it they render inline instead of above other content)**
    
    **(You MUST use `useTheme()` to access theme colors and fonts in components - never hardcode Material Design color values)**
    
    **(You MUST add `react-native-paper/babel` to production plugins for bundle optimization - without it the entire library is included)**
    
    </critical_requirements>
    
    ---
    
    **Auto-detection:** React Native Paper, react-native-paper, PaperProvider, MD3LightTheme, MD3DarkTheme, useTheme, Portal, FAB, Appbar, BottomNavigation, SegmentedButtons, configureFonts, adaptNavigationTheme, Card, Dialog, Snackbar, TextInput, Button mode, Surface, Chip, Searchbar, ActivityIndicator, Banner, Divider, ProgressBar, Switch, RadioButton, Checkbox, DataTable, Tooltip, Badge, Menu, Drawer.Item
    
    **When to use:**
    
    - Setting up PaperProvider with custom MD3 themes (light, dark, dynamic color)
    - Using Paper components (Button, Card, TextInput, FAB, Appbar, Dialog, Snackbar, SegmentedButtons)
    - Integrating Paper's BottomNavigation.Bar with React Navigation bottom tabs
    - Configuring custom fonts with `configureFonts` for MD3 typography
    - Implementing Android 12+ dynamic color theming
    - Bridging Paper and React Navigation themes with `adaptNavigationTheme`
    
    **Key patterns covered:**
    
    - PaperProvider setup and MD3 theming (light, dark, custom, dynamic color)
    - Component usage: Button modes, Card variants, TextInput modes, FAB sizes/variants, Dialog with Portal
    - BottomNavigation.Bar integration with React Navigation v7 bottom tabs
    - Custom fonts with `configureFonts` and TypeScript custom text variants
    - Bundle optimization with `react-native-paper/babel` plugin
    - Accessibility patterns (built-in a11y props, screen reader support)
    
    **When NOT to use:**
    
    - General React Native component architecture (not Paper-specific)
    - Navigation patterns beyond BottomNavigation integration (not Paper-specific)
    - Custom animations or gestures (not a UI component library concern)
    
    **Detailed Resources:**
    
    - [examples/core.md](examples/core.md) - PaperProvider setup, theming, custom fonts, babel plugin, dynamic color
    - [examples/components.md](examples/components.md) - Button, Card, TextInput, FAB, Appbar, Dialog, Snackbar, SegmentedButtons
    - [examples/navigation-integration.md](examples/navigation-integration.md) - BottomNavigation.Bar with React Navigation, adaptNavigationTheme, Drawer integration
    - [reference.md](reference.md) - Component decision framework, theme color roles, MD3 typography scale
    
    ---
    
    <philosophy>
    
    ## Philosophy
    
    React Native Paper is a **Material Design 3 component library** for React Native. It provides pre-built, accessible, and themeable components that follow the Material You design system. The library is maintained by Callstack and is one of the most mature UI libraries in the React Native ecosystem.
    
    **Core principles:**
    
    1. **Theme-driven styling** - All components read colors, fonts, and roundness from the theme. Override the theme, not individual component styles, for consistent branding.
    2. **MD3 by default** - v5+ applies Material Design 3 automatically. MD2 is still supported via `{ version: 2 }` but is legacy.
    3. **Portal for overlays** - Dialog, Menu, and similar overlay components must be wrapped in `Portal` to render above other content.
    4. **Accessibility built-in** - Components include proper `accessibilityRole`, `accessibilityState`, and screen reader support out of the box. Don't override these without reason.
    5. **Bundle optimization required** - Add the babel plugin in production to exclude unused components from the bundle.
    
    **Mental model:** Paper provides the Material Design visual layer. Your app's component architecture, state management, and navigation structure are separate concerns handled by their respective skills.
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: PaperProvider and Theming
    
    Wrap your app root in `PaperProvider`. MD3 is applied by default. Extend `MD3LightTheme` or `MD3DarkTheme` for custom branding. Use `useTheme()` to access theme values in components.
    
    ```typescript
    import {
      MD3LightTheme,
      MD3DarkTheme,
      PaperProvider,
      useTheme,
    } from "react-native-paper";
    import { useColorScheme } from "react-native";
    
    const lightTheme = {
      ...MD3LightTheme,
      colors: {
        ...MD3LightTheme.colors,
        primary: "#6750A4",
        secondary: "#625B71",
      },
    };
    
    const darkTheme = {
      ...MD3DarkTheme,
      colors: {
        ...MD3DarkTheme.colors,
        primary: "#D0BCFF",
        secondary: "#CCC2DC",
      },
    };
    
    // In your root component:
    const colorScheme = useColorScheme();
    const theme = colorScheme === "dark" ? darkTheme : lightTheme;
    // <PaperProvider theme={theme}>...</PaperProvider>
    ```
    
    **Why good:** Extends default themes (preserves all MD3 color roles), dynamically switches on system preference, all Paper components pick up the overrides
    
    For typed custom themes, dynamic color with `expo-material3-theme`, and `configureFonts`, see [examples/core.md](examples/core.md).
    
    ---
    
    ### Pattern 2: Portal for Overlay Components
    
    Dialog, Menu, and overlay components must be wrapped in `Portal` to render above all other content. `Portal` teleports children to the nearest `Portal.Host` (which `PaperProvider` includes by default).
    
    ```typescript
    import { Portal, Dialog, Button, Text } from "react-native-paper";
    
    // Inside a component:
    <Portal>
      <Dialog visible={visible} onDismiss={hideDialog}>
        <Dialog.Title>Confirm</Dialog.Title>
        <Dialog.Content>
          <Text variant="bodyMedium">Are you sure?</Text>
        </Dialog.Content>
        <Dialog.Actions>
          <Button onPress={hideDialog}>Cancel</Button>
          <Button onPress={handleConfirm}>OK</Button>
        </Dialog.Actions>
      </Dialog>
    </Portal>
    ```
    
    **Why good:** Portal ensures the dialog renders above all screen content including headers and tabs. Without Portal, the dialog renders inline in the component tree and may be clipped or hidden behind other elements.
    
    ---
    
    ### Pattern 3: Button Modes
    
    Paper's Button supports five modes with increasing visual emphasis: `text`, `outlined`, `contained`, `elevated`, and `contained-tonal`.
    
    ```typescript
    import { Button } from "react-native-paper";
    
    <Button mode="text" onPress={onCancel}>Cancel</Button>
    <Button mode="outlined" onPress={onEdit}>Edit</Button>
    <Button mode="contained-tonal" onPress={onSave}>Save Draft</Button>
    <Button mode="contained" onPress={onSubmit}>Submit</Button>
    <Button mode="elevated" onPress={onAction}>Elevated</Button>
    
    // With icon and loading state:
    <Button mode="contained" icon="camera" loading={isUploading} onPress={onUpload}>
      Upload Photo
    </Button>
    ```
    
    **Why good:** Modes map directly to MD3 emphasis levels (low to high: text < outlined < contained-tonal < contained). Use the right mode for the action's importance.
    
    See [examples/components.md](examples/components.md) for all component patterns.
    
    ---
    
    ### Pattern 4: TextInput with Validation
    
    TextInput supports `flat` and `outlined` modes, error state, and adornments (icons/affixes) via `TextInput.Icon` and `TextInput.Affix`.
    
    ```typescript
    import { TextInput } from "react-native-paper";
    
    <TextInput
      mode="outlined"
      label="Email"
      value={email}
      onChangeText={setEmail}
      error={!!emailError}
      keyboardType="email-address"
      autoCapitalize="none"
      left={<TextInput.Icon icon="email" />}
    />
    ```
    
    **Why good:** `error` prop automatically applies MD3 error color to outline and label. Adornments via `TextInput.Icon` and `TextInput.Affix` are positioned correctly by the component.
    
    ---
    
    ### Pattern 5: BottomNavigation.Bar with React Navigation
    
    The old `createMaterialBottomTabNavigator` is deprecated since v5.14. Use `@react-navigation/bottom-tabs` with `BottomNavigation.Bar` as a custom `tabBar` renderer instead.
    
    ```typescript
    import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
    import { BottomNavigation } from "react-native-paper";
    
    const Tab = createBottomTabNavigator();
    
    // Pass BottomNavigation.Bar as the tabBar prop:
    <Tab.Navigator
      tabBar={({ navigation, state, descriptors, insets }) => (
        <BottomNavigation.Bar
          navigationState={state}
          safeAreaInsets={insets}
          onTabPress={({ route, preventDefault }) => { /* emit tabPress, navigate */ }}
          renderIcon={({ route, focused, color }) => { /* render tabBarIcon */ }}
          getLabelText={({ route }) => { /* return label */ }}
        />
      )}
    >
    ```
    
    **Why good:** Uses React Navigation's latest bottom-tabs API with Paper's MD3-styled bar. Handles tab press events correctly (with `canPreventDefault` for listeners). Deprecated `createMaterialBottomTabNavigator` is no longer maintained.
    
    See [examples/navigation-integration.md](examples/navigation-integration.md) for full implementation including `adaptNavigationTheme` and drawer integration.
    
    ---
    
    ### Pattern 6: Bundle Optimization with Babel Plugin
    
    Add `react-native-paper/babel` to `env.production.plugins` in `babel.config.js`. Without it, importing `{ Button }` from `react-native-paper` includes the entire library. The plugin rewrites imports to per-component paths, reducing bundle size significantly.
    
    **Important:** Only works with ES2015 `import` syntax, not `require()`.
    
    See [examples/core.md](examples/core.md) for the babel config snippet.
    
    </patterns>
    
    ---
    
    <decision_framework>
    
    ## Decision Framework
    
    ### Component Selection
    
    ```
    What type of action does the user perform?
    ├─ Primary action on screen → FAB (icon="plus" or extended with label)
    ├─ High-emphasis button → Button mode="contained"
    ├─ Medium-emphasis button → Button mode="outlined" or "contained-tonal"
    ├─ Low-emphasis button → Button mode="text"
    └─ Segmented choice → SegmentedButtons
    
    What type of content container?
    ├─ Tappable entry point → Card mode="elevated" with onPress
    ├─ Informational group → Card mode="outlined" (non-interactive)
    ├─ Elevated surface → Surface elevation={2}
    └─ Dismissable message → Snackbar (with optional action)
    
    What type of text input?
    ├─ Dense form (many fields) → TextInput mode="flat"
    ├─ Prominent input (few fields) → TextInput mode="outlined"
    └─ Search → Searchbar component
    
    What type of navigation bar?
    ├─ Top of screen → Appbar.Header with Appbar.Content + Appbar.Action
    ├─ Bottom tabs (with React Navigation) → BottomNavigation.Bar as tabBar
    └─ Screen header with back → Appbar.Header with Appbar.BackAction
    ```
    
    ### Theming Decision
    
    ```
    Do you need custom brand colors?
    ├─ YES → Extend MD3LightTheme/MD3DarkTheme with your colors
    └─ NO → Use default PaperProvider (MD3 applied automatically)
    
    Do you need Android 12+ system colors?
    ├─ YES → Use expo-material3-theme's useMaterial3Theme hook
    └─ NO → Use your static custom theme
    
    Do you need custom fonts?
    ├─ YES → Use configureFonts({ config: fontConfig })
    │   └─ Need custom text variants? → Use customText<'myVariant'>()
    └─ NO → Default MD3 typography (Roboto/System/sans-serif) is applied
    
    Do you need to bridge Paper + React Navigation themes?
    ├─ YES → Use adaptNavigationTheme() to unify color schemes
    └─ NO → Configure each provider's theme independently
    ```
    
    </decision_framework>
    
    ---
    
    <red_flags>
    
    ## RED FLAGS
    
    **High Priority Issues:**
    
    - **Missing `PaperProvider` at app root** - All Paper components silently fall back to unstyled defaults without the provider. Symptoms: wrong colors, missing ripples, broken Dialogs.
    - **Dialog without `Portal` wrapper** - Dialog renders inline in the component tree and gets clipped by parent containers or hidden behind navigation headers.
    - **Hardcoding MD3 color values** - Use `theme.colors.primary`, `theme.colors.surface`, etc. via `useTheme()`. Hardcoded hex values break when the theme changes (dark mode, dynamic color, brand update).
    - **Missing babel plugin in production** - Without `react-native-paper/babel`, the entire library is bundled regardless of which components you import.
    - **Using deprecated `createMaterialBottomTabNavigator`** - Deprecated since v5.14. Use `@react-navigation/bottom-tabs` with `BottomNavigation.Bar` as custom `tabBar` instead.
    
    **Medium Priority Issues:**
    
    - Using `require()` instead of ES2015 `import` for Paper components - the babel plugin only optimizes `import` statements
    - Not providing `onDismiss` callback on Dialog - users cannot close the dialog with back button or outside tap
    - Overriding component `accessibilityRole` without reason - Paper components have correct roles by default
    - Using `mode="contained"` for all buttons - use appropriate emphasis levels (text, outlined, contained-tonal, contained)
    - Not switching theme based on system color scheme - leads to always-light or always-dark UI regardless of user preference
    
    **Gotchas & Edge Cases:**
    
    - `useTheme()` returns the default theme if called outside `PaperProvider` - no error thrown, just wrong colors
    - TextInput label floats over outline in some edge cases (known issue) - apply `outlineStyle` padding workaround
    - `Snackbar` renders at bottom of its parent by default - wrap in `Portal` to display as a true overlay above tab bars
    - `Dialog` does not support scrollable content by default - use `Dialog.ScrollArea` for long content instead of wrapping in ScrollView
    - `Card.Cover` images have no default aspect ratio - set explicit height or use `resizeMode`
    - Android `StatusBar` does not automatically adapt to Paper theme - manually set `StatusBar` `backgroundColor` / `barStyle`
    - Variable fonts may not render correctly on all platforms - install each weight as a separate `.ttf` file
    - `FAB.Group` visibility: set `visible={true}` explicitly - it defaults to `true` but can silently become `false` if parent re-renders with stale props
    - MD2 mode (`version: 2`) and MD3 cannot coexist in the same provider - choose one per `PaperProvider`
    - `react-native-safe-area-context` is a required peer dependency since v5 - install it even if not using SafeAreaView directly
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## CRITICAL REMINDERS
    
    > **All code must follow project conventions in CLAUDE.md**
    
    **(You MUST wrap your app root in `PaperProvider` - all Paper components require the provider context to function)**
    
    **(You MUST wrap Dialog, Menu, and similar overlay components in `Portal` - without it they render inline instead of above other content)**
    
    **(You MUST use `useTheme()` to access theme colors and fonts in components - never hardcode Material Design color values)**
    
    **(You MUST add `react-native-paper/babel` to production plugins for bundle optimization - without it the entire library is included)**
    
    **Failure to follow these rules will cause unstyled components, invisible dialogs, broken dark mode, and bloated bundles.**
    
    </critical_reminders>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related