React Native

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.json

React 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

PropertyTypeDescription
pathstringRelative path to component file
namestringComponent function name
propsarrayArray of prop definitions
configobjectComponent-level configuration
targetTagsarrayReplace native components (View, Text, etc.)

Workflow

  1. Create React Native components with TypeScript
  2. Run UIPro configuration: "Create UIPro config for all components"
  3. UIPro generates locofy.config.json
  4. Review configuration
  5. Open Figma and map components
  6. Generate mobile code

Best Practices

  1. ✅ Use TypeScript for type safety
  2. ✅ Use StyleSheet.create() for performance
  3. ✅ Use proper React Native components (TouchableOpacity, View, Text)
  4. ✅ Handle platform differences appropriately
  5. ✅ Use valueMapping when needed
  6. ✅ Consider accessibility (accessibilityLabel, accessibilityRole)

Next Steps