| name | UI Component Generation |
| description | Generate multi-file React component bundles with design tokens from natural language descriptions. |
UI Component Generation Skill
You are now acquiring the skill of generating React UI components. After reading
this document, you will know how to produce high-quality, multi-file React
component bundles from natural language descriptions.
Hard Rules
- All colors, spacing, typography, and radii MUST use
--cg- design
tokens. No hex colors, no rgb(), no named colors, no raw pixel values.
Hardcoded values like #8B6F47 or color: olive break the live theme
switcher. This is a build error, not a suggestion.
- Your output renders inside a host application. Don't create app names,
brand headers, splash screens, or taglines. Start with the actual task UI.
The host provides the chrome.
What You're Building
A multi-file React component bundle rendered in a sandboxed iframe. The
bundle consists of:
App.jsx — the root component that accepts configuration props
components/*.jsx — reusable sub-components
styles.css — shared styles using CSS custom properties
Components use inline styles with CSS custom properties from a design token
system. Import resolution between files is handled automatically by the build
pipeline.
Output Format
Save files as jsx and return all of them as the outcome.
Component Library
Before generating sub-components, check /mnt/library/ for existing components
from previous runs. Each subdirectory is a previous run, containing its
App.jsx and components/*.jsx.
Reuse workflow:
- Use
system_read_text_from_file to list /mnt/library/ and browse available
components.
- If a component matches what you need (e.g., a
PieChart, Header), just
import it — import PieChart from "./components/PieChart". You do NOT need
to save the file; the build pipeline resolves library components
automatically.
- Only generate a new component from scratch when nothing in the library fits.
- You may adapt a library component by reading it, modifying it, and saving the
modified version to your output.
When you reuse a component, include a comment at the top:
// Reused from: library/<run-id>/<filename>
Rules
- App.jsx is the entry point. It must be named exactly
App.jsx and
contain a function called App.
- App carries the configuration. All data that should be configurable by
the caller (location, users, items, dates, etc.) appears as props on
App
with realistic defaults.
- Sub-components are reusable. Each component in
components/ should
render standalone with sensible defaults. Document all props with @prop
JSDoc.
- Every file imports what it uses. Include
import React from "react" in
every JSX file. Import sub-components with relative paths (e.g.
import Header from "./components/Header").
- CSS imports work. Use
import "./styles.css" in App.jsx for shared
styles.
- Export default. Each component file must
export default its component
function.
Configuration Props
When creating a component, think about what data the caller would want to
customize. These become props on App:
| UI Type | Example Props |
|---|
| Weather dashboard | location, temperature, condition, forecast (array) |
| User profile | name, avatar, bio, stats (object) |
| Product card | title, price, image, rating, reviews |
| Task manager | tasks (array), categories, user |
| Analytics dashboard | metrics (array), timeRange, chartData |
All props MUST have realistic default values so the component renders standalone
with zero configuration.
Design Token System
Reminder: this is a hard rule (see above). Every visual value — colors,
spacing, type, radii, shadows — MUST use --cg- tokens. No exceptions.
Token Rules
| Category | Use | Never |
|---|
| Colors | var(--cg-color-...) | #hex, rgb(), named colors |
| Spacing | var(--cg-sp-...) | Raw pixel values for padding/margin/gap |
| Font sizes | var(--cg-text-...-size) | 14px, 1rem |
| Border radius | var(--cg-radius-...) or var(--cg-card-radius) | 12px, 24px |
| Shadows | var(--cg-elevation-...) or var(--cg-card-shadow) | Raw box-shadow values |
| Font family | var(--cg-font-sans) or var(--cg-font-mono) | 'Arial', sans-serif |
Available Tokens
Colors: --cg-color-surface-dim, --cg-color-surface,
--cg-color-surface-bright, --cg-color-surface-container-lowest,
--cg-color-surface-container-low, --cg-color-surface-container,
--cg-color-surface-container-high, --cg-color-surface-container-highest,
--cg-color-on-surface, --cg-color-on-surface-muted, --cg-color-primary,
--cg-color-primary-container, --cg-color-on-primary,
--cg-color-on-primary-container, --cg-color-secondary,
--cg-color-secondary-container, --cg-color-on-secondary,
--cg-color-on-secondary-container, --cg-color-tertiary,
--cg-color-tertiary-container, --cg-color-on-tertiary,
--cg-color-on-tertiary-container, --cg-color-error,
--cg-color-error-container, --cg-color-on-error,
--cg-color-on-error-container, --cg-color-outline,
--cg-color-outline-variant
Typography: --cg-font-sans, --cg-font-mono,
--cg-text-display-{lg,md,sm}-{size,line-height,weight},
--cg-text-headline-{lg,md,sm}-{size,line-height,weight},
--cg-text-title-{lg,md,sm}-{size,line-height,weight},
--cg-text-body-{lg,md,sm}-{size,line-height,weight},
--cg-text-label-{lg,md,sm}-{size,line-height,weight}
Spacing (4px grid): --cg-sp-0 through --cg-sp-16
Radius: --cg-radius-{xs,sm,md,lg,xl,full}
Elevation: --cg-elevation-{1,2,3}
Motion: --cg-motion-duration-{short,medium,long},
--cg-motion-easing-{standard,decel,accel}
Component tokens: Card: --cg-card-{bg,radius,padding,shadow}, Button:
--cg-button-{radius,padding,bg,color,font-size,font-weight}, Input:
--cg-input-{bg,border,radius,padding,color,placeholder}, Badge:
--cg-badge-{bg,color,radius,padding,font-size}, Divider:
--cg-divider-{color,thickness,style}
Expressive: --cg-border-{style,width},
--cg-heading-{transform,letter-spacing},
--cg-img-{radius,border,shadow,filter}, --cg-hover-{scale,brightness,shadow}
Component Design
Decomposition
- Compose, don't monolith. A dashboard should be built from
Header,
MetricsGrid, ForecastCard, etc.
- Each component renders standalone with realistic defaults.
- The top-level App composes everything into a cohesive layout.
Icons
Google Material Symbols Outlined is available:
<span className="material-symbols-outlined" style={{ fontSize: "20px" }}>
search
</span>
Interactivity
Components should be interactive where appropriate. Use useState, useEffect
with cleanup. Supported patterns: timers, carousels, accordions, tabs,
checklists, toggles.
Stable Defaults
Never use Date.now(), Math.random(), or new Date() in default parameters.
Compute once at module level or use useState(() => ...).
Multi-View Apps (Journey Mode)
When building UI for a journey segment, you are building a multi-view
mini-app — one React component per state in the segment's XState machine.
Critical: You're a Segment, Not a Standalone App
Your mini-app is one segment of a wider orchestrated journey. Between
segments, an LLM orchestrator examines the user's data and decides what comes
next. This means:
- Don't brand it. No app names, no splash screens, no taglines. The host
application provides the chrome and framing. Start with the task UI.
- Emit your data. The last view in your segment MUST call
ark.emit() to
hand collected data back to the orchestrator. Without this, the journey stalls.
- Receive context. Your segment may receive data from prior segments as
props. Use it to personalize the experience.
File Structure
Each state gets its own component file named after the state:
App.jsx — shell that renders the initial state
views/InputRequirements.jsx — one view per state
views/SelectModels.jsx
views/DetailedComparison.jsx
views/DecisionReport.jsx
components/*.jsx — shared sub-components (reusable across views)
styles.css — shared styles
Navigation
Within the segment, views navigate using window.ark.navigateTo. At the
boundary (the segment's final view), use window.ark.emit to send data
back to the orchestrator.
The Ark SDK is available as window.ark. It has exactly three methods:
window.ark.navigateTo("select_models", { teamProfile });
window.ark.emit("journey:result", { decision, comparisonSet });
window.ark.asset("logo");
Do not call any other methods on window.ark. There is no onNavigation,
subscribe, or event listener API. Navigation state is managed internally by
your App component (e.g. useState + switch statement), not by the SDK.
View Contract
Each view component receives two props:
data — the journey context relevant to this state
onTransition — callback for state transitions (wired to ark.navigateTo)
export default function SelectModels({ data = {}, onTransition }) {
const handleSelect = (item) => {
onTransition("detailed_comparison", {
...data,
shortlist: [...(data.shortlist || []), item],
});
};
}
Rules
- One view per state. Don't merge states into a single component.
- Views are self-contained. Each view renders standalone with defaults.
- Shared components go in
components/. Headers, cards, buttons used
across multiple views should be extracted.
- Context flows forward. Each transition carries the data the next view
needs. Views never fetch — they receive.
- Final view emits. The last view must include a CTA that calls
window.ark.emit("journey:result", data) with the data the orchestrator
needs to decide what happens next.
Available Globals
React, useState, useEffect, useRef, useCallback, useMemo,
useContext, useReducer, useLayoutEffect, memo, forwardRef,
createContext, Fragment
Generate realistic, plausible sample data — no "Lorem ipsum". Be creative and
visually impressive.