Custom Components for React Native
This guide explains how to configure custom React Native components in Locofy using the locofy.config.json file. This allows you to map your existing React Native components to Figma designs for mobile app development.
Configuration File: locofy.config.json
The locofy.config.json file defines how your React Native components map to Figma components. It's automatically generated by UIPro when you run: "Create UIPro config for all components"
File Location
your-project/
├── src/
│ └── components/
├── locofy.config.json ← Configuration file
└── package.jsonReact Native Component Examples
Example 1: Button Component
Component Code:
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
interface ButtonProps {
label: string;
size?: 'small' | 'medium' | 'large';
variant?: 'primary' | 'secondary';
disabled?: boolean;
onPress?: () => void;
}
const Button: React.FC<ButtonProps> = ({
label,
size = 'medium',
variant = 'primary',
disabled = false,
onPress
}) => {
return (
<TouchableOpacity
style={[
styles.button,
styles[`button_${size}`],
styles[`button_${variant}`],
disabled && styles.button_disabled
]}
onPress={onPress}
disabled={disabled}
activeOpacity={0.7}
>
<Text style={styles.text}>{label}</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
button_small: {
paddingHorizontal: 12,
paddingVertical: 6,
},
button_medium: {
paddingHorizontal: 16,
paddingVertical: 10,
},
button_large: {
paddingHorizontal: 24,
paddingVertical: 14,
},
button_primary: {
backgroundColor: '#007AFF',
},
button_secondary: {
backgroundColor: '#5856D6',
},
button_disabled: {
opacity: 0.5,
},
text: {
fontSize: 16,
fontWeight: '600',
color: '#FFFFFF',
},
});
export default Button;locofy.config.json Configuration:
{
"components": [
{
"path": "./src/components/Button.tsx",
"name": "Button",
"props": [
{
"name": "label",
"dataType": "string",
"propType": 1,
"isOptional": false,
"config": {
"layerName": "[children]"
}
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["small", "medium", "large"],
"config": {
"layerProp": "Size"
}
},
{
"name": "variant",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["primary", "secondary"],
"config": {
"layerProp": "Variant"
}
},
{
"name": "disabled",
"dataType": "boolean",
"propType": 4,
"isOptional": true
}
],
"config": {
"layerName": "Button"
}
}
],
"projectId": "your-project-id",
"projectName": "Your React Native App"
}Example 2: Card Component
Component Code:
import React, { ReactNode } from 'react';
import { View, StyleSheet } from 'react-native';
interface CardProps {
header?: ReactNode;
children: ReactNode;
footer?: ReactNode;
}
const Card: React.FC<CardProps> = ({ header, children, footer }) => {
return (
<View style={styles.card}>
{header && <View style={styles.header}>{header}</View>}
<View style={styles.content}>{children}</View>
{footer && <View style={styles.footer}>{footer}</View>}
</View>
);
};
const styles = StyleSheet.create({
card: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 3,
},
header: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#E5E5EA',
},
content: {
padding: 16,
},
footer: {
padding: 16,
borderTopWidth: 1,
borderTopColor: '#E5E5EA',
},
});
export default Card;locofy.config.json Configuration:
{
"components": [
{
"path": "./src/components/Card.tsx",
"name": "Card",
"props": [
{
"name": "header",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "Header"
}
},
{
"name": "children",
"dataType": "node",
"propType": 5,
"isOptional": false,
"config": {
"layerName": "Content"
}
},
{
"name": "footer",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "Footer"
}
}
],
"config": {
"layerName": "Card"
}
}
]
}Example 3: TextInput Component
Component Code:
import React from 'react';
import { TextInput as RNTextInput, View, Text, StyleSheet } from 'react-native';
interface TextInputProps {
placeholder?: string;
label?: string;
error?: string;
secureTextEntry?: boolean;
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad';
size?: 'sm' | 'md' | 'lg';
}
const TextInput: React.FC<TextInputProps> = ({
placeholder,
label,
error,
secureTextEntry = false,
keyboardType = 'default',
size = 'md'
}) => {
return (
<View style={styles.container}>
{label && <Text style={styles.label}>{label}</Text>}
<RNTextInput
style={[styles.input, styles[`input_${size}`], error && styles.input_error]}
placeholder={placeholder}
secureTextEntry={secureTextEntry}
keyboardType={keyboardType}
/>
{error && <Text style={styles.errorText}>{error}</Text>}
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#1C1C1E',
marginBottom: 8,
},
input: {
backgroundColor: '#F2F2F7',
borderRadius: 8,
borderWidth: 1,
borderColor: 'transparent',
color: '#1C1C1E',
},
input_sm: {
paddingHorizontal: 12,
paddingVertical: 8,
fontSize: 14,
},
input_md: {
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 16,
},
input_lg: {
paddingHorizontal: 20,
paddingVertical: 16,
fontSize: 18,
},
input_error: {
borderColor: '#FF3B30',
},
errorText: {
fontSize: 12,
color: '#FF3B30',
marginTop: 4,
},
});
export default TextInput;locofy.config.json Configuration:
{
"components": [
{
"path": "./src/components/TextInput.tsx",
"name": "TextInput",
"props": [
{
"name": "placeholder",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "label",
"dataType": "string",
"propType": 1,
"isOptional": true,
"config": {
"layerName": "Label"
}
},
{
"name": "error",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "secureTextEntry",
"dataType": "boolean",
"propType": 4,
"isOptional": true
},
{
"name": "keyboardType",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["default", "email-address", "numeric", "phone-pad"]
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["sm", "md", "lg"],
"config": {
"layerProp": "Size",
"valueMapping": {
"sm": ["small"],
"md": ["medium"],
"lg": ["large"]
}
}
}
],
"config": {
"layerName": "TextInput"
}
}
]
}Example 4: Avatar Component
Component Code:
import React from 'react';
import { Image, View, StyleSheet } from 'react-native';
interface AvatarProps {
source: string | { uri: string };
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
shape?: 'circle' | 'square';
showBadge?: boolean;
}
const Avatar: React.FC<AvatarProps> = ({
source,
size = 'md',
shape = 'circle',
showBadge = false
}) => {
const sizeValue = sizeMap[size];
const imageSource = typeof source === 'string' ? { uri: source } : source;
return (
<View style={styles.container}>
<Image
source={imageSource}
style={[
styles.avatar,
{ width: sizeValue, height: sizeValue },
shape === 'circle' && { borderRadius: sizeValue / 2 },
shape === 'square' && { borderRadius: 8 }
]}
/>
{showBadge && <View style={[styles.badge, { right: sizeValue * 0.1 }]} />}
</View>
);
};
const sizeMap = {
xs: 24,
sm: 32,
md: 48,
lg: 64,
xl: 96,
};
const styles = StyleSheet.create({
container: {
position: 'relative',
},
avatar: {
backgroundColor: '#E5E5EA',
},
badge: {
position: 'absolute',
bottom: 0,
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: '#34C759',
borderWidth: 2,
borderColor: '#FFFFFF',
},
});
export default Avatar;locofy.config.json Configuration:
{
"components": [
{
"path": "./src/components/Avatar.tsx",
"name": "Avatar",
"props": [
{
"name": "source",
"dataType": "string",
"propType": 1,
"isOptional": false,
"attr": "src"
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["xs", "sm", "md", "lg", "xl"],
"config": {
"layerProp": "Size"
}
},
{
"name": "shape",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["circle", "square"],
"config": {
"layerProp": "Shape"
}
},
{
"name": "showBadge",
"dataType": "boolean",
"propType": 4,
"isOptional": true
}
],
"config": {
"layerName": "Avatar"
}
}
]
}Example 5: List Item Component
Component Code:
import React, { ReactNode } from 'react';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';
interface ListItemProps {
leftIcon?: ReactNode;
title: string;
subtitle?: string;
rightIcon?: ReactNode;
onPress?: () => void;
}
const ListItem: React.FC<ListItemProps> = ({
leftIcon,
title,
subtitle,
rightIcon,
onPress
}) => {
return (
<TouchableOpacity
style={styles.container}
onPress={onPress}
disabled={!onPress}
activeOpacity={0.7}
>
{leftIcon && <View style={styles.leftIcon}>{leftIcon}</View>}
<View style={styles.content}>
<Text style={styles.title}>{title}</Text>
{subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
</View>
{rightIcon && <View style={styles.rightIcon}>{rightIcon}</View>}
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: '#E5E5EA',
},
leftIcon: {
marginRight: 12,
},
content: {
flex: 1,
},
title: {
fontSize: 16,
fontWeight: '600',
color: '#1C1C1E',
},
subtitle: {
fontSize: 14,
color: '#8E8E93',
marginTop: 4,
},
rightIcon: {
marginLeft: 12,
},
});
export default ListItem;locofy.config.json Configuration:
{
"components": [
{
"path": "./src/components/ListItem.tsx",
"name": "ListItem",
"props": [
{
"name": "leftIcon",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "LeftIcon"
}
},
{
"name": "title",
"dataType": "string",
"propType": 1,
"isOptional": false,
"config": {
"layerName": "Title"
}
},
{
"name": "subtitle",
"dataType": "string",
"propType": 1,
"isOptional": true,
"config": {
"layerName": "Subtitle"
}
},
{
"name": "rightIcon",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "RightIcon"
}
}
],
"config": {
"layerName": "ListItem"
}
}
]
}Complete locofy.config.json Example
{
"components": [
{
"path": "./src/components/Button.tsx",
"name": "Button",
"props": [
{
"name": "label",
"dataType": "string",
"propType": 1,
"isOptional": false
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["small", "medium", "large"],
"config": {
"layerProp": "Size"
}
},
{
"name": "variant",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["primary", "secondary"],
"config": {
"layerProp": "Variant"
}
}
],
"config": {
"layerName": "Button"
}
},
{
"path": "./src/components/Card.tsx",
"name": "Card",
"props": [
{
"name": "header",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "Header"
}
},
{
"name": "children",
"dataType": "node",
"propType": 5,
"isOptional": false,
"config": {
"layerName": "Content"
}
}
],
"config": {
"layerName": "Card"
}
},
{
"path": "./src/components/TextInput.tsx",
"name": "TextInput",
"props": [
{
"name": "placeholder",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "label",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["sm", "md", "lg"],
"config": {
"layerProp": "Size",
"valueMapping": {
"sm": ["small"],
"md": ["medium"],
"lg": ["large"]
}
}
}
],
"config": {
"layerName": "TextInput"
}
}
],
"projectId": "abc123xyz",
"projectName": "My React Native App"
}React Native-Specific Features
Target Tags for Native Components
Replace React Native primitives with custom components:
{
"components": [
{
"path": "./src/components/Container.tsx",
"name": "Container",
"targetTags": ["View"],
"props": []
},
{
"path": "./src/components/Label.tsx",
"name": "Label",
"targetTags": ["Text"],
"props": []
}
]
}Image Source Handling
React Native uses different image source formats:
{
"props": [
{
"name": "source",
"dataType": "string",
"propType": 1,
"isOptional": false,
"attr": "src"
}
]
}Configuration Properties Reference
| Property | Type | Description |
|---|---|---|
path | string | Relative path to component file |
name | string | Component function name |
props | array | Array of prop definitions |
config | object | Component-level configuration |
targetTags | array | Replace native components (View, Text, etc.) |
Workflow
- Create React Native components with TypeScript
- Run UIPro configuration:
"Create UIPro config for all components" - UIPro generates
locofy.config.json - Review configuration
- Open Figma and map components
- Generate mobile code
Best Practices
- ✅ Use TypeScript for type safety
- ✅ Use StyleSheet.create() for performance
- ✅ Use proper React Native components (TouchableOpacity, View, Text)
- ✅ Handle platform differences appropriately
- ✅ Use
valueMappingwhen needed - ✅ Consider accessibility (accessibilityLabel, accessibilityRole)
Next Steps
- Check the main Custom Components guide
- Explore other framework guides: React, Angular, Vue
- Join our Discord community (opens in a new tab)