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
| Symbol | Meaning |
|---|---|
| Direct | 1:1 mapping, minimal changes |
| Wrapper | Needs wrapper to preserve Paper API |
| Compose | Build from multiple Gluestack components |
| Custom | Requires custom implementation |
Typography
Text
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Text } from 'react-native-paper' | import { Text } from '@gluestack-ui/themed' |
| Mapping | Direct | |
| Usage Count | 32 |
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 Variant | Tailwind Classes |
|---|---|
| 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 |
Buttons
Button
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Button } from 'react-native-paper' | import { Button, ButtonText, ButtonIcon } from '@gluestack-ui/themed' |
| Mapping | Wrapper | |
| Usage Count | 24 |
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 Mode | Gluestack Variant | Gluestack Action |
|---|---|---|
| contained | solid | primary |
| outlined | outline | primary |
| text | link | primary |
| contained-tonal | solid | secondary |
| elevated | solid | primary (+ 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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { IconButton } from 'react-native-paper' | Compose from Button + ButtonIcon |
| Mapping | Wrapper | |
| Usage Count | 95 (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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { TextInput } from 'react-native-paper' | import { Input, InputField, FormControl } from '@gluestack-ui/themed' |
| Mapping | Wrapper | |
| Usage Count | 27 |
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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { HelperText } from 'react-native-paper' | Part of FormControl |
| Mapping | Integrated into TextInput wrapper | |
| Usage Count | 11 |
Handled within the TextInput wrapper above.
Switch
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Switch } from 'react-native-paper' | import { Switch } from '@gluestack-ui/themed' |
| Mapping | Direct | |
| Usage Count | 6 |
Before:
<Switch value={enabled} onValueChange={setEnabled} color="#6200ee" />
After:
<Switch value={enabled} onValueChange={setEnabled} className="data-[state=checked]:bg-primary-500" />
Searchbar
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Searchbar } from 'react-native-paper' | Compose from Input |
| Mapping | Compose | |
| Usage Count | 6 |
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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Surface } from 'react-native-paper' | import { Box } from '@gluestack-ui/themed' |
| Mapping | Direct | |
| Usage Count | 20 |
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 Elevation | Tailwind Shadow |
|---|---|
| 0 | shadow-none |
| 1 | shadow-sm |
| 2 | shadow |
| 3 | shadow-md |
| 4 | shadow-lg |
| 5 | shadow-xl |
Divider
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Divider } from 'react-native-paper' | import { Divider } from '@gluestack-ui/themed' |
| Mapping | Direct | |
| Usage Count | 11 |
Before:
<Divider style={{ marginVertical: 8 }} />
After:
<Divider className="my-2" />
Overlays
Portal
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Portal } from 'react-native-paper' | import { Portal } from '@digiwedge/gluestack-ui' |
| Mapping | Wrapper (compat) | |
| Usage Count | 6 |
Dialog
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Dialog } from 'react-native-paper' | import { Dialog } from '@digiwedge/gluestack-ui' |
| Mapping | Wrapper (AlertDialog) | |
| Usage Count | 3 |
See migration-plan.md Phase 3 for full wrapper implementation.
Menu
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Menu } from 'react-native-paper' | import { Menu } from '@digiwedge/gluestack-ui' |
| Mapping | Wrapper (anchor + items) | |
| Usage Count | 6 |
See migration-plan.md Phase 3 for full wrapper implementation.
Feedback
Toast
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { showToast } from '@digiwedge/react-native-paper' | import { useToast } from '@digiwedge/gluestack-ui' |
| Mapping | Hook 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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { ActivityIndicator } from 'react-native-paper' | import { Spinner } from '@gluestack-ui/themed' |
| Mapping | Direct (rename) | |
| Usage Count | 18 |
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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { Avatar } from 'react-native-paper' | import { Avatar } from '@gluestack-ui/themed' |
| Mapping | Wrapper | |
| Usage Count | 7 |
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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons' | import { Icon } from '@gluestack-ui/themed' with lucide |
| Mapping | Custom (icon library change) | |
| Usage Count | 23 |
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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { PaperProvider } from 'react-native-paper' | import { GluestackUIProvider } from '@gluestack-ui/themed' |
| Mapping | Direct |
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
| Aspect | react-native-paper | gluestack-ui v3 |
|---|---|---|
| Import | import { useTheme } from 'react-native-paper' | Use Tailwind classes or CSS variables |
| Mapping | Paradigm 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 Component | Alternative |
|---|---|
Appbar | Build from Box + HStack + IconButton |
Banner | Build from Box + Alert styling |
Card | Use Gluestack Card component |
Chip | Custom wrapper in @digiwedge/gluestack-ui (components/forms/Chip.tsx) |
DataTable | Use Gluestack Table or third-party |
FAB | Use Gluestack Fab component |
List | Build from VStack + custom items |
ProgressBar | Use Gluestack Progress |
SegmentedButtons | Custom wrapper in @digiwedge/gluestack-ui (components/forms/SegmentedButtons.tsx) |
Snackbar | Use Toast instead |
ToggleButton | Build 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';
};