DEV Community

Yuuichi Eguchi
Yuuichi Eguchi

Posted on

Constela - Building UI Without Writing JavaScript: The JSON DSL Alternative

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
}
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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 } }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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": "+" } }
            ]
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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" } }
          ]
        }
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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": []
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)