From React Components
Convert React JSX to A2UI JSON structures.
This guide helps you migrate from other UI approaches to the A2UI component system.
A2UI (App-to-UI) is a declarative, platform-agnostic UI system that:
From React Components
Convert React JSX to A2UI JSON structures.
From HTML/CSS
Transform static markup to A2UI components.
From Custom JSON
Map your schema to A2UI format.
From Figma
Export Figma designs as A2UI.
<div className="p-4 bg-white rounded-lg shadow"> <h2 className="text-xl font-bold">{title}</h2> <p className="text-gray-600">{description}</p> <button onClick={handleClick}> Click me </button></div>{ "type": "Column", "style": { "className": "p-4 bg-white rounded-lg shadow" }, "children": [ { "type": "Text", "props": { "variant": "h2", "content": { "path": "/title" } }, "style": { "className": "text-xl font-bold" } }, { "type": "Text", "props": { "content": { "path": "/description" } }, "style": { "className": "text-gray-600" } }, { "type": "Button", "props": { "label": "Click me" }, "actions": { "onClick": { "type": "emit", "event": "button_clicked" } } } ]}| React | A2UI Equivalent |
|---|---|
<div> | Column, Row, or Stack |
<span> | Text |
<h1>-<h6> | Text with variant prop |
<p> | Text |
<img> | Image |
<button> | Button |
<input type="text"> | TextField |
<input type="checkbox"> | Checkbox |
<input type="radio"> | RadioGroup |
<select> | Select |
<ul>/<ol> | List |
<form> | Column with form handling |
const handleSubmit = (e) => { e.preventDefault(); submitForm(formData);};
<form onSubmit={handleSubmit}> <input value={name} onChange={(e) => setName(e.target.value)} /> <button type="submit">Submit</button></form>{ "type": "Column", "children": [ { "type": "TextField", "props": { "value": { "path": "/form/name" } }, "actions": { "onChange": { "type": "update", "path": "/form/name" } } }, { "type": "Button", "props": { "label": "Submit" }, "actions": { "onClick": { "type": "submit", "formId": "contact-form" } } } ]}{isLoading && <Spinner />}{error && <ErrorMessage error={error} />}{data && <DataDisplay data={data} />}{ "type": "Column", "children": [ { "type": "Icon", "condition": { "path": "/isLoading" }, "props": { "name": "spinner", "spin": true } }, { "type": "Text", "condition": { "path": "/error" }, "props": { "content": { "path": "/error" } }, "style": { "className": "text-red-500" } }, { "type": "Column", "condition": { "path": "/data" }, "children": [...] } ]}<ul> {items.map((item) => ( <li key={item.id}> <span>{item.name}</span> <button onClick={() => deleteItem(item.id)}> Delete </button> </li> ))}</ul>{ "type": "List", "props": { "items": { "path": "/items" } }, "itemTemplate": { "type": "Row", "style": { "className": "justify-between items-center" }, "children": [ { "type": "Text", "props": { "content": { "path": "name" } } }, { "type": "Button", "props": { "label": "Delete", "variant": "ghost" }, "actions": { "onClick": { "type": "emit", "event": "delete_item", "payload": { "id": { "path": "id" } } } } } ] }}<div class="card"> <div class="card-header"> <img src="avatar.jpg" class="avatar"> <div class="user-info"> <h3 class="user-name">John Doe</h3> <span class="user-role">Developer</span> </div> </div> <div class="card-body"> <p>User description here...</p> </div> <div class="card-footer"> <button class="btn btn-primary">Follow</button> </div></div>{ "type": "Card", "children": [ { "type": "Row", "style": { "className": "gap-3 items-center" }, "children": [ { "type": "Image", "props": { "src": { "path": "/user/avatar" } }, "style": { "className": "w-12 h-12 rounded-full" } }, { "type": "Column", "children": [ { "type": "Text", "props": { "variant": "h3", "content": { "path": "/user/name" } } }, { "type": "Text", "props": { "content": { "path": "/user/role" } }, "style": { "className": "text-muted-foreground" } } ] } ] }, { "type": "Text", "props": { "content": { "path": "/user/description" } }, "style": { "className": "py-4" } }, { "type": "Button", "props": { "label": "Follow", "variant": "default" } } ]}A2UI uses Tailwind CSS classes. Common conversions:
| CSS Property | Tailwind Class |
|---|---|
margin: 16px | m-4 |
padding: 24px | p-6 |
display: flex | flex |
flex-direction: column | flex-col |
justify-content: space-between | justify-between |
align-items: center | items-center |
gap: 8px | gap-2 |
border-radius: 8px | rounded-lg |
font-size: 18px | text-lg |
font-weight: bold | font-bold |
color: #gray | text-gray-500 |
background: white | bg-white |
A2UI uses a data path system instead of component state:
const [count, setCount] = useState(0);
<button onClick={() => setCount(c => c + 1)}> Count: {count}</button>{ "type": "Button", "props": { "label": { "template": "Count: {count}", "values": { "count": { "path": "/count" } } } }, "actions": { "onClick": { "type": "update", "path": "/count", "transform": "increment" } }}const [form, setForm] = useState({ email: '', password: ''});
<input type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })}/>{ "type": "TextField", "props": { "type": "email", "value": { "path": "/form/email" }, "placeholder": "Enter email" }, "actions": { "onChange": { "type": "update", "path": "/form/email" } }}Audit Your Components
List all components that need migration. Prioritize:
Map Data Requirements
Document what data each component needs:
Design Data Paths
Create a data path schema:
/user/name/user/email/form/email/form/password/ui/isModalOpen/items/0/nameConvert Components
Start with leaf components (no children), then work up:
Set Up Actions
Map interactions to A2UI actions:
onClick actionsonChange with updatesubmit actionsnavigate actionsTest and Validate
const [isOpen, setIsOpen] = useState(false);
<button onClick={() => setIsOpen(true)}>Open</button><Modal open={isOpen} onClose={() => setIsOpen(false)}> <ModalContent>...</ModalContent></Modal>{ "type": "Column", "children": [ { "type": "Button", "props": { "label": "Open" }, "actions": { "onClick": { "type": "update", "path": "/ui/modalOpen", "value": true } } }, { "type": "Modal", "props": { "open": { "path": "/ui/modalOpen" } }, "actions": { "onClose": { "type": "update", "path": "/ui/modalOpen", "value": false } }, "children": [...] } ]}const [activeTab, setActiveTab] = useState('tab1');
<Tabs value={activeTab} onChange={setActiveTab}> <Tab value="tab1">Tab 1</Tab> <Tab value="tab2">Tab 2</Tab></Tabs><TabPanel value="tab1">Content 1</TabPanel><TabPanel value="tab2">Content 2</TabPanel>{ "type": "Tabs", "props": { "value": { "path": "/ui/activeTab" }, "tabs": [ { "value": "tab1", "label": "Tab 1" }, { "value": "tab2", "label": "Tab 2" } ] }, "actions": { "onChange": { "type": "update", "path": "/ui/activeTab" } }, "children": [ { "type": "Column", "condition": { "path": "/ui/activeTab", "equals": "tab1" }, "children": [...] }, { "type": "Column", "condition": { "path": "/ui/activeTab", "equals": "tab2" }, "children": [...] } ]}