skills/corlab-tech/skills/figma-illustration-import

figma-illustration-import

SKILL.md

Figma Illustration Import

Import multi-layer vector illustrations from Figma into reusable React atom components with local SVG/PNG assets, Storybook stories, and light/dark theme support.

Pre-Flight: Check Existing Illustrations

Before creating any illustration, check if it already exists:

# List all existing illustration components
find apps/web-app/src/components/atoms -name "Illustration*" -type f | sort

# List all existing illustration asset folders
ls apps/web-app/public/assets/illustrations/

If the illustration already exists, import and use it directly:

import { IllustrationPassword } from '../atoms/IllustrationPassword';
<IllustrationPassword size={160} />

If it does NOT exist, proceed with the workflow below.

Workflow

Step 1: Extract from Figma

Require a Figma node ID. Use the Figma MCP tools to extract the design:

mcp0_get_screenshot(nodeId: "<node-id>")
mcp0_get_design_context(nodeId: "<node-id>", artifactType: "REUSABLE_COMPONENT", forceCode: true)

The get_design_context response returns:

  • Asset URLshttp://localhost:3845/assets/<hash>.svg|png constants
  • React+Tailwind code — with absolute positioning using inset-[top right bottom left] percentages
  • Layer names — via data-name attributes (use these for descriptive file names)

Step 2: Download Assets

Create a folder and download all assets with descriptive names derived from layer names:

mkdir -p apps/web-app/public/assets/illustrations/<illustration-name>/

# Download each asset — name files descriptively based on layer purpose
curl -L -o <descriptive-name>.svg "http://localhost:3845/assets/<hash>.svg"
curl -L -o <descriptive-name>.png "http://localhost:3845/assets/<hash>.png"

Naming conventions:

  • Use kebab-case: shield-border.svg, check-circle.png
  • Name by visual purpose, not Figma layer ID: envelope-front.svg not vector-3.svg
  • Keep .svg for vectors, .png for rasters/gradients

Step 3: Create the Component

Create apps/web-app/src/components/atoms/Illustration<Name>.tsx:

'use client';

import { forwardRef } from 'react';
import { cn } from '../../lib/utils';

export interface Illustration<Name>Props
  extends React.HTMLAttributes<HTMLDivElement> {
  size?: number;
}

const Illustration<Name> = forwardRef<HTMLDivElement, Illustration<Name>Props>(
  ({ className, size = 160, ...props }, ref) => {
    return (
      <div
        ref={ref}
        className={cn('relative shrink-0', className)}
        style={{ width: size, height: size }}
        role="img"
        aria-label="<descriptive label>"
        {...props}
      >
        {/* Layer comment */}
        <div
          className="absolute"
          style={{
            top: '<top>%',
            right: '<right>%',
            bottom: '<bottom>%',
            left: '<left>%',
          }}
        >
          <img
            alt=""
            className="block size-full"
            src="/assets/illustrations/<name>/<asset>.svg"
            style={{ maxWidth: 'none' }}
          />
        </div>
        {/* ... repeat for each layer ... */}
      </div>
    );
  }
);

Illustration<Name>.displayName = 'Illustration<Name>';

export { Illustration<Name> };

Key patterns:

  • 'use client' directive — required (uses forwardRef)
  • cn() utility — from ../../lib/utils for className merging
  • size prop — default 160, controls width and height
  • Percentage-based positioning — from Figma's inset-[top right bottom left] values, split into individual top/right/bottom/left style properties
  • role="img" + aria-label — accessibility
  • mix-blend-multiply class — preserve from Figma when present on shadow layers
  • Rotated elements — wrap in flex container with items-center justify-center, inner div with flex-none and explicit width/height/transform
  • <img> tags — use alt="" (decorative), className="block size-full", style={{ maxWidth: 'none' }}

Step 4: Create Storybook Stories

Create apps/web-app/src/components/atoms/Illustration<Name>.stories.tsx:

import type { Meta, StoryObj } from '@storybook/react';
import { Illustration<Name> } from './Illustration<Name>';

const meta: Meta<typeof Illustration<Name>> = {
  title: 'Atoms/Illustration<Name>',
  component: Illustration<Name>,
  tags: ['autodocs'],
  argTypes: {
    size: {
      control: { type: 'range', min: 40, max: 320, step: 8 },
    },
  },
};

export default meta;
type Story = StoryObj<typeof Illustration<Name>>;

export const Default: Story = { args: { size: 160 } };
export const Small: Story = { args: { size: 80 } };
export const Large: Story = { args: { size: 240 } };

export const OnLightBackground: Story = {
  args: { size: 160 },
  decorators: [
    (Story) => (
      <div style={{ padding: '2rem', background: '#ffffff', borderRadius: '8px' }}>
        <Story />
      </div>
    ),
  ],
};

export const OnDarkBackground: Story = {
  args: { size: 160 },
  decorators: [
    (Story) => (
      <div style={{ padding: '2rem', background: '#020712', borderRadius: '8px' }}>
        <Story />
      </div>
    ),
  ],
};

export const LightAndDarkComparison: Story = {
  args: { size: 160 },
  render: (args) => (
    <div style={{ display: 'flex', gap: '2rem', alignItems: 'center' }}>
      <div style={{ padding: '2rem', background: '#ffffff', borderRadius: '8px', border: '1px solid #d7dde9' }}>
        <p style={{ marginBottom: '1rem', fontSize: '12px', color: '#7184af', fontFamily: 'Titillium Web, sans-serif' }}>Light</p>
        <Illustration<Name> {...args} />
      </div>
      <div style={{ padding: '2rem', background: '#020712', borderRadius: '8px', border: '1px solid #2f3953' }}>
        <p style={{ marginBottom: '1rem', fontSize: '12px', color: '#99a8c6', fontFamily: 'Titillium Web, sans-serif' }}>Dark</p>
        <Illustration<Name> {...args} />
      </div>
    </div>
  ),
};

Step 5: Verify

  1. Run TypeScript check: npx tsc --noEmit --project apps/web-app/tsconfig.json
  2. Open Storybook and navigate to the LightAndDarkComparison story
  3. Take a screenshot and compare with the Figma original
  4. Iterate if needed

Storybook Config Prerequisite

Ensure apps/web-app/.storybook/main.ts includes staticDirs:

const config: StorybookConfig = {
  stories: ['../**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
  addons: [],
  staticDirs: ['../public'],
  // ...
};

Without this, illustration assets will 404 in Storybook.

Theme Handling

Illustrations use the same assets for both light and dark themes. The SVGs use var(--fill-0, <fallback>) where the fallback is the light-theme color. Theme differentiation comes from the parent background color, not from swapping assets. A single component works on both light and dark backgrounds.

Weekly Installs
3
First Seen
Feb 13, 2026
Installed on
opencode3
gemini-cli3
github-copilot3
amp3
codex3
kimi-cli3