Introduction
Constela is a UI language that describes UI in JSON instead of JavaScript.
All UI elements—headings, text, buttons, state, and events—are defined as structured JSON and compiled into web pages.
Developers don't need to write JavaScript to create UI.
JavaScript is the execution target, not the description language for UI.
Constela treats UI as verifiable data rather than "code."
This allows detecting structural inconsistencies, undefined state references, and invalid update operations before execution.
The constrained JSON format means UI expression surface area is finite and statically analyzable.
This design is suitable not only for humans but also for generative AI workflows that create and modify UI.
Why Write UI in JSON?
1. Errors Are Detected at Compile Time
With React/TypeScript, type errors are caught by TypeScript, but logical UI errors (references to non-existent state, invalid event handlers, etc.) aren't discovered until runtime.
With Constela, all errors are detected at compile time:
{
code: 'UNDEFINED_STATE',
message: 'State "count" is not defined',
path: '/view/children/0/props/onClick' // Exact error location
}
2. State Management is Declarative and Predictable
React approach:
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
// Where setCount gets called is unclear
// Multiple useEffects can interleave
setCount(prev => prev + 1);
setLoading(false);
};
Constela approach:
{
"state": {
"count": { "type": "number", "initial": 0 },
"loading": { "type": "boolean", "initial": false }
},
"actions": [
{
"name": "increment",
"steps": [
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": true } },
{ "do": "update", "target": "count", "operation": "increment" },
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": false } }
]
}
]
}
In Constela, state types and initial values are explicitly declared, and actions are arrays of "what to do" steps. The execution path is crystal clear.
3. Constrained DSL Suitable for AI Generation
React's JSX allows embedding arbitrary JavaScript. This poses a security risk when AI generates UI.
Constela is a constrained DSL:
- 13 expression types (
lit,state,var,bin,not,param,cond,get,route,import,data,ref,index) - Only safe operators (
+,-,*,/,==,!=,<,<=,>,>=,&&,||) - Arbitrary JavaScript execution is impossible
This constraint allows AI to safely generate structurally correct UI.
Where does Constela make sense?
Constela is not aimed at traditional hand-written UI development.
Its value becomes clear in workflows where UI is generated, validated, or modified by machines, not just humans.
Some concrete scenarios where this model fits well:
AI-generated UI workflows
When UI is produced by AI (or other tools), JavaScript and JSX are often too flexible.
Small mistakes in state usage or update logic tend to surface only at runtime.
Because Constela uses a constrained and finite DSL, generated UI can be validated at compile time.
This makes it much safer to generate, regenerate, and iterate on UI automatically.
UI as data (CMS / form builders / admin tools)
In systems where UI is stored, diffed, versioned, or reviewed as data,
representing UI as structured JSON has clear advantages.
Constela allows UI definitions to be:
- diffable
- schema-validated
- statically analyzed
rather than hidden inside imperative code.
Safety-critical or restricted environments
Constela deliberately avoids arbitrary JavaScript execution.
State updates and side effects are expressed through a limited set of operations.
This makes UI behavior more predictable and easier to reason about
in environments where correctness and safety matter more than flexibility.
Constela is designed around one idea:
treat UI as something machines can reason about, not just code that happens to run.
Measured Differences: Constela vs Next.js
We rebuilt Constela’s official website using both Constela and Next.js and compared the results.
The measurements were repeated multiple times and showed consistent trends.
Results
| Metric | Constela | Next.js | Difference |
|---|---|---|---|
| Build time | 2.2s | 12.3s | 5.6× faster |
| node_modules size | 297MB | 794MB | 2.7× smaller |
| Output size | 14MB | 72MB | 5.1× smaller |
| Deploy time | 10s | 50s | 5.0× faster |
Why this difference exists
This is not an accidental optimization, but a structural difference.
Next.js is a full application framework.
Its build process includes routing analysis, bundling, optimization, and framework-level runtime setup.Constela is a compiler-first UI language.
UI is expressed as JSON, validated, and compiled directly into minimal runtime output.
As a result:
- The build pipeline is significantly simpler.
- Dependencies are smaller and more predictable.
- The generated output contains only what is required to render the UI.
These characteristics naturally lead to faster builds, smaller artifacts, and shorter deployment times in environments such as CI/CD pipelines and hosting platforms like Vercel.
Note: Performance is not Constela’s primary goal.
The core value lies in compile-time validation and safe UI generation.
The lighter build and deploy characteristics are a direct consequence of its design.
Code Examples
Counter App
{
"version": "1.0",
"state": {
"count": { "type": "number", "initial": 0 }
},
"actions": [
{
"name": "increment",
"steps": [
{ "do": "update", "target": "count", "operation": "increment" }
]
},
{
"name": "decrement",
"steps": [
{ "do": "update", "target": "count", "operation": "decrement" }
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "h1",
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "Constela Counter" } }
]
},
{
"kind": "element",
"tag": "div",
"children": [
{ "kind": "text", "value": { "expr": "state", "name": "count" } }
]
},
{
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "decrement" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "-" } }
]
},
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "increment" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "+" } }
]
}
]
}
]
}
}
Todo List with Loops
{
"version": "1.0",
"state": {
"todos": { "type": "list", "initial": [] },
"newTodo": { "type": "string", "initial": "" }
},
"actions": [
{
"name": "addTodo",
"steps": [
{
"do": "update",
"target": "todos",
"operation": "push",
"value": { "expr": "state", "name": "newTodo" }
},
{ "do": "set", "target": "newTodo", "value": { "expr": "lit", "value": "" } }
]
}
],
"view": {
"kind": "element",
"tag": "ul",
"children": [
{
"kind": "each",
"items": { "expr": "state", "name": "todos" },
"as": "item",
"body": {
"kind": "element",
"tag": "li",
"children": [
{ "kind": "text", "value": { "expr": "var", "name": "item" } }
]
}
}
]
}
}
Async Data Fetching
{
"version": "1.0",
"state": {
"users": { "type": "list", "initial": [] },
"loading": { "type": "string", "initial": "idle" },
"error": { "type": "string", "initial": "" }
},
"actions": [
{
"name": "fetchUsers",
"steps": [
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": "loading" } },
{
"do": "fetch",
"url": { "expr": "lit", "value": "https://api.example.com/users" },
"method": "GET",
"result": "data",
"onSuccess": [
{ "do": "set", "target": "users", "value": { "expr": "var", "name": "data" } },
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": "done" } }
],
"onError": [
{ "do": "set", "target": "error", "value": { "expr": "var", "name": "error" } },
{ "do": "set", "target": "loading", "value": { "expr": "lit", "value": "error" } }
]
}
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": []
}
}
Comparison with React/Next.js
| Aspect | React/TypeScript | Constela |
|---|---|---|
| Language | JavaScript/JSX | JSON DSL |
| Execution Model | Runtime-driven | Compiler-driven |
| Type Safety | TypeScript (erased at runtime) | JSON Schema (compile-time validation) |
| State Updates | Arbitrary functions | Declarative actions |
| Errors | Runtime exceptions | Structured errors (with location info) |
| AI Generation | Unsafe (arbitrary JS possible) | Safe (constrained DSL) |
| Debugging | Inspect closures | Read action steps |
| Markdown/Code | Requires dependencies | Built-in (Shiki support) |
| SSR | Complex (Next.js setup) | First-class support |
| Determinism | Complex (re-rendering issues) | Guaranteed (compiler-validated) |
Built-in Features
Constela includes the following features out of the box:
-
Markdown Rendering:
{ "kind": "markdown", "content": ... } - Syntax Highlighting: Dual-theme support via Shiki
-
File-based Routing:
index.json,users/[id].json,docs/[...slug].json - SSR/SSG: Server-side rendering and hydration
- Data Loading: Fetch data from files, APIs, and MDX at build time
Getting Started
# Create project
mkdir my-app && cd my-app
npm init -y
npm install @constela/start
mkdir -p src/routes
# After creating a page at src/routes/index.json...
# Start dev server
npx constela dev
# Production build
npx constela build
Conclusion
Constela is a new approach that treats UI as "data" rather than "programs."
- Compile-time Validation: Discover errors before execution
- Declarative State Management: Predictable and traceable
- AI Affinity: Safe UI generation with constrained DSL
- Simple Mental Model: Write JSON, compile, it works
This project has just started, and we've finally reached the point where the official Constela website can be built with Constela instead of React/Next.js. We would appreciate your feedback for improvement.
Official Site: https://constela-dev.vercel.app/
Repository: https://github.com/yuuichieguchi/constela
Top comments (0)