@tela-lang/tela
v0.3.0
Published
A declarative UI language that compiles to plain JS + CSS. Reactive components, scoped styles, zero runtime overhead.
Maintainers
Readme
✨ Features
- Single File Components — state, logic, view, and styles live in one
.telafile - Reactive by default — Proxy-based state triggers virtual DOM diffing and targeted DOM updates
- Scoped CSS — styles compile to unique class names with zero bleed between components
- Tiny runtime — ~10KB, zero dependencies, ships as a UMD bundle
- Backend-agnostic — works with Spring Boot, Express, Flask, or plain static files
- Global store —
storekeyword declares shared reactive state; the compiler auto-subscribes components that reference it - Live-reload dev server —
tela devcompiles, serves, and live-reloads on every.telasave - Zero config — no webpack, no bundler; just compile and drop a
<script>tag
🚀 Quick Start
npx @tela-lang/tela init my-app
cd my-app
npm install
npx @tela-lang/tela compile-all components/ --global
open index.htmlThat's it. No config files. No bundler setup. Open the file in a browser and you're live.
📝 Your first component
Counter.tela
component Counter {
state count: Number = 0
function increment() {
count = count + 1
}
function decrement() {
count = count - 1
}
view {
div {
style {
display: "flex"
flex-direction: "column"
align-items: "center"
padding: 32px
gap: 16px
border-radius: 12px
background: "#ffffff"
box-shadow: "0 4px 24px rgba(0,0,0,0.08)"
}
h1 {
content: "Count: ${count}"
style { font-size: 48px color: "#1a1a2e" }
}
div {
style { display: "flex" gap: 12px }
button {
content: "−"
@click: decrement
style { padding: "12px 24px" font-size: 24px cursor: "pointer" border-radius: 8px background: "#ef4444" color: "#fff" border: "none" }
}
button {
content: "+"
@click: increment
style { padding: "12px 24px" font-size: 24px cursor: "pointer" border-radius: 8px background: "#22c55e" color: "#fff" border: "none" }
}
}
}
}
}Compile and include:
npx @tela-lang/tela compile Counter.tela --global<link rel="stylesheet" href="Counter.css">
<script src="https://unpkg.com/@tela-lang/tela@latest/dist/runtime.umd.js"></script>
<script src="Counter.js"></script>
<script>Tela.render(Counter, document.getElementById('app'));</script>📦 Installation
Global install — use tela anywhere:
npm install -g @tela-lang/tela
tela compile MyApp.telaProject devDependency — pin to a version:
npm install --save-dev @tela-lang/tela
npx tela compile MyApp.telaNo install — run directly with npx:
npx @tela-lang/tela compile MyApp.tela🔧 CLI Reference
| Command | Description |
|---|---|
| tela init [name] | Scaffold a new project with a starter component |
| tela compile <file.tela> | Compile a single component to .js + .css |
| tela compile-all <dir> | Compile every .tela file in a directory |
| tela dev [dir] | Start a live-reload dev server; watches .tela files and recompiles on every save |
Options
| Flag | Description |
|---|---|
| --global | Expose the component on window (required for plain <script> use) |
| --port <n> | Port for tela dev (default: 3000) |
| --root <dir> | Directory to serve with tela dev (default: current directory) |
🌐 Runtime CDN
unpkg:
<script src="https://unpkg.com/@tela-lang/tela@latest/dist/runtime.umd.js"></script>jsDelivr:
<script src="https://cdn.jsdelivr.net/npm/@tela-lang/tela@latest/dist/runtime.umd.js"></script>Mount a compiled component onto any DOM node:
Tela.render(MyComponent, document.getElementById('app'));🏗️ Language Overview
import Header from "./Header.tela"
component UserDashboard {
// ── State ────────────────────────────────────────────
state users: Array = []
state loading: Boolean = false
state query: String = ""
// ── Props ────────────────────────────────────────────
prop title: String
// ── Lifecycle ────────────────────────────────────────
onMount {
fetchUsers()
}
// ── Functions ────────────────────────────────────────
async function fetchUsers() {
loading = true
response = await fetch("/api/users")
users = await response.json()
loading = false
}
function deleteUser(id) {
fetchUsers()
}
// ── View ─────────────────────────────────────────────
view {
div {
// Render a child component
Header { title: title }
// Two-way binding on an input
input { bind value: query placeholder: "Search..." }
if (loading) {
p { content: "Loading..." }
} else {
div {
for (user in users) {
div {
style { display: "flex" justify-content: "space-between" padding: 8px }
span { content: "${user.name}" }
button {
content: "Delete"
@click: deleteUser(user.id)
}
}
}
}
}
}
}
}Core syntax at a glance
| Syntax | Purpose |
|---|---|
| state name: Type = value | Reactive local state |
| prop name: Type | Input from parent |
| computed name = expr | Derived value, auto-updates |
| route path: String | Reactive URL variable for client-side routing; add a second route params: Object to capture named URL segments (e.g. /users/:id → params.id) |
| store Name { field: Type = value } | Shared reactive state across components |
| enum Name { A B C } | Compile-time constant set |
| model Name { field: Type } | Data-shape factory |
| onMount / onUpdate / onDestroy { } | Lifecycle hooks |
| async function name() { } | Async function with await |
| emit eventName(value) | Fire a custom event to the parent |
| bind value: stateVar | Two-way input binding |
| @click: fn | Event handler |
| if (cond) { } else { } | Conditional rendering |
| for (item in list) { } | List rendering |
| switch (expr) { case v: ... } | Switch/case in logic and view |
| while (cond) { } | While loop |
| try { } catch (e) { } | Error handling |
| content: "Hello ${name}" | Text with interpolation |
| style { prop: value } | Scoped styles (static → CSS, dynamic → inline) |
Full language reference: SPEC.md · Docs site
State vs Store
Tela has two reactive primitives. Knowing which to use is the most important decision in any Tela component.
state — private to one component, lives and dies with it.
component Toggle {
state open: Boolean = false // only Toggle cares about this
view {
button { content: "Toggle" @click: open = !open }
if (open) { p { content: "Visible" } }
}
}store — shared across all components that reference it, persists for the app's lifetime.
store AuthStore {
user: Object = null
}
component NavBar {
view {
span { content: "Hello, ${AuthStore.user?.name}" } // re-renders on user change
}
}
component ProfilePage {
view {
p { content: "${AuthStore.user?.email}" } // same store, same update
}
}Rule of thumb:
- UI-only data (loading flag, open/close, form input) →
state - Data two or more components share (current user, cart, settings) →
store
See State and Stores in SPEC.md for the full reference.
☕ Spring Boot Integration
Tela integrates cleanly with any JVM backend. The compiler runs as a Maven plugin step during generate-resources — no extra tooling required.
pom.xml — auto-compile before the build:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>compile-tela</id>
<phase>generate-resources</phase>
<goals><goal>exec</goal></goals>
<configuration>
<executable>npx</executable>
<arguments>
<argument>@tela-lang/tela</argument>
<argument>compile-all</argument>
<argument>${project.basedir}/src/main/resources/static/components</argument>
<argument>--global</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>index.html (Thymeleaf template):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/components/App.css">
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/@tela-lang/tela@latest/dist/runtime.umd.js"></script>
<script src="/components/App.js"></script>
<script>Tela.render(App, document.getElementById('app'));</script>
</body>
</html>Run mvn spring-boot:run and Maven will compile all .tela files automatically before starting the server.
🌍 Live Demo
Spring PetClinic → Tela — a fully functional CRUD app with Owners, Pets, Vets, and Visits, built with Tela + Spring Boot + H2. Clone it, run mvn spring-boot:run, and see a complete multi-component Tela app in production form.
🏗 Architecture
Tela uses a Virtual DOM with component-level re-rendering. Here is the full picture:
| Layer | What happens |
|-------|-------------|
| Compiler | .tela source → ComponentDef object (plain JS + CSS). No runtime template evaluation. |
| Reactive state | Tela.reactive() wraps component state in a Proxy. Any state write immediately calls instance.update(). |
| Virtual DOM | Each update() call re-runs the component's render function, producing a new vnode tree. |
| Diffing | _patchChildren / _patchNode diff old vs new vnodes, touching only the DOM nodes that actually changed. |
| Keyed lists | Children with a key: attribute use map-based reconciliation — DOM nodes are reused by key, not by index. |
| Global store | Tela.store() is a named, shared Proxy. Writes batch subscriber notifications via a microtask, so multiple store mutations in one tick produce a single re-render pass across all subscribing components. |
| No-op guard | Both reactive() and store() skip re-renders when the new value === the old value. |
Granularity: re-renders are per-component, not per-property. When any piece of state inside a component changes, that component's subtree is re-diffed. Child components are reused by identity (cursor-based); only props differences are patched through.
This is the same model as React/Preact/Vue, deliberately chosen for predictability and debuggability over more aggressive compile-time optimisations.
🤝 Contributing
git clone https://github.com/tela-lang/tela.git
cd tela
npm install
npm testBug reports, feature requests, and pull requests are all welcome. Open an issue to discuss larger changes before submitting a PR.
🙏 Acknowledgements
Tela draws inspiration from the best ideas in modern frontend tooling:
- Svelte — the "compile away the framework" philosophy
- Vue.js — single-file components and template syntax design
- React — virtual DOM diffing and component model
- Angular — declarative template syntax and two-way binding
The Spring PetClinic demo application (© Spring team, Apache 2.0) inspired the example app in examples/petclinic-tela/.
📄 License
MIT © tela-lang
