Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save connorwforsyth/e76b05c47cb95d1a2774a75bbe9d06b5 to your computer and use it in GitHub Desktop.

Select an option

Save connorwforsyth/e76b05c47cb95d1a2774a75bbe9d06b5 to your computer and use it in GitHub Desktop.
Figma Icon Variant Builder
// Get current selection
const selection = figma.currentPage.selection;
// First, convert all selections to components if they aren't already
const componentNodes = [];
for (let i = 0; i < selection.length; i++) {
const node = selection[i];
let component;
// If node is already a component, use it; otherwise create a new component
if (node.type === 'COMPONENT') {
component = node;
} else {
// Create a new component
component = figma.createComponent();
component.resize(node.width, node.height);
component.x = node.x;
component.y = node.y;
// Handle different node types
if (node.type === 'IMAGE') {
// For images, we need to copy the image hash
if (node.fills && node.fills.length > 0) {
component.fills = [...node.fills];
}
} else if (node.children && node.children.length > 0) {
// For nodes with children, clone each child
for (const child of node.children) {
const clone = child.clone();
component.appendChild(clone);
}
} else {
// For other node types, try to copy visual properties
if (node.fills) component.fills = [...node.fills];
if (node.strokes) component.strokes = [...node.strokes];
if (node.effects) component.effects = [...node.effects];
if (node.cornerRadius !== undefined) component.cornerRadius = node.cornerRadius;
}
// Set component name to match the original node
component.name = node.name;
// Replace the original node with the component
if (node.parent) {
node.parent.insertChild(node.parent.children.indexOf(node), component);
node.remove();
}
}
componentNodes.push(component);
}
// Group components by their icon name
const groupedComponents = {};
const standaloneComponents = [];
for (const component of componentNodes) {
const nameParts = component.name.split('_');
if (nameParts.length < 2) {
standaloneComponents.push(component);
continue; // Skip if not enough parts
}
const number = nameParts[0]; // e.g., "001"
// Get the name part (everything between number and variant type)
const namePartArray = nameParts.slice(1, nameParts.length - 1);
if (namePartArray.length === 0) {
standaloneComponents.push(component);
continue; // Skip if no middle parts
}
const namePart = namePartArray.join('_');
const groupKey = `${number}_${namePart}`;
if (!groupedComponents[groupKey]) {
groupedComponents[groupKey] = [];
}
groupedComponents[groupKey].push(component);
}
// Create a component set for each icon name
const componentSets = [];
for (const groupKey in groupedComponents) {
const components = groupedComponents[groupKey];
// Skip if there's only one component (need at least 2 for a component set)
if (components.length < 2) {
standaloneComponents.push(...components);
continue;
}
// Set variant properties for each component in this group
for (const comp of components) {
const nameParts = comp.name.split('_');
const variantType = nameParts[nameParts.length - 1]; // circle, grey01, outlined, white
// Set only the Type variant property
comp.name = `Type=${variantType}`;
}
// Combine components for this icon name into a component set
const componentSet = figma.combineAsVariants(components, figma.currentPage);
// Apply auto layout only to component sets
componentSet.layoutMode = "HORIZONTAL";
componentSet.primaryAxisAlignItems = "CENTER";
componentSet.counterAxisAlignItems = "CENTER";
componentSet.paddingLeft = 0;
componentSet.paddingRight = 0;
componentSet.paddingTop = 0;
componentSet.paddingBottom = 0;
componentSet.itemSpacing = 0;
// Set height to "Hug Contents"
componentSet.counterAxisSizingMode = "AUTO";
// Extract a nice name for the component set without the number
const nameParts = groupKey.split('_');
// Skip the first element (the number)
const namePartArray = nameParts.slice(1);
const displayName = namePartArray.map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
// Set the component set name without the number
componentSet.name = displayName;
componentSets.push(componentSet);
}
// Process standalone components - just clean up their names
for (const component of standaloneComponents) {
const name = component.name;
// Extract the number prefix (first part before underscore)
const numberMatch = name.match(/^(\d+)_/);
if (numberMatch) {
// Get the base name without the number
let baseName = name.substring(numberMatch[0].length);
// Format the name nicely
const displayName = baseName.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
// Set the component name without the number
component.name = displayName;
}
}
console.log(`Created ${componentSets.length} component sets with auto layout and height set to "Hug Contents"`);
console.log(`Kept ${standaloneComponents.length} standalone components with cleaned-up names`);
@connorwforsyth
Copy link
Copy Markdown
Author

Select the icons you want to turn into a component set. Depending on their naming convention - this should create all the variants you need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment