Skip to main content

Component Mapping: react-native-paper → gluestack-ui v3

This document provides detailed mapping between react-native-paper components and their gluestack-ui v3 equivalents.

Mapping Legend

SymbolMeaning
Direct1:1 mapping, minimal changes
WrapperNeeds wrapper to preserve Paper API
ComposeBuild from multiple Gluestack components
CustomRequires custom implementation

Typography

Text

Aspectreact-native-papergluestack-ui v3
Importimport { Text } from 'react-native-paper'import { Text } from '@gluestack-ui/themed'
MappingDirect
Usage Count32

Before:

<Text variant="headlineMedium">Title</Text>
<Text variant="bodyLarge">Body text</Text>

After:

<Text className="text-2xl font-semibold">Title</Text>
<Text className="text-base">Body text</Text>

Variant Mapping:

Paper VariantTailwind Classes
displayLargetext-6xl font-normal
displayMediumtext-5xl font-normal
displaySmalltext-4xl font-normal
headlineLargetext-3xl font-normal
headlineMediumtext-2xl font-normal
headlineSmalltext-xl font-normal
titleLargetext-xl font-medium
titleMediumtext-base font-medium
titleSmalltext-sm font-medium
bodyLargetext-base
bodyMediumtext-sm
bodySmalltext-xs
labelLargetext-sm font-medium
labelMediumtext-xs font-medium
labelSmalltext-[10px] font-medium

Buttons

Button

Aspectreact-native-papergluestack-ui v3
Importimport { Button } from 'react-native-paper'import { Button, ButtonText, ButtonIcon } from '@gluestack-ui/themed'
MappingWrapper
Usage Count24

Before:

<Button mode="contained" onPress={handlePress}>
Save
</Button>

<Button mode="outlined" icon="plus" onPress={handleAdd}>
Add Item
</Button>

<Button mode="text" loading={isLoading} onPress={handleSubmit}>
Submit
</Button>

After (with wrapper):

// Wrapper preserves Paper API
<Button mode="contained" onPress={handlePress}>
Save
</Button>

// Or native Gluestack API
<Button action="primary" onPress={handlePress}>
<ButtonText>Save</ButtonText>
</Button>

<Button variant="outline" onPress={handleAdd}>
<ButtonIcon as={PlusIcon} className="mr-2" />
<ButtonText>Add Item</ButtonText>
</Button>

<Button variant="link" onPress={handleSubmit}>
{isLoading && <ButtonSpinner className="mr-2" />}
<ButtonText>Submit</ButtonText>
</Button>

Mode/Variant Mapping:

Paper ModeGluestack VariantGluestack Action
containedsolidprimary
outlinedoutlineprimary
textlinkprimary
contained-tonalsolidsecondary
elevatedsolidprimary (+ shadow)

Wrapper Implementation:

export const Button = ({
mode = 'contained',
icon,
loading,
disabled,
children,
buttonColor,
textColor,
...props
}) => {
const variant = mode === 'contained' ? 'solid'
: mode === 'outlined' ? 'outline'
: 'link';

return (
<GlueButton
variant={variant}
isDisabled={disabled}
className={buttonColor ? `bg-[${buttonColor}]` : ''}
{...props}
>
{loading && <ButtonSpinner className="mr-2" />}
{icon && !loading && <ButtonIcon as={getIcon(icon)} className="mr-2" />}
<ButtonText className={textColor ? `text-[${textColor}]` : ''}>
{children}
</ButtonText>
</GlueButton>
);
};

IconButton

Aspectreact-native-papergluestack-ui v3
Importimport { IconButton } from 'react-native-paper'Compose from Button + ButtonIcon
MappingWrapper
Usage Count95 (highest)

Before:

<IconButton icon="menu" size={24} onPress={openMenu} />
<IconButton icon="close" iconColor="#FF0000" onPress={close} />
<IconButton icon="heart" mode="contained" onPress={like} />

After (with wrapper):

// Wrapper preserves Paper API
<IconButton icon="menu" size={24} onPress={openMenu} />

// Or native Gluestack
<Button variant="link" className="p-2" onPress={openMenu}>
<ButtonIcon as={MenuIcon} size={24} />
</Button>

Wrapper Implementation:

export const IconButton = ({
icon,
size = 24,
iconColor,
containerColor,
mode = 'default',
disabled,
...props
}) => {
const variant = mode === 'contained' ? 'solid'
: mode === 'outlined' ? 'outline'
: 'link';

return (
<GlueButton
variant={variant}
isDisabled={disabled}
className={`p-2 rounded-full ${containerColor ? `bg-[${containerColor}]` : ''}`}
{...props}
>
<ButtonIcon
as={getIcon(icon)}
size={size}
className={iconColor ? `text-[${iconColor}]` : ''}
/>
</GlueButton>
);
};

