Custom Components for React

This guide explains how to configure custom React components in Locofy using the locofy.config.json file. This allows you to map your existing React components to Figma designs, enabling UIPro to generate code using your actual component library.

Configuration File: locofy.config.json

The locofy.config.json file is the central configuration that defines how your React components map to Figma components. It's automatically generated by UIPro when you run: "Create UIPro config for all components"

File Location

The file should be placed in your project root directory:

your-project/
├── src/
│   └── components/
├── locofy.config.json  ← Configuration file
└── package.json

React Component Examples

Example 1: Button Component

Component Code:

import React from 'react';
 
interface ButtonProps {
  label: string;
  size?: 'small' | 'medium' | 'large';
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  onClick?: () => void;
}
 
const Button: React.FC<ButtonProps> = ({
  label,
  size = 'medium',
  variant = 'primary',
  disabled = false,
  onClick
}) => {
  return (
    <button
      className={`btn btn-${size} btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
};
 
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 App"
}

Example 2: Card Component

Component Code:

import React, { ReactNode } from 'react';
 
interface CardProps {
  header?: ReactNode;
  children: ReactNode;
  footer?: ReactNode;
  className?: string;
}
 
const Card: React.FC<CardProps> = ({ header, children, footer, className }) => {
  return (
    <div className={`card ${className || ''}`}>
      {header && <div className="card-header">{header}</div>}
      <div className="card-content">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
};
 
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"
          }
        },
        {
          "name": "className",
          "dataType": "string",
          "propType": 1,
          "isOptional": true
        }
      ],
      "config": {
        "layerName": "Card"
      }
    }
  ]
}

Example 3: Input Component with Value Mapping

Component Code:

import React from 'react';
 
interface InputProps {
  type?: 'text' | 'email' | 'password' | 'number';
  placeholder?: string;
  value?: string;
  disabled?: boolean;
  size?: 'sm' | 'md' | 'lg';
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
 
const Input: React.FC<InputProps> = ({
  type = 'text',
  placeholder,
  value,
  disabled = false,
  size = 'md',
  onChange
}) => {
  return (
    <input
      type={type}
      placeholder={placeholder}
      value={value}
      disabled={disabled}
      className={`input input-${size}`}
      onChange={onChange}
    />
  );
};
 
export default Input;

locofy.config.json Configuration:

{
  "components": [
    {
      "path": "./src/components/Input.tsx",
      "name": "Input",
      "props": [
        {
          "name": "type",
          "dataType": "enum",
          "propType": 1,
          "isOptional": true,
          "expectValues": ["text", "email", "password", "number"]
        },
        {
          "name": "placeholder",
          "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"]
            }
          }
        },
        {
          "name": "disabled",
          "dataType": "boolean",
          "propType": 4,
          "isOptional": true
        }
      ],
      "config": {
        "layerName": "Input"
      }
    }
  ]
}

Example 4: Avatar Component

Component Code:

import React from 'react';
 
interface AvatarProps {
  src: string;
  alt?: string;
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  shape?: 'circle' | 'square';
}
 
const Avatar: React.FC<AvatarProps> = ({
  src,
  alt = 'Avatar',
  size = 'md',
  shape = 'circle'
}) => {
  return (
    <img
      src={src}
      alt={alt}
      className={`avatar avatar-${size} avatar-${shape}`}
    />
  );
};
 
export default Avatar;

locofy.config.json Configuration:

{
  "components": [
    {
      "path": "./src/components/Avatar.tsx",
      "name": "Avatar",
      "props": [
        {
          "name": "src",
          "dataType": "string",
          "propType": 1,
          "isOptional": false,
          "attr": "src"
        },
        {
          "name": "alt",
          "dataType": "string",
          "propType": 1,
          "isOptional": true,
          "attr": "alt"
        },
        {
          "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"
          }
        }
      ],
      "config": {
        "layerName": "Avatar"
      }
    }
  ]
}

Example 5: Icon Button Component

Component Code:

import React, { ReactNode } from 'react';
 
interface IconButtonProps {
  icon: ReactNode;
  label: string;
  size?: 'small' | 'medium' | 'large';
  onClick?: () => void;
}
 
const IconButton: React.FC<IconButtonProps> = ({
  icon,
  label,
  size = 'medium',
  onClick
}) => {
  return (
    <button className={`icon-btn icon-btn-${size}`} onClick={onClick}>
      <span className="icon-btn-icon">{icon}</span>
      <span className="icon-btn-label">{label}</span>
    </button>
  );
};
 
export default IconButton;

locofy.config.json Configuration:

{
  "components": [
    {
      "path": "./src/components/IconButton.tsx",
      "name": "IconButton",
      "props": [
        {
          "name": "icon",
          "dataType": "node",
          "propType": 5,
          "isOptional": false,
          "config": {
            "layerName": "[icon]"
          }
        },
        {
          "name": "label",
          "dataType": "string",
          "propType": 1,
          "isOptional": false,
          "config": {
            "layerName": "[label]"
          }
        },
        {
          "name": "size",
          "dataType": "enum",
          "propType": 1,
          "isOptional": true,
          "expectValues": ["small", "medium", "large"]
        }
      ],
      "config": {
        "layerName": "IconButton"
      }
    }
  ]
}

Complete locofy.config.json Example

Here's a complete configuration file with multiple components:

{
  "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"
          }
        }
      ],
      "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"
          }
        },
        {
          "name": "footer",
          "dataType": "node",
          "propType": 5,
          "isOptional": true,
          "config": {
            "layerName": "Footer"
          }
        }
      ],
      "config": {
        "layerName": "Card"
      }
    },
    {
      "path": "./src/components/Input.tsx",
      "name": "Input",
      "props": [
        {
          "name": "placeholder",
          "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": "Input"
      }
    }
  ],
  "projectId": "abc123xyz",
  "projectName": "My React App"
}

Configuration Properties Reference

Component-Level Properties

PropertyTypeDescription
pathstringRelative path to component file from project root
namestringComponent function/class name
propsarrayArray of prop definitions
configobjectComponent-level configuration
targetTagsarrayReplace native HTML tags with this component
acceptAllStylesbooleanAccept all generated styles
wrapperComponentobjectDefine wrapper component
subComponentsarrayConfigure child components
siblingComponentsarrayAdd sibling components

Prop Properties

PropertyTypeDescription
namestringProp name
dataTypestringData type: string, number, boolean, enum, node, object
propTypenumber1 for most types, 4 for boolean, 5 for node
isOptionalbooleanWhether prop is optional
expectValuesarrayAllowed values for enum types
attrstringHTML attribute to bind to (e.g., src, alt)
configobjectProp-level mapping configuration

Config Object Properties

PropertyTypeDescription
layerNamestringFigma layer name to map from
layerPropstringFigma component property name
valueMappingobjectMap code values to Figma values
layerarrayAdvanced layer configuration
nodeFieldstringExtract from node field (e.g., name)

Advanced Features

1. Target Tags

Replace native HTML elements with your custom component:

{
  "components": [
    {
      "path": "./src/components/Box.tsx",
      "name": "Box",
      "targetTags": ["div", "section"],
      "props": []
    }
  ]
}

2. Wrapper Component

Define a parent wrapper that should contain your component:

{
  "components": [
    {
      "path": "./src/components/Input.tsx",
      "name": "Input",
      "wrapperComponent": {
        "component": "FormField",
        "props": [
          {
            "name": "label",
            "config": {
              "layerName": "Label"
            }
          }
        ]
      },
      "props": []
    }
  ]
}

3. Sub Components

Configure child components within a parent:

{
  "components": [
    {
      "path": "./src/components/FormField.tsx",
      "name": "FormField",
      "subComponents": [
        {
          "component": "Input",
          "props": [
            {
              "name": "maxLength",
              "config": {
                "layerProp": "Character Limit"
              }
            }
          ]
        }
      ],
      "props": []
    }
  ]
}

4. Sibling Components

Add components that render alongside your main component:

{
  "components": [
    {
      "path": "./src/components/Button.tsx",
      "name": "Button",
      "siblingComponents": [
        {
          "component": "Icon",
          "position": "before",
          "config": {
            "layerProp": "Icon"
          },
          "props": [
            {
              "name": "name",
              "config": {
                "layerProp": "Icon"
              }
            }
          ]
        }
      ],
      "props": []
    }
  ]
}

5. Component Variant Matching

Map different components based on Figma property values:

{
  "components": [
    {
      "path": "./src/components/PrimaryButton.tsx",
      "name": "PrimaryButton",
      "config": {
        "layerName": "Button",
        "layerProps": {
          "variant": "primary"
        }
      },
      "props": []
    },
    {
      "path": "./src/components/SecondaryButton.tsx",
      "name": "SecondaryButton",
      "config": {
        "layerName": "Button",
        "layerProps": {
          "variant": "secondary"
        }
      },
      "props": []
    }
  ]
}

Workflow

  1. Create your React components with proper TypeScript interfaces
  2. Run UIPro configuration in your IDE: "Create UIPro config for all components"
  3. UIPro generates locofy.config.json automatically
  4. Review and adjust the configuration as needed
  5. Open Figma and use the UIPro plugin
  6. Map components in the UIPro interface
  7. Generate code and UIPro will use your custom components

Best Practices

  1. ✅ Use TypeScript for better type inference
  2. ✅ Keep component and prop names consistent between Figma and code
  3. ✅ Use valueMapping when Figma property names differ from code
  4. ✅ Set isOptional: false for required props
  5. ✅ Use attr for HTML attributes like src, alt, href
  6. ✅ Use descriptive layerName values in config
  7. ✅ Organize components in a clear folder structure
  8. ✅ Document complex mappings with comments

Troubleshooting

Component not detected

  • Verify the file path in locofy.config.json is correct
  • Ensure the component name matches the export
  • Check that the component is properly exported

Props not mapping correctly

  • Check config.layerProp matches Figma property name exactly
  • Verify valueMapping if using different value names
  • Ensure dataType matches the actual prop type
  • Check propType is set correctly (1, 4, or 5)

Config file not being used

  • Ensure locofy.config.json is in project root
  • Restart your IDE after creating the config
  • Verify JSON syntax is valid

Value mapping issues

  • Check that all mapped values exist in Figma
  • Verify the mapping direction (code → Figma)
  • Ensure enum expectValues includes all mapped values

Next Steps