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.

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)