Form Inputs

TextInput

Aspectreact-native-papergluestack-ui v3
Importimport { TextInput } from 'react-native-paper'import { Input, InputField, FormControl } from '@gluestack-ui/themed'
MappingWrapper
Usage Count27

Before:

<TextInput
label="Email"
value={email}
onChangeText={setEmail}
mode="outlined"
error={!!errors.email}
left={<TextInput.Icon icon="email" />}
right={<TextInput.Icon icon="close" onPress={clear} />}
/>
<HelperText type="error" visible={!!errors.email}>
{errors.email}
</HelperText>

After (with wrapper):

// Wrapper preserves Paper API
<TextInput
label="Email"
value={email}
onChangeText={setEmail}
mode="outlined"
error={!!errors.email}
errorText={errors.email}
left={<TextInput.Icon icon="email" />}
/>

// Or native Gluestack
<FormControl isInvalid={!!errors.email}>
<FormControlLabel>
<FormControlLabelText>Email</FormControlLabelText>
</FormControlLabel>
<Input variant="outline">
<InputSlot className="pl-3">
<InputIcon as={MailIcon} />
</InputSlot>
<InputField
value={email}
onChangeText={setEmail}
placeholder="Enter email"
/>
<InputSlot className="pr-3" onPress={clear}>
<InputIcon as={XIcon} />
</InputSlot>
</Input>
<FormControlError>
<FormControlErrorText>{errors.email}</FormControlErrorText>
</FormControlError>
</FormControl>

Wrapper Implementation:

export const TextInput = ({
label,
value,
onChangeText,
mode = 'flat',
error,
errorText,
helperText,
left,
right,
secureTextEntry,
disabled,
multiline,
numberOfLines,
...props
}) => {
const variant = mode === 'outlined' ? 'outline' : 'underlined';

return (
<FormControl isInvalid={!!error} isDisabled={disabled}>
{label && (
<FormControlLabel>
<FormControlLabelText>{label}</FormControlLabelText>
</FormControlLabel>
)}
<Input variant={variant}>
{left && <InputSlot className="pl-3">{left}</InputSlot>}
<InputField
value={value}
onChangeText={onChangeText}
secureTextEntry={secureTextEntry}
multiline={multiline}
numberOfLines={numberOfLines}
{...props}
/>
{right && <InputSlot className="pr-3">{right}</InputSlot>}
</Input>
{(error && errorText) && (
<FormControlError>
<FormControlErrorText>{errorText}</FormControlErrorText>
</FormControlError>
)}
{(!error && helperText) && (
<FormControlHelper>
<FormControlHelperText>{helperText}</FormControlHelperText>
</FormControlHelper>
)}
</FormControl>
);
};

TextInput.Icon = ({ icon, onPress }) => (
<Pressable onPress={onPress}>
<InputIcon as={getIcon(icon)} />
</Pressable>
);

HelperText

Aspectreact-native-papergluestack-ui v3
Importimport { HelperText } from 'react-native-paper'Part of FormControl
MappingIntegrated into TextInput wrapper
Usage Count11

Handled within the TextInput wrapper above.

Switch

Aspectreact-native-papergluestack-ui v3
Importimport { Switch } from 'react-native-paper'import { Switch } from '@gluestack-ui/themed'
MappingDirect
Usage Count6

Before:

<Switch value={enabled} onValueChange={setEnabled} color="#6200ee" />

After:

<Switch value={enabled} onValueChange={setEnabled} className="data-[state=checked]:bg-primary-500" />
Aspectreact-native-papergluestack-ui v3
Importimport { Searchbar } from 'react-native-paper'Compose from Input
MappingCompose
Usage Count6

Before:

<Searchbar
placeholder="Search"
value={query}
onChangeText={setQuery}
onIconPress={handleSearch}
/>

After (wrapper):

export const Searchbar = ({ placeholder, value, onChangeText, onIconPress }) => (
<Input variant="rounded" className="bg-gray-100">
<InputSlot className="pl-3" onPress={onIconPress}>
<InputIcon as={SearchIcon} />
</InputSlot>
<InputField
placeholder={placeholder}
value={value}
onChangeText={onChangeText}
/>
{value && (
<InputSlot className="pr-3" onPress={() => onChangeText('')}>
<InputIcon as={XIcon} />
</InputSlot>
)}
</Input>
);

Layout

Surface

