# Code Conversion Reference
Convert Pencil designs to production code (HTML/CSS/React/Blazor).
## Overview
This guide covers converting `.pen` files to production code with proper structure, responsive design, and design system integration.
## Element Mapping
### Pencil to HTML/CSS
| Pencil Element | HTML | CSS Layout | Notes |
|----------------|------|------------|-------|
| `frame` (layout:vertical) | `
` | `flex-direction: column` | Flexbox container |
| `frame` (layout:horizontal) | `
` | `flex-direction: row` | Flexbox container |
| `frame` (layout:none) | `
` | `position: relative` | Free positioning |
| `text` | `
`, `
`-``, `` | - | Based on fontSize |
| `rectangle` | `` | - | With background |
| `ellipse` | `
` | `border-radius: 50%` | Circular shape |
| `icon_font` | `
` | - | Lucide icons |
### Pencil to React/Blazor
| Pencil | React | Blazor |
|--------|-------|--------|
| `frame` | `` | `
` |
| `text` | `
{content}
` | `
@content
` |
| `icon_font` | `
` | `
` |
| Component ref | `
` | ` ` |
## Property Mapping
### Layout Properties → CSS
```javascript
// Pencil layout properties
{
"layout": "vertical",
"gap": 16,
"padding": [24, 32],
"justifyContent": "center",
"alignItems": "center"
}
// CSS output
.container {
display: flex;
flex-direction: column;
gap: 16px;
padding: 24px 32px;
justify-content: center;
align-items: center;
}
```
### Size Properties → CSS
```javascript
// Fixed size
{ "width": 400, "height": 200 }
// CSS
.element { width: 400px; height: 200px; }
// Fill container
{ "width": "fill_container", "height": 200 }
// CSS
.element { width: 100%; height: 200px; }
```
### Color Properties → CSS
```javascript
// Solid color
{ "fill": "#FF5C00" }
// CSS
.element { background: #FF5C00; }
// Design token
{ "fill": "$bg-page" }
// CSS
.element { background: var(--bg-page); }
// Gradient
{
"fill": {
"type": "gradient",
"colors": [
{"color": "#FF5C00", "position": 0},
{"color": "#FF8A4C", "position": 1}
]
}
}
// CSS
.element {
background: linear-gradient(180deg, #FF5C00 0%, #FF8A4C 100%);
}
```
### Border Properties → CSS
```javascript
// Corner radius
{ "cornerRadius": 10 }
// CSS
.element { border-radius: 10px; }
// Stroke
{ "stroke": "$border-default", "strokeWidth": 2 }
// CSS
.element { border: 2px solid var(--border-default); }
```
## Conversion Workflow
### Step 1: Extract Design Tokens
```javascript
import pencilData from './design.pen';
// Extract variables
const tokens = pencilData.variables;
// Generate CSS Variables
const cssTokens = Object.entries(tokens)
.map(([key, value]) => {
if (value.type === 'color') {
return ` --${key}: ${value.value};`;
}
if (value.type === 'number') {
return ` --${key}: ${value.value}px;`;
}
return ` --${key}: ${value.value};`;
})
.join('\n');
console.log(`:root {\n${cssTokens}\n}`);
```
**Output:**
```css
:root {
--bg-page: #0A0A0B;
--text-primary: #FFFFFF;
--orange-primary: #FF5C00;
--space-4: 16px;
--radius-md: 10px;
}
```
### Step 2: Convert Layout Structure
```javascript
function convertElement(element) {
if (element.type === 'frame') {
return convertFrame(element);
}
if (element.type === 'text') {
return convertText(element);
}
if (element.type === 'icon_font') {
return convertIcon(element);
}
if (element.type === 'ref') {
return convertRef(element);
}
}
function convertFrame(frame) {
const className = frame.name.toLowerCase().replace(/\s+/g, '-');
const children = frame.children?.map(convertElement).join('\n') || '';
return `\n${children}\n
`;
}
function convertText(text) {
const tag = getTextTag(text.fontSize);
return `<${tag}>${text.content}${tag}>`;
}
function getTextTag(fontSize) {
if (fontSize >= 32) return 'h1';
if (fontSize >= 24) return 'h2';
if (fontSize >= 18) return 'h3';
if (fontSize >= 16) return 'h4';
return 'p';
}
function convertIcon(icon) {
return ` `;
}
```
### Step 3: Generate CSS Classes
```javascript
function generateCSS(element, className) {
const styles = [];
// Layout
if (element.layout) {
styles.push('display: flex');
styles.push(`flex-direction: ${element.layout === 'vertical' ? 'column' : 'row'}`);
}
// Gap
if (element.gap) {
styles.push(`gap: ${element.gap}px`);
}
// Padding
if (element.padding) {
const padding = Array.isArray(element.padding)
? `${element.padding[0]}px ${element.padding[1]}px`
: `${element.padding}px`;
styles.push(`padding: ${padding}`);
}
// Size
if (element.width) {
const width = element.width === 'fill_container' ? '100%' : `${element.width}px`;
styles.push(`width: ${width}`);
}
if (element.height) {
styles.push(`height: ${element.height}px`);
}
// Fill (background)
if (element.fill) {
if (element.fill.startsWith('$')) {
// Design token
const token = element.fill.substring(1);
styles.push(`background: var(--${token})`);
} else if (typeof element.fill === 'object' && element.fill.type === 'gradient') {
// Gradient
const gradient = convertGradient(element.fill);
styles.push(`background: ${gradient}`);
} else {
// Solid color
styles.push(`background: ${element.fill}`);
}
}
// Border radius
if (element.cornerRadius) {
styles.push(`border-radius: ${element.cornerRadius}px`);
}
// Alignment
if (element.justifyContent) {
const value = element.justifyContent.replace('_', '-');
styles.push(`justify-content: ${value}`);
}
if (element.alignItems) {
styles.push(`align-items: ${element.alignItems}`);
}
return `.${className} {\n ${styles.join(';\n ')};\n}`;
}
function convertGradient(fill) {
const colors = fill.colors
.map(c => `${c.color} ${c.position * 100}%`)
.join(', ');
return `linear-gradient(180deg, ${colors})`;
}
```
## Best Practices
### 1. Semantic HTML
```html
```
### 2. CSS Variables for Tokens
```css
/* ❌ BAD: Hardcoded colors */
.button {
background: #FF5C00;
color: #FFFFFF;
}
/* ✅ GOOD: CSS variables */
.button {
background: var(--orange-primary);
color: var(--text-primary);
}
```
### 3. Mobile-First Responsive
```css
/* ✅ GOOD: Mobile-first */
.container {
padding: 16px;
flex-direction: column;
}
@media (min-width: 768px) {
.container {
padding: 24px;
flex-direction: row;
}
}
@media (min-width: 1440px) {
.container {
padding: 80px 120px;
}
}
```
### 4. Component Structure
```jsx
// ✅ GOOD: Match Pencil component hierarchy
// Atom/Button/Primary/Default → Button.jsx
function Button({ children, variant = 'primary', state = 'default' }) {
return (
{children}
);
}
// CSS
.button {
/* Base styles */
}
.button--primary {
background: var(--orange-primary);
}
.button--default {
/* Default state */
}
.button--hover {
background: var(--orange-light);
}
```
## Framework-Specific Examples
### React Component
```jsx
import React from 'react';
import './HeroSection.css';
export function HeroSection({ title, subtitle, ctaText }) {
return (
{title}
{subtitle}
{ctaText}
);
}
```
```css
/* HeroSection.css */
.hero-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 80px 120px;
background: var(--bg-page);
}
.hero-content {
max-width: 800px;
text-align: center;
}
.hero-title {
font-size: 48px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 16px;
}
.hero-subtitle {
font-size: 18px;
color: var(--text-secondary);
margin-bottom: 32px;
}
.hero-cta {
padding: 16px 32px;
background: var(--orange-primary);
color: var(--text-primary);
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
}
.hero-cta:hover {
background: var(--orange-light);
}
```
### Blazor Component
```razor
@* HeroSection.razor *@
@Title
@Subtitle
@CtaText
@code {
[Parameter] public string Title { get; set; } = "";
[Parameter] public string Subtitle { get; set; } = "";
[Parameter] public string CtaText { get; set; } = "";
[Parameter] public EventCallback OnCtaClick { get; set; }
}
```
### Tailwind CSS
From design tokens to Tailwind config:
```javascript
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
'bg-page': '#0A0A0B',
'bg-surface': '#1A1A1C',
'text-primary': '#FFFFFF',
'orange-primary': '#FF5C00',
},
spacing: {
'1': '4px',
'2': '8px',
'4': '16px',
'6': '24px',
},
borderRadius: {
'sm': '6px',
'md': '10px',
'lg': '16px',
}
}
}
}
```
```jsx
// Component with Tailwind
{title}
{subtitle}
{ctaText}
```
## Conversion Tools
### Automated Conversion Script
```javascript
import fs from 'fs';
function convertPencilToHTML(pencilFile) {
const data = JSON.parse(fs.readFileSync(pencilFile, 'utf-8'));
// Extract main page
const mainPage = data.children[0];
// Generate HTML
const html = convertElement(mainPage);
// Generate CSS
const css = generateCSSForAll(mainPage);
// Generate design tokens
const tokens = generateTokensCSS(data.variables);
return {
html,
css: `${tokens}\n\n${css}`,
tokens: data.variables
};
}
// Example usage
const result = convertPencilToHTML('design.pen');
fs.writeFileSync('output.html', result.html);
fs.writeFileSync('output.css', result.css);
console.log('✅ Conversion complete!');
```
## Common Patterns
### Navigation Bar
```html
Sign Up
```
```css
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 120px;
background: var(--bg-surface);
}
.navbar-menu {
display: flex;
gap: 32px;
list-style: none;
}
.navbar-menu a {
color: var(--text-primary);
text-decoration: none;
}
```
### Card Component
```html
```
```css
.card {
display: flex;
flex-direction: column;
padding: 24px;
background: var(--bg-surface);
border-radius: var(--radius-md);
gap: 16px;
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
}
.card-description {
color: var(--text-secondary);
line-height: 1.6;
}
.card-link {
color: var(--orange-primary);
text-decoration: none;
font-weight: 600;
}
```
## Resources
- Back to [Pencil Design Skill](../SKILL.md)
- [File Format Reference](./FILE_FORMAT.md)
- [Build System Reference](./BUILD_SYSTEM.md)
- [Atomic Design Reference](./ATOMIC_DESIGN.md)
- [Tailwind Design System](../tailwind-design-system/SKILL.md)
- [React UI Components](../react-ui-components/SKILL.md)
- [Blazor Theme Patterns](../blazor-theme-patterns/SKILL.md)
- [MAUI Branding Expert](../maui-branding-expert/SKILL.md)
- [Swift UI Components](../swift-ui-components/SKILL.md)