@reallygoodwork/coral-to-react
Generate React component code from Coral specifications.
Generate React component code from Coral specifications. This package transforms Coral JSON specifications into fully functional React components with TypeScript support, state management, event handlers, styling, and component composition.
Features
- Complete React Components - Generate fully functional React components from Coral specs
- TypeScript Support - Automatically generate TypeScript interfaces for props (supports both legacy and new props systems)
- Component Formats - Choose between function declarations or arrow functions
- Style Options - Output inline styles or separate CSS classes with Tailwind support
- Variant Strategies - Support for CVA (Class Variance Authority), inline styles, or custom variant handling
- Component Composition - Render component instances with prop/slot/event bindings
- Package Generation - Generate entire component libraries from Coral packages
- State Management - Generate React hooks (useState, useEffect, etc.)
- Event Handlers - Create methods and event handler functions
- Prettier Integration - Optional code formatting with Prettier
- Import Management - Generate necessary import statements automatically
Installation
npm install @reallygoodwork/coral-to-reactpnpm add @reallygoodwork/coral-to-reactyarn add @reallygoodwork/coral-to-reactAPI Reference
coralToReact
Main function to convert a Coral specification into React component code.
Prop
Type
Returns: Promise<{ reactCode: string; cssCode: string }> - Object containing generated React code and CSS (if using className format)
Basic Example
import { coralToReact } from '@reallygoodwork/coral-to-react';
import type { CoralRootNode } from '@reallygoodwork/coral-core';
const spec: CoralRootNode = {
name: 'Button',
componentName: 'Button',
elementType: 'button',
componentProperties: {
label: { type: 'string' },
onClick: { type: 'function' }
},
styles: {
padding: '10px 20px',
backgroundColor: '#007bff',
color: '#ffffff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
},
textContent: '{props.label}'
};
const { reactCode, cssCode } = await coralToReact(spec);Generated Output:
import React from 'react'
interface ButtonProps {
label: string
onClick?: () => void
}
export function Button(props: ButtonProps) {
return (
<button style={{ padding: '10px 20px', backgroundColor: '#007bff', color: '#ffffff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
{props.label}
</button>
)
}generatePackage
NEW - Generate an entire component library from a Coral package. This function processes all components in a package, resolves dependencies, and generates a complete React component library with proper imports and exports.
Prop
Type
Returns: Promise<PackageGenerationResult> - Object containing component files, CSS files, and index file
Package Generation Example
import { loadPackage } from '@reallygoodwork/coral-core';
import { generatePackage } from '@reallygoodwork/coral-to-react';
import * as fs from 'fs/promises';
// Load your Coral package
const pkg = await loadPackage('./coral.config.json', {
readFile: (path) => fs.readFile(path, 'utf-8'),
});
// Generate entire component library
const result = await generatePackage(pkg, {
componentFormat: 'arrow',
styleFormat: 'className',
includeTypes: true,
variantStrategy: 'cva',
});
// Write all component files
for (const file of result.components) {
await fs.writeFile(`./dist/${file.path}`, file.content);
}
// Write CSS files
for (const file of result.styles) {
await fs.writeFile(`./dist/${file.path}`, file.content);
}
// Write index file
if (result.index) {
await fs.writeFile(`./dist/${result.index.path}`, result.index.content);
}The generated package will include:
- All components in dependency order (dependencies generated first)
- Proper import statements between components
- CSS files (if using className format)
- Index file with all exports
- TypeScript type exports (if includeTypes is true)
Options
Configuration options for React generation.
Prop
Type
Component Format
Choose between function declaration or arrow function style:
Function Declaration (default):
export function Button(props: ButtonProps) {
return <button>Click me</button>
}Arrow Function:
export const Button = (props: ButtonProps) => {
return <button>Click me</button>
}Style Format
Inline Styles (default):
<button style={{ padding: '10px', backgroundColor: '#007bff' }}>
Click me
</button>CSS Classes:
// Component
<button className="coral-root-button">Click me</button>
// Separate CSS file
.coral-root-button {
padding: 10px;
background-color: #007bff;
}Automatic CVA Detection
When using styleFormat: 'className' with components that have variants, CVA is automatically enabled even if variantStrategy is not explicitly set. The generator detects variants and uses CVA to manage them efficiently.
Variant Strategy
The variantStrategy option controls how component variants are handled:
Learn more: For comprehensive information about Coral's variant system, see the Component Variants guide and Component Variants API.
'inline' (default): Variants are applied as inline styles or CSS classes directly in the JSX. Use this when you want simple, direct style application without CVA.
'cva': Uses Class Variance Authority for variant management. Requires styleFormat: 'className' and Tailwind CSS. Automatically generates CVA configuration from Coral variant definitions. Automatically enabled when styleFormat: 'className' and variants are detected.
'custom': Allows custom variant handling (for advanced use cases).
When CVA is Used
CVA is automatically used when:
styleFormat: 'className'is set, AND- The component has
componentVariantsdefined, AND variantStrategyis'cva'or not specified (defaults to auto-detection)
You can explicitly disable CVA by setting variantStrategy: 'inline'.
CVA Example
When using variantStrategy: 'cva' (or auto-detected) with variants:
// Coral spec with variants
const buttonSpec = {
componentVariants: {
axes: [
{ name: 'intent', values: ['primary', 'secondary'], default: 'primary' },
{ name: 'size', values: ['sm', 'md', 'lg'], default: 'md' }
],
compounds: [
{
conditions: { intent: 'destructive', size: 'sm' },
description: 'Small destructive buttons need extra emphasis'
}
]
},
styles: {
display: 'inline-flex',
borderRadius: '6px',
fontWeight: '500'
},
variantStyles: {
intent: {
primary: { backgroundColor: '#007bff', color: '#fff' },
secondary: { backgroundColor: '#6c757d', color: '#fff' }
},
size: {
sm: { padding: '4px 8px', fontSize: '12px' },
md: { padding: '8px 16px', fontSize: '14px' },
lg: { padding: '12px 24px', fontSize: '16px' }
}
},
compoundVariantStyles: [
{
conditions: { intent: 'destructive', size: 'sm' },
styles: { fontWeight: 'bold' }
}
}
};
const { reactCode } = await coralToReact(buttonSpec, {
styleFormat: 'className',
variantStrategy: 'cva', // or omit - auto-detected
includeTypes: true
});Generated React code:
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva('inline-flex rounded font-medium', {
variants: {
intent: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-500 text-white'
},
size: {
sm: 'px-2 py-1 text-xs',
md: 'px-4 py-2 text-sm',
lg: 'px-6 py-3 text-base'
}
},
compoundVariants: [
{
intent: 'destructive',
size: 'sm',
class: 'font-bold'
}
],
defaultVariants: {
intent: 'primary',
size: 'md'
}
});
interface ButtonProps {
label: string
intent?: 'primary' | 'secondary'
size?: 'sm' | 'md' | 'lg'
onClick?: () => void
}
export const Button = (props: ButtonProps) => {
const className = buttonVariants({
intent: props.intent,
size: props.size
});
return (
<button className={className} onClick={props.onClick}>
{props.label}
</button>
);
};Key Features:
- Base styles are converted to Tailwind classes and included in the CVA base
- Variant styles become CVA variant definitions
- Compound variants are automatically converted to CVA compound variants
- Default variants are set from Coral's default values
- TypeScript props automatically include variant axes as optional props
- Style conversion uses
@reallygoodwork/style-to-tailwindto convert CSS to Tailwind classes
Note: CVA generation requires the
@reallygoodwork/style-to-tailwindpackage to convert Coral styles to Tailwind classes. See the Style to Tailwind documentation for details.
Component Composition
NEW - Components can now embed other components using COMPONENT_INSTANCE type. This enables building complex UIs by composing smaller components together.
Learn more: For comprehensive information about component composition, prop bindings, slot bindings, and event bindings, see the Component Composition guide and Component Composition API.
Basic Component Instance
const cardSpec: CoralRootNode = {
name: 'Card',
elementType: 'div',
props: {
title: { type: 'string', required: true },
onSave: { type: 'function' }
},
children: [
{
id: 'save-button',
name: 'SaveButton',
type: 'COMPONENT_INSTANCE',
elementType: 'button',
$component: {
ref: './button.coral.json'
},
propBindings: {
label: 'Save',
intent: 'primary'
},
eventBindings: {
onClick: { $event: 'onSave' }
}
}
]
};
const { reactCode } = await coralToReact(cardSpec);Generated Output:
import { Button } from './Button';
export function Card(props: CardProps) {
return (
<div>
<Button label="Save" intent="primary" onClick={props.onSave} />
</div>
);
}Prop Bindings
Prop bindings can be static values or dynamic references to parent props:
Static Values:
{
"propBindings": {
"label": "Click me",
"intent": "primary",
"disabled": false
}
}Dynamic Prop References:
{
"propBindings": {
"label": { "$prop": "buttonLabel" },
"disabled": { "$prop": "isDisabled" }
}
}This generates:
<Button label={props.buttonLabel} disabled={props.isDisabled} />Event Bindings
Event bindings forward events from child components to parent handlers:
{
"eventBindings": {
"onClick": { "$event": "onSave" },
"onChange": { "$event": "handleChange" }
}
}This generates:
<Button onClick={props.onSave} onChange={props.handleChange} />Slot Bindings
Slot bindings pass content to component slots:
{
"slotBindings": {
"default": { "$prop": "children" },
"header": [
{
"elementType": "h2",
"textContent": "Card Title"
}
]
}
}Variant Overrides
Override variant values for component instances:
{
"variantOverrides": {
"intent": "destructive",
"size": "lg"
}
}This locks the component instance to specific variant values, regardless of parent props.
Flattening Composition
By default, component instances are rendered as separate React components with imports. If you want to flatten them (resolve instances to their actual node structures), use the flattenComposition option:
import { loadPackage } from '@reallygoodwork/coral-core';
import { coralToReact } from '@reallygoodwork/coral-to-react';
const pkg = await loadPackage('./coral.config.json', options);
const { reactCode } = await coralToReact(cardSpec, {
flattenComposition: true,
package: pkg // Required for flattening
});When flattenComposition: true, component instances are resolved and inlined into the parent component's JSX structure.
Note: For more details on component composition, see the Composition guide.
Props Interface Generation
The generatePropsInterface function supports both the legacy componentProperties format and the new props definition system.
Learn more: For comprehensive information about defining props and events, see the Props & Events guide and Props & Events API.
Legacy Props System
const spec = {
componentProperties: {
label: { type: 'string' },
count: { type: 'number', optional: true }
}
};
const interface = generatePropsInterface(spec.componentProperties, 'Button');
// interface ButtonProps {
// label: string
// count?: number
// }New Props System
const spec = {
props: {
label: {
type: 'string',
required: true,
description: 'Button text'
},
intent: {
type: { enum: ['primary', 'secondary'] },
default: 'primary'
},
disabled: {
type: 'boolean',
default: false
}
}
};
const interface = generatePropsInterface(spec, 'Button');
// interface ButtonProps {
// label: string
// intent?: 'primary' | 'secondary'
// disabled?: boolean
// }The function automatically detects which props system is being used and generates the appropriate TypeScript interface.
Note: For details on the new props system, see the Props & Events documentation.
Helper Functions
generateComponent
Lower-level function for generating a single component. Used internally by coralToReact and generatePackage.
import { generateComponent } from '@reallygoodwork/coral-to-react';
const { reactCode, cssCode } = await generateComponent(spec, options);generateJSXElement
Generate JSX for a single Coral node.
import { generateJSXElement } from '@reallygoodwork/coral-to-react';
const jsx = generateJSXElement(node, indent, idMapping);
// '<div style={{ padding: "20px" }}>...</div>'generatePropsInterface
Generate TypeScript props interface. Supports both legacy and new props systems.
import { generatePropsInterface } from '@reallygoodwork/coral-to-react';
// Legacy format
const interface = generatePropsInterface(
{ title: { type: 'string' } },
'Card'
);
// New format (pass entire spec)
const interface = generatePropsInterface(spec, 'Card');generateStateHooks
Generate React useState hooks from Coral state specifications.
import { generateStateHooks } from '@reallygoodwork/coral-to-react';
const hooks = generateStateHooks([
{ name: 'count', setter: 'setCount', initialValue: 0 }
]);
// 'const [count, setCount] = useState(0)'generateMethods
Generate method/function code from Coral method specifications.
import { generateMethods } from '@reallygoodwork/coral-to-react';
const methods = generateMethods([
{ name: 'handleClick', parameters: [], body: 'console.log("clicked")' }
]);
// 'const handleClick = () => {\n console.log("clicked")\n}'generateCSS
Generate CSS code from Coral styles (used when styleFormat: 'className').
import { generateCSS } from '@reallygoodwork/coral-to-react';
const css = generateCSS(spec, idMapping);
// '.component-root { padding: 20px; }'generateImports
Generate import statements from Coral import specifications.
import { generateImports } from '@reallygoodwork/coral-to-react';
const imports = generateImports([
{ source: 'react', specifiers: [{ name: 'useState' }] }
]);
// "import { useState } from 'react'"stylesToInlineStyle
Convert Coral style objects to React inline style objects.
import { stylesToInlineStyle } from '@reallygoodwork/coral-to-react';
const inlineStyle = stylesToInlineStyle({ padding: '20px', margin: '10px' });
// 'style={{ padding: "20px", margin: "10px" }}'generateCVA
Generate Class Variance Authority (CVA) configuration and code from Coral component variants. This function is used internally by coralToReact but can also be called directly for custom CVA generation.
import { generateCVA } from '@reallygoodwork/coral-to-react';
const result = generateCVA(buttonSpec, {
includeStates: false, // Include state styles (hover, focus, etc.)
nodeId: undefined, // Generate for specific node (defaults to root)
});
// Returns:
// {
// config: CVAConfig, // CVA configuration object
// code: string, // Generated CVA function call code
// imports: string[], // Required import statements
// stateConfigs?: Record<string, CVAConfig> // State-specific CVA configs
// }Example:
const buttonSpec = {
componentVariants: {
axes: [
{ name: 'intent', values: ['primary', 'secondary'], default: 'primary' },
{ name: 'size', values: ['sm', 'md', 'lg'], default: 'md' }
]
},
variantStyles: {
intent: {
primary: { backgroundColor: '#007bff', color: '#fff' },
secondary: { backgroundColor: '#6c757d', color: '#fff' }
},
size: {
sm: { padding: '4px 8px', fontSize: '12px' },
md: { padding: '8px 16px', fontSize: '14px' },
lg: { padding: '12px 24px', fontSize: '16px' }
}
}
};
const result = generateCVA(buttonSpec);
// result.code:
// const componentVariants = cva('', {
// variants: {
// intent: {
// primary: 'bg-blue-500 text-white',
// secondary: 'bg-gray-500 text-white'
// },
// size: {
// sm: 'px-2 py-1 text-xs',
// md: 'px-4 py-2 text-sm',
// lg: 'px-6 py-3 text-base'
// }
// },
// defaultVariants: {
// intent: 'primary',
// size: 'md'
// }
// })generateCNHelper
Generate the cn utility function for merging Tailwind classes. This helper combines clsx and tailwind-merge for proper class merging and is automatically included when generating components with CVA support.
import { generateCNHelper } from '@reallygoodwork/coral-to-react';
const cnHelper = generateCNHelper();
// Returns:
// import { clsx, type ClassValue } from 'clsx'
// import { twMerge } from 'tailwind-merge'
//
// export function cn(...inputs: ClassValue[]) {
// return twMerge(clsx(inputs))
// }This utility is commonly used with CVA for merging variant classes with additional classes:
const className = cn(
buttonVariants({ intent: props.intent, size: props.size }),
props.className // Merge with additional classes
);Complete Examples
Button with Variants and CVA
import { coralToReact } from '@reallygoodwork/coral-to-react';
const buttonSpec = {
name: 'Button',
componentName: 'Button',
elementType: 'button',
componentVariants: {
axes: [
{ name: 'intent', values: ['primary', 'secondary'], default: 'primary' },
{ name: 'size', values: ['sm', 'md', 'lg'], default: 'md' }
]
},
props: {
label: { type: 'string', required: true },
disabled: { type: 'boolean', default: false }
},
events: {
onClick: { description: 'Button click handler' }
},
styles: {
display: 'inline-flex',
alignItems: 'center',
borderRadius: '6px',
fontWeight: '500',
border: 'none',
cursor: 'pointer'
},
variantStyles: {
intent: {
primary: { backgroundColor: '#007bff', color: '#ffffff' },
secondary: { backgroundColor: '#6c757d', color: '#ffffff' }
},
size: {
sm: { padding: '4px 8px', fontSize: '12px' },
md: { padding: '8px 16px', fontSize: '14px' },
lg: { padding: '12px 24px', fontSize: '16px' }
}
},
textContent: { $prop: 'label' },
elementAttributes: {
disabled: { $prop: 'disabled' }
},
eventHandlers: {
onClick: { $event: 'onClick' }
}
};
const { reactCode, cssCode } = await coralToReact(buttonSpec, {
componentFormat: 'arrow',
styleFormat: 'className',
variantStrategy: 'cva',
includeTypes: true
});Card with Component Composition
const cardSpec = {
name: 'Card',
componentName: 'Card',
elementType: 'div',
props: {
title: { type: 'string', required: true },
description: { type: 'string' },
onAction: { type: 'function' }
},
styles: {
padding: '24px',
borderRadius: '12px',
backgroundColor: '#ffffff',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
},
children: [
{
elementType: 'h3',
textContent: { $prop: 'title' },
styles: { fontSize: '20px', fontWeight: 'bold', marginBottom: '8px' }
},
{
elementType: 'p',
textContent: { $prop: 'description' },
styles: { color: '#666', lineHeight: '1.5' }
},
{
id: 'action-button',
type: 'COMPONENT_INSTANCE',
$component: { ref: './button.coral.json' },
propBindings: {
label: 'Learn More',
intent: 'primary'
},
eventBindings: {
onClick: { $event: 'onAction' }
}
}
]
};
const { reactCode } = await coralToReact(cardSpec, {
componentFormat: 'arrow',
includeTypes: true
});Component with State and Methods
const counterSpec = {
name: 'Counter',
componentName: 'Counter',
elementType: 'div',
stateHooks: [
{
name: 'count',
setter: 'setCount',
initialValue: 0,
tsType: 'number',
hookType: 'useState'
}
],
methods: [
{
name: 'increment',
body: 'setCount(count + 1)',
parameters: []
},
{
name: 'decrement',
body: 'setCount(count - 1)',
parameters: []
}
],
children: [
{
elementType: 'button',
elementAttributes: { onClick: '{increment}' },
textContent: '+'
},
{
elementType: 'span',
textContent: '{count}'
},
{
elementType: 'button',
elementAttributes: { onClick: '{decrement}' },
textContent: '-'
}
]
};
const { reactCode } = await coralToReact(counterSpec);Type Exports
Options
Configuration options for React generation.
Prop
Type
GeneratedFile
Represents a generated file in package generation.
Prop
Type
PackageGenerationResult
Result of package generation.
Prop
Type
Style Conversion
The package automatically converts Coral style objects to React-compatible formats:
Dimension Objects
// Coral
{ padding: { value: 20, unit: 'px' } }
// React (inline)
{ padding: '20px' }
// React (CSS)
padding: 20px;Color Objects
// Coral
{ backgroundColor: { hex: '#007bff', rgb: {...}, hsl: {...} } }
// React (inline)
{ backgroundColor: '#007bff' }
// React (CSS)
background-color: #007bff;Numeric Values
Numeric values are automatically converted:
- Most properties get
pxunits added - Unitless properties (like
fontWeight,lineHeight,zIndex) remain as numbers
Related Packages
- @reallygoodwork/coral-core - Core utilities, types, CLI, and composition helpers
- @reallygoodwork/react-to-coral - Convert React components to Coral specs (reverse operation)
- @reallygoodwork/coral-to-html - Convert Coral specs to HTML
- @reallygoodwork/style-to-tailwind - Convert CSS styles to Tailwind classes (used for CVA generation)
- @reallygoodwork/coral-tw2css - Convert Tailwind classes to CSS
Additional Resources
- Getting Started Guide - Learn how to use Coral in your projects
- Component Variants Guide - Understanding Coral's variant system
- Component Composition Guide - Deep dive into component composition
- Props & Events Guide - Understanding the props system
- Package System Guide - Working with Coral packages
Additional Resources
- Component Variants Guide - Learn about Coral's variant system
- Component Composition Guide - Deep dive into component composition
- Props & Events Guide - Understanding the props system
- Package System Guide - Working with Coral packages