Aspectreact-native-papergluestack-ui v3
Importimport { Surface } from 'react-native-paper'import { Box } from '@gluestack-ui/themed'
MappingDirect
Usage Count20

Before:

<Surface style={styles.container} elevation={2}>
<Text>Content</Text>
</Surface>

After:

<Box className="bg-white rounded-lg shadow-md p-4">
<Text>Content</Text>
</Box>

Elevation Mapping:

Paper ElevationTailwind Shadow
0shadow-none
1shadow-sm
2shadow
3shadow-md
4shadow-lg
5shadow-xl

Divider

Aspectreact-native-papergluestack-ui v3
Importimport { Divider } from 'react-native-paper'import { Divider } from '@gluestack-ui/themed'
MappingDirect
Usage Count11

Before:

<Divider style={{ marginVertical: 8 }} />

After:

<Divider className="my-2" />

Overlays

Portal

Aspectreact-native-papergluestack-ui v3
Importimport { Portal } from 'react-native-paper'import { Portal } from '@digiwedge/gluestack-ui'
MappingWrapper (compat)
Usage Count6

Dialog

Aspectreact-native-papergluestack-ui v3
Importimport { Dialog } from 'react-native-paper'import { Dialog } from '@digiwedge/gluestack-ui'
MappingWrapper (AlertDialog)
Usage Count3

See migration-plan.md Phase 3 for full wrapper implementation.

Aspectreact-native-papergluestack-ui v3
Importimport { Menu } from 'react-native-paper'import { Menu } from '@digiwedge/gluestack-ui'
MappingWrapper (anchor + items)
Usage Count6

See migration-plan.md Phase 3 for full wrapper implementation.


Feedback

Toast

Aspectreact-native-papergluestack-ui v3
Importimport { showToast } from '@digiwedge/react-native-paper'import { useToast } from '@digiwedge/gluestack-ui'
MappingHook wrapper (Gluestack toast)

Before:

showToast({ type: 'success', text1: 'Saved', text2: 'Your changes are live.' });

After:

const { showToast } = useToast();
showToast({ type: 'success', text1: 'Saved', text2: 'Your changes are live.' });

ActivityIndicator

Aspectreact-native-papergluestack-ui v3
Importimport { ActivityIndicator } from 'react-native-paper'import { Spinner } from '@gluestack-ui/themed'
MappingDirect (rename)
Usage Count18

Before:

<ActivityIndicator animating={loading} size="large" color="#6200ee" />

After:

<Spinner size="large" className="text-primary-500" />

Wrapper (optional):

export const ActivityIndicator = ({ animating = true, size, color }) => {
if (!animating) return null;
return <Spinner size={size} className={color ? `text-[${color}]` : ''} />;
};

Media

Avatar

Aspectreact-native-papergluestack-ui v3
Importimport { Avatar } from 'react-native-paper'import { Avatar } from '@gluestack-ui/themed'
MappingWrapper
Usage Count7

Before:

<Avatar.Image size={48} source={{ uri: imageUrl }} />
<Avatar.Text size={48} label="JD" />
<Avatar.Icon size={48} icon="account" />

After (wrapper):

export const Avatar = {
Image: ({ size, source }) => (
<GlueAvatar size={sizeMap[size]}>
<AvatarImage source={source} />
<AvatarFallbackText>?</AvatarFallbackText>
</GlueAvatar>
),
Text: ({ size, label }) => (
<GlueAvatar size={sizeMap[size]}>
<AvatarFallbackText>{label}</AvatarFallbackText>
</GlueAvatar>
),
Icon: ({ size, icon }) => (
<GlueAvatar size={sizeMap[size]}>
<Icon as={getIcon(icon)} />
</GlueAvatar>
),
};

const sizeMap = { 24: 'xs', 32: 'sm', 48: 'md', 64: 'lg', 96: 'xl' };

Icon

Aspectreact-native-papergluestack-ui v3
Importimport { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons'import { Icon } from '@gluestack-ui/themed' with lucide
MappingCustom (icon library change)
Usage Count23

Icon Library Migration: Paper uses Material Community Icons; Gluestack recommends lucide-react-native.

// Create icon mapping utility
import * as LucideIcons from 'lucide-react-native';

const iconMap: Record<string, React.ComponentType> = {
'account': LucideIcons.User,
'menu': LucideIcons.Menu,
'close': LucideIcons.X,
'plus': LucideIcons.Plus,
'minus': LucideIcons.Minus,
'check': LucideIcons.Check,
'chevron-right': LucideIcons.ChevronRight,
'chevron-left': LucideIcons.ChevronLeft,
'arrow-left': LucideIcons.ArrowLeft,
'search': LucideIcons.Search,
'heart': LucideIcons.Heart,
'star': LucideIcons.Star,
'email': LucideIcons.Mail,
'phone': LucideIcons.Phone,
'calendar': LucideIcons.Calendar,
'clock': LucideIcons.Clock,
'map-marker': LucideIcons.MapPin,
'settings': LucideIcons.Settings,
'logout': LucideIcons.LogOut,
'edit': LucideIcons.Edit,
'delete': LucideIcons.Trash2,
'refresh': LucideIcons.RefreshCw,
'eye': LucideIcons.Eye,
'eye-off': LucideIcons.EyeOff,
// Add more mappings as needed
};

export const getIcon = (name: string) => iconMap[name] || LucideIcons.HelpCircle;

Theming

Provider

Aspectreact-native-papergluestack-ui v3
Importimport { PaperProvider } from 'react-native-paper'import { GluestackUIProvider } from '@gluestack-ui/themed'
MappingDirect

Before:

import { PaperProvider, MD3LightTheme, MD3DarkTheme } from 'react-native-paper';

const theme = {
...MD3LightTheme,
colors: {
...MD3LightTheme.colors,
primary: '#6200ee',
secondary: '#03dac6',
},
};

<PaperProvider theme={theme}>
<App />
</PaperProvider>

After:

import { GluestackUIProvider } from '@gluestack-ui/themed';
import { config } from './gluestack-ui.config';
import '../global.css';

<GluestackUIProvider config={config}>
<App />
</GluestackUIProvider>

useTheme Hook

Aspectreact-native-papergluestack-ui v3
Importimport { useTheme } from 'react-native-paper'Use Tailwind classes or CSS variables
MappingParadigm shift

Before:

const theme = useTheme();
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
borderColor: theme.colors.outline,
},
text: {
color: theme.colors.primary,
},
});

After:

// Option 1: Tailwind classes
<View className="bg-background border-outline">
<Text className="text-primary-500">Hello</Text>
</View>

// Option 2: CSS variables (for dynamic theming)
<View style={{ backgroundColor: 'var(--color-background)' }}>
<Text style={{ color: 'var(--color-primary-500)' }}>Hello</Text>
</View>

Components NOT in Gluestack (Need Custom Implementation)

Paper ComponentAlternative
AppbarBuild from Box + HStack + IconButton
BannerBuild from Box + Alert styling
CardUse Gluestack Card component
ChipCustom wrapper in @digiwedge/gluestack-ui (components/forms/Chip.tsx)
DataTableUse Gluestack Table or third-party
FABUse Gluestack Fab component
ListBuild from VStack + custom items
ProgressBarUse Gluestack Progress
SegmentedButtonsCustom wrapper in @digiwedge/gluestack-ui (components/forms/SegmentedButtons.tsx)
SnackbarUse Toast instead
ToggleButtonBuild from Button + state

Migration Utility Functions

// libs/ui/gluestack-ui/src/utils/migration.ts

/**
* Map Paper icon names to Lucide icons
*/
export { getIcon } from './icons';

/**
* Map Paper elevation to Tailwind shadow
*/
export const getElevationClass = (elevation: number): string => {
const map: Record<number, string> = {
0: 'shadow-none',
1: 'shadow-sm',
2: 'shadow',
3: 'shadow-md',
4: 'shadow-lg',
5: 'shadow-xl',
};
return map[elevation] || 'shadow';
};

/**
* Map Paper text variants to Tailwind classes
*/
export const getTextVariantClass = (variant: string): string => {
const map: Record<string, string> = {
displayLarge: 'text-6xl font-normal',
displayMedium: 'text-5xl font-normal',
displaySmall: 'text-4xl font-normal',
headlineLarge: 'text-3xl font-normal',
headlineMedium: 'text-2xl font-normal',
headlineSmall: 'text-xl font-normal',
titleLarge: 'text-xl font-medium',
titleMedium: 'text-base font-medium',
titleSmall: 'text-sm font-medium',
bodyLarge: 'text-base',
bodyMedium: 'text-sm',
bodySmall: 'text-xs',
labelLarge: 'text-sm font-medium',
labelMedium: 'text-xs font-medium',
labelSmall: 'text-[10px] font-medium',
};
return map[variant] || 'text-base';
};

/**
* Map Paper button mode to Gluestack variant
*/
export const getButtonVariant = (mode: string): 'solid' | 'outline' | 'link' => {
const map: Record<string, 'solid' | 'outline' | 'link'> = {
contained: 'solid',
outlined: 'outline',
text: 'link',
'contained-tonal': 'solid',
elevated: 'solid',
};
return map[mode] || 'solid';
};