Provides guidelines for converting designs from Pencil .pen files into React + Tailwind CSS code.
Conversion Workflow
1. Design Analysis
Identify the structure of the target node:
mcp__pencil__batch_get(
filePath: "design.pen",
nodeIds: ["targetNodeId"],
readDepth: 5,
resolveInstances: true,
resolveVariables: true
)
resolveInstances: trueto expand and inspect component instances, and resolveVariables: trueto check variable values as actual values.
2. Variable Extraction
Convert design tokens into Tailwind configuration:
mcp__pencil__get_variables(filePath: "design.pen")
3. Code Generation
Convert to appropriate React components according to node type.
4. Screenshot Comparison
Visually verify the generated code.
Node to Component Mapping
| Pencil Node | React/HTML | Tailwind Classes |
|---|---|---|
| frame (layout: vertical) | <div> |
flex flex-col |
| frame (layout: horizontal) | <div> |
flex flex-row |
| frame (layout: grid) | <div> |
grid grid-cols-N |
| text | <p>, <span>, <h1-6> |
text-*, font-* |
| rectangle | <div> |
rounded-*, bg-* |
| ref (Button) | <button> or <Button> |
Per component |
Property to Tailwind Mapping
Layout
| Pencil Property | Tailwind |
|---|---|
layout: "horizontal" |
flex flex-row |
layout: "vertical" |
flex flex-col |
layout: "grid" |
grid |
gap: 16 |
gap-4 |
padding: 16 |
p-4 |
padding: [16, 24, 16, 24] |
py-4 px-6 |
alignItems: "center" |
items-center |
justifyContent: "center" |
justify-center |
justifyContent: "space-between" |
justify-between |
Sizing
| Pencil Property | Tailwind |
|---|---|
width: "fill_container" |
w-full |
height: "fill_container" |
h-full |
width: "hug_contents" |
w-fit |
width: 320 |
w-80 or w-[320px] |
Typography
| Pencil Property | Tailwind |
|---|---|
fontSize: 16 |
text-base |
fontSize: 24 |
text-2xl |
fontWeight: "bold" |
font-bold |
fontWeight: "semibold" |
font-semibold |
textAlign: "center" |
text-center |
textColor: "#3B82F6" |
text-blue-500 |
Colors
| Pencil Color | Tailwind |
|---|---|
#3B82F6 |
blue-500 |
#EF4444 |
red-500 |
#22C55E |
green-500 |
#F8FAFC |
slate-50 |
#0F172A |
slate-900 |
Border Radius
| Pencil Property | Tailwind |
|---|---|
cornerRadius: 4 |
rounded-sm |
cornerRadius: 8 |
rounded-lg |
cornerRadius: 12 |
rounded-xl |
cornerRadius: 9999 |
rounded-full |
Effects
| Pencil Property | Tailwind |
|---|---|
opacity: 0.5 |
opacity-50 |
overflow: "hidden" |
overflow-hidden |
| Shadow (small) | shadow-sm |
| Shadow (medium) | shadow-md |
Code Generation Patterns
Basic Component
Pencil node:
{
"type": "frame",
"layout": "vertical",
"padding": 24,
"gap": 16,
"fill": "#FFFFFF",
"cornerRadius": 12,
"children": [
{ "type": "text", "content": "Title", "fontSize": 24, "fontWeight": "bold" },
{ "type": "text", "content": "Description", "textColor": "#64748B" }
]
}
React + Tailwind:
function Card() {
return (
<div className="flex flex-col p-6 gap-4 bg-white rounded-xl">
<h2 className="text-2xl font-bold">Title</h2>
<p className="text-slate-500">Description</p>
</div>
)
}
Interactive Component
Button component:
interface ButtonProps {
children: React.ReactNode
variant?: 'primary' | 'secondary'
onClick?: () => void
}
function Button({ children, variant = 'primary', onClick }: ButtonProps) {
const baseClasses = "flex items-center justify-center px-6 py-3 gap-2 rounded-lg font-medium transition-colors"
const variantClasses = {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-transparent border border-blue-500 text-blue-500 hover:bg-blue-50"
}
return (
<button
className={`${baseClasses} ${variantClasses[variant]}`}
onClick={onClick}
>
{children}
</button>
)
}
Layout Component
Page layout:
function PageLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen">
{/* Sidebar */}
<aside className="flex flex-col w-64 p-6 gap-4 bg-slate-50 border-r border-slate-200">
<h1 className="text-xl font-bold">Logo</h1>
<nav className="flex flex-col gap-2">
<a href="#" className="px-4 py-2 rounded-lg hover:bg-slate-100">Dashboard</a>
<a href="#" className="px-4 py-2 rounded-lg hover:bg-slate-100">Settings</a>
</nav>
</aside>
{/* Main Content */}
<main className="flex-1 p-8">
{children}
</main>
</div>
)
}
Variable to Tailwind Config
Convert Pencil variables to tailwind.config.js:
Input (Pencil Variables)
{
"colors": {
"primary": { "500": "#3B82F6", "600": "#2563EB" },
"neutral": { "50": "#F8FAFC", "900": "#0F172A" }
},
"spacing": { "4": 16, "6": 24, "8": 32 },
"radii": { "md": 8, "lg": 12 }
}
Output (Tailwind Config)
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: {
500: '#3B82F6',
600: '#2563EB',
},
neutral: {
50: '#F8FAFC',
900: '#0F172A',
},
},
spacing: {
4: '16px',
6: '24px',
8: '32px',
},
borderRadius: {
md: '8px',
lg: '12px',
},
},
},
}
Best Practices
Component Naming
- Use meaningful names (Card, Button, Header)
- Use PascalCase
- File names match component names
Props Design
- Distinguish required props from optional props
- Provide reasonable default values
- Define TypeScript interfaces
Tailwind Usage
- Prefer utility classes
- Extract repeated patterns into components
- Use arbitrary values for complex values
w-[320px] @applyminimize
Accessibility
- Use semantic HTML elements
- Add ARIA attributes appropriately
- Support keyboard navigation
- Check color contrast
Output Format
Single TSX file format:
// ComponentName.tsx
import { useState } from 'react'
interface ComponentNameProps {
// props 정의
}
export function ComponentName({ ...props }: ComponentNameProps) {
// 상태 및 로직
return (
// JSX
)
}
Additional Resources
Reference Files
references/tailwind-mapping.md- Full Pencil-Tailwind mapping tablereferences/component-templates.md- Common component templates