Custom Components for Angular
This guide explains how to configure custom Angular components in Locofy using the locofy.config.json file. This allows you to map your existing Angular 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 Angular 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/
│ └── app/
│ └── components/
├── locofy.config.json ← Configuration file
└── package.jsonAngular-Specific Configuration
Angular projects support additional component-level configurations:
1. Custom Tag
Specify a custom HTML tag instead of the component's default selector:
{
"config": {
"tag": "custom-button"
}
}2. Directives
Apply Angular directives to the component:
{
"config": {
"directives": [
{ "selector": "matInput" },
{ "selector": "matFormField" }
]
}
}3. Default Attributes
Define default HTML attributes:
{
"config": {
"defaultAttributes": {
"type": "text",
"placeholder": "Enter text",
"class": "form-control"
}
}
}Angular Component Examples
Example 1: Button Component
Component Code:
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-button',
template: `
<button
[class]="'btn btn-' + size + ' btn-' + variant"
[disabled]="disabled"
(click)="handleClick()">
{{ label }}
</button>
`,
styleUrls: ['./button.component.scss']
})
export class ButtonComponent {
@Input() label: string = 'Button';
@Input() size: 'small' | 'medium' | 'large' = 'medium';
@Input() variant: 'primary' | 'secondary' = 'primary';
@Input() disabled: boolean = false;
@Output() onClick = new EventEmitter<void>();
handleClick(): void {
if (!this.disabled) {
this.onClick.emit();
}
}
}locofy.config.json Configuration:
{
"components": [
{
"path": "./src/app/components/button/button.component.ts",
"name": "ButtonComponent",
"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",
"tag": "app-button"
}
}
],
"projectId": "your-project-id",
"projectName": "Your Angular App"
}Example 2: Card Component
Component Code:
import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-card',
template: `
<div [class]="'card ' + (className || '')">
<div class="card-header" *ngIf="header">
<ng-container *ngTemplateOutlet="header"></ng-container>
</div>
<div class="card-content">
<ng-content></ng-content>
</div>
<div class="card-footer" *ngIf="footer">
<ng-container *ngTemplateOutlet="footer"></ng-container>
</div>
</div>
`,
styleUrls: ['./card.component.scss']
})
export class CardComponent {
@Input() header?: TemplateRef<any>;
@Input() footer?: TemplateRef<any>;
@Input() className?: string;
}locofy.config.json Configuration:
{
"components": [
{
"path": "./src/app/components/card/card.component.ts",
"name": "CardComponent",
"props": [
{
"name": "header",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "Header"
}
},
{
"name": "footer",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "Footer"
}
},
{
"name": "className",
"dataType": "string",
"propType": 1,
"isOptional": true
}
],
"config": {
"layerName": "Card",
"tag": "app-card"
}
}
]
}Example 3: Input Component with Material UI
Component Code:
import { Component, Input } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
@Component({
selector: 'app-input',
template: `
<input
[type]="type"
[placeholder]="placeholder"
[disabled]="disabled"
[formControl]="control"
[class]="'input input-' + size"
/>
`,
styleUrls: ['./input.component.scss']
})
export class InputComponent {
@Input() type: 'text' | 'email' | 'password' | 'number' = 'text';
@Input() placeholder: string = '';
@Input() disabled: boolean = false;
@Input() size: 'sm' | 'md' | 'lg' = 'md';
@Input() control: UntypedFormControl = new UntypedFormControl(null);
}locofy.config.json Configuration:
{
"components": [
{
"path": "./src/app/components/input/input.component.ts",
"name": "InputComponent",
"props": [
{
"name": "type",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["text", "email", "password", "number"]
},
{
"name": "placeholder",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "disabled",
"dataType": "boolean",
"propType": 4,
"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",
"tag": "app-input",
"directives": [
{ "selector": "matInput" }
],
"defaultAttributes": {
"type": "text",
"class": "form-control"
}
}
}
]
}Example 4: Icon Component
Component Code:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-icon',
template: `
<i [class]="'icon icon-' + name + ' icon-' + size" [style.color]="color"></i>
`,
styleUrls: ['./icon.component.scss']
})
export class IconComponent {
@Input() name: string = '';
@Input() size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = 'md';
@Input() color?: string;
}locofy.config.json Configuration:
{
"components": [
{
"path": "./src/app/components/icon/icon.component.ts",
"name": "IconComponent",
"props": [
{
"name": "name",
"dataType": "string",
"propType": 1,
"isOptional": false,
"config": {
"nodeField": "name"
}
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["xs", "sm", "md", "lg", "xl"],
"config": {
"layerProp": "Size"
}
},
{
"name": "color",
"dataType": "string",
"propType": 1,
"isOptional": true
}
],
"config": {
"layerName": "Icon",
"tag": "app-icon"
}
}
]
}Example 5: Form Field with Directives
Component Code:
import { Component, Input } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
@Component({
selector: 'app-form-field',
template: `
<mat-form-field [appearance]="appearance">
<mat-label>{{ label }}</mat-label>
<input matInput
[type]="type"
[placeholder]="placeholder"
[formControl]="control">
<mat-hint *ngIf="hint">{{ hint }}</mat-hint>
<mat-error *ngIf="control.invalid && control.touched">
{{ errorMessage }}
</mat-error>
</mat-form-field>
`
})
export class FormFieldComponent {
@Input() label: string = '';
@Input() type: string = 'text';
@Input() placeholder: string = '';
@Input() hint?: string;
@Input() errorMessage: string = 'Invalid input';
@Input() appearance: 'fill' | 'outline' = 'outline';
@Input() control: UntypedFormControl = new UntypedFormControl(null);
}locofy.config.json Configuration:
{
"components": [
{
"path": "./src/app/components/form-field/form-field.component.ts",
"name": "FormFieldComponent",
"props": [
{
"name": "label",
"dataType": "string",
"propType": 1,
"isOptional": false
},
{
"name": "type",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["text", "email", "password", "number"]
},
{
"name": "placeholder",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "hint",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "errorMessage",
"dataType": "string",
"propType": 1,
"isOptional": true
},
{
"name": "appearance",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["fill", "outline"]
}
],
"config": {
"layerName": "FormField",
"tag": "app-form-field",
"directives": [
{ "selector": "matFormField" }
]
}
}
]
}Complete locofy.config.json Example
Here's a complete configuration file with multiple Angular components:
{
"components": [
{
"path": "./src/app/components/button/button.component.ts",
"name": "ButtonComponent",
"props": [
{
"name": "label",
"dataType": "string",
"propType": 1,
"isOptional": false
},
{
"name": "size",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["small", "medium", "large"],
"config": {
"layerProp": "Size"
}
}
],
"config": {
"layerName": "Button",
"tag": "app-button"
}
},
{
"path": "./src/app/components/input/input.component.ts",
"name": "InputComponent",
"props": [
{
"name": "type",
"dataType": "enum",
"propType": 1,
"isOptional": true,
"expectValues": ["text", "email", "password"]
},
{
"name": "placeholder",
"dataType": "string",
"propType": 1,
"isOptional": true
}
],
"config": {
"layerName": "Input",
"tag": "app-input",
"directives": [
{ "selector": "matInput" }
],
"defaultAttributes": {
"type": "text",
"class": "form-control"
}
}
},
{
"path": "./src/app/components/card/card.component.ts",
"name": "CardComponent",
"props": [
{
"name": "header",
"dataType": "node",
"propType": 5,
"isOptional": true,
"config": {
"layerName": "Header"
}
}
],
"config": {
"layerName": "Card",
"tag": "app-card"
}
}
],
"projectId": "abc123xyz",
"projectName": "My Angular App"
}Configuration Properties Reference
Component-Level Properties
| Property | Type | Description |
|---|---|---|
path | string | Relative path to component file from project root |
name | string | Component class name |
props | array | Array of @Input prop definitions |
config | object | Component-level configuration |
config.tag | string | Custom HTML tag (Angular-specific) |
config.directives | array | Angular directives (Angular-specific) |
config.defaultAttributes | object | Default HTML attributes (Angular-specific) |
targetTags | array | Replace native HTML tags |
wrapperComponent | object | Define wrapper component |
subComponents | array | Configure child components |
Prop Properties
| Property | Type | Description |
|---|---|---|
name | string | @Input property name |
dataType | string | Data type: string, number, boolean, enum, node |
propType | number | 1 for most types, 4 for boolean, 5 for node |
isOptional | boolean | Whether @Input is optional |
expectValues | array | Allowed values for enum types |
config | object | Prop-level mapping configuration |
Advanced Features
1. Using Directives
Apply Material UI or custom directives:
{
"components": [
{
"path": "./src/app/components/select/select.component.ts",
"name": "SelectComponent",
"config": {
"tag": "app-select",
"directives": [
{ "selector": "matSelect" },
{ "selector": "matOption" }
]
},
"props": []
}
]
}2. Custom Tags
Override the component selector:
{
"components": [
{
"path": "./src/app/components/button/button.component.ts",
"name": "ButtonComponent",
"config": {
"tag": "custom-btn",
"layerName": "Button"
},
"props": []
}
]
}3. Default Attributes
Set default HTML attributes:
{
"components": [
{
"path": "./src/app/components/input/input.component.ts",
"name": "InputComponent",
"config": {
"tag": "app-input",
"defaultAttributes": {
"type": "text",
"placeholder": "Enter text",
"class": "form-control",
"autocomplete": "off"
}
},
"props": []
}
]
}4. Wrapper Component
Define a parent wrapper for your component:
{
"components": [
{
"path": "./src/app/components/input/input.component.ts",
"name": "InputComponent",
"wrapperComponent": {
"component": "FormFieldComponent",
"props": [
{
"name": "label",
"config": {
"layerName": "Label"
}
}
]
},
"props": []
}
]
}5. Sub Components
Configure child components:
{
"components": [
{
"path": "./src/app/components/form-field/form-field.component.ts",
"name": "FormFieldComponent",
"subComponents": [
{
"component": "InputComponent",
"props": [
{
"name": "placeholder",
"config": {
"layerProp": "Placeholder"
}
}
]
}
],
"props": []
}
]
}Workflow
- Create your Angular components with @Input/@Output decorators
- Run UIPro configuration in your IDE:
"Create UIPro config for all components" - UIPro generates
locofy.config.jsonautomatically - Add Angular-specific config (directives, tags, attributes) if needed
- Open Figma and use the UIPro plugin
- Map components in the UIPro interface
- Generate code and UIPro will use your custom components
Best Practices
- ✅ Use strict TypeScript types for @Input properties
- ✅ Use
config.directivesfor Material UI components - ✅ Set custom tags with
config.tagfor better readability - ✅ Use
defaultAttributesfor common HTML attributes - ✅ Keep component and Figma names consistent
- ✅ Use
valueMappingwhen Figma differs from code - ✅ Set
isOptional: falsefor required @Input properties - ✅ Organize components by feature modules
Troubleshooting
Component not detected
- Ensure the component has
@Componentdecorator - Check that the component class name matches the
namefield - Verify the file path is correct
Directives not applying
- Check directive selector matches exactly
- Ensure the directive module is imported in your Angular module
- Verify directive syntax in
config.directives
Props not mapping correctly
- Check
config.layerPropmatches Figma property name exactly - Verify
valueMappingfor different value names - Ensure
dataTypematches the @Input type
Custom tag not working
- Verify
config.tagis set correctly - Ensure the tag name is valid (lowercase with hyphens)
- Check that the component is declared in the module
Next Steps
- Check the main Custom Components guide for general concepts
- Explore other framework guides: React, Vue, React Native
- Join our Discord community (opens in a new tab) for support