Status: Pre-release specification
Llux is a tool for independent creators. It turns interface intent into native software that runs anywhere—without platform lock-in, hidden dependencies, or surprises.
This document specifies the Llux language syntax and file structure. It is organized to match the creator’s workflow: from initial brainstorming through to production-ready implementation.
app.count, session.user, or component.value.state, bind, action, and view..llux carries implementation; .llux.md can reference Llux expressions with @.Llux follows Niklaus Wirth’s principle that language constructs should have single, clear meanings.
| Construct | Single Meaning |
|---|---|
@ |
“This component property is live and reactive” |
= |
“This is static configuration” |
<- |
“Data flows from source to target” |
<-> |
“Data flows both ways” |
-> |
“Data flows from target to source” |
No construct is overloaded. Every symbol has one job.
| Stage | What Happens | Artifact | Format |
|---|---|---|---|
| 1. Brainstorming | Free-form exploration, notes, sketches, intent capture | .intent.md |
Plain Markdown (no Llux syntax required) |
| 2. Ideation | Structured intent, layout decisions, component identification | .llux.md |
Markdown with Llux extensions |
| 3. Implementation | Formal logic, state, actions, and bindings | .llux |
Llux logic syntax |
| 4. Production | Compiled binary, header, and integration artifacts | .so + .h |
Generated by compiler |
| Extension | Purpose | Author | Stage |
|---|---|---|---|
.intent.md |
Brainstorming, free-form notes, sketches, and intent capture | Anyone (designer, founder, product) | 1. Brainstorming |
.llux.md |
Structured intent, layout, semantics, and design decisions | Designer | 2. Ideation |
.llux |
Formal logic, state, actions, and bindings | Designer + Compiler | 3. Implementation |
.lmd |
Short extension for Llux Layout Markdown (equivalent to .llux.md) |
Designer | 2. Ideation |
I will incorporate the frontmatter specification into the language reference, placing it after the File Types table and before the workflow stages, with the clear rule that no properties are required and everything is optional.
All Llux Markdown files (.llux.md, .lmd, .intent.md) MAY include a YAML frontmatter block at the top of the file, delimited by --- lines.
No property is required. A file with no frontmatter is still valid.
The $schema directive is optional. It exists to enable editor validation and autocompletion—not to enforce a rigid structure.
| Property | Type | Description |
|---|---|---|
$schema |
string (URI) | Reference endpoint for machine-validation |
title |
string | Human-readable name of the module/page |
type |
string (enum) | component, layout, view, guide, intent, contract, idea, note |
version |
string | Semantic versioning (^[0-9]+\.[0-9]+\.[0-9]+$) |
author |
string | Creator of the document |
created |
string (ISO 8601) | Creation timestamp |
modified |
string (ISO 8601) | Last modification timestamp |
status |
string (enum) | draft, review, published, archived |
ai_optimized |
boolean | Defaults to true if omitted |
Any other properties are permitted and will not cause validation errors. Tools may use them for their own purposes.
Custom properties for distinct tooling environments SHOULD be prefixed with x- to prevent collisions with future core specification updates.
x-llux-compiler:
target_architecture: "wasm32-unknown-unknown"
# Formal document
---
$schema: https://llux.org/frontmatter.schema.json
title: "Button Component"
type: "component"
version: "1.0.0"
author: "Your Name"
status: "published"
---
# Quick sketch (minimal)
---
title: "Counter App Idea"
type: "idea"
---
# No frontmatter at all (still valid)
# Just Markdown content
The canonical JSON Schema for Llux frontmatter lives at:
https://llux.org/frontmatter.schema.json
It declares all recommended properties, allows additional properties, and requires none. Editors with YAML Language Server support can use this schema for validation and autocompletion when the $schema directive is present.
---
## Stage 1: Brainstorming (`*.intent.md`)
Brainstorming files are **pure Markdown**. They contain no Llux-specific syntax. They are for:
- Free-form notes
- Sketches (ASCII or referenced images)
- User stories
- Feature lists
- Meeting notes
- References to external resources
**Example (`app.intent.md`):**
```markdown
# Counter App
A simple counter app for demonstration purposes.
## User Stories
- As a user, I want to see the current count.
- As a user, I want to increment the count by pressing a button.
- As a user, I want to reset the count.
## Design Notes
- The count should be large and centered.
- The button should be below the count.
- Use a blue button for increment, red for reset.
Importing from Other Formats:
Brainstorming files can be imported from other formats via importers:
llux import ideas.docx --from=docx --to=intent.md
llux import sketches.fig --from=figma --to=intent.md
llux import requirements.pdf --from=pdf --to=intent.md
*.llux.md)Ideation files are Markdown with Llux extensions. They contain:
::: flex, ::: grid, ::: card, etc.)@ references to logic (when ready)All layout containers use the same pattern:
::: [type] [attributes]
content
:::
Syntax Requirements:
::: and the type:::flex (without space) is invalidStandard Types:
| Type | Purpose | Example |
|---|---|---|
flex |
Flex container | ::: flex gap-4 |
grid |
Grid container | ::: grid cols-3 gap-2 |
page |
Page boundary | ::: page new |
chapter |
Chapter marker | ::: chapter introduction |
section |
Section marker | ::: section background |
sidebar |
Sidebar | ::: sidebar width=1/3 |
callout |
Highlighted block | ::: callout type=warning |
card |
Card component | ::: card title="Profile" |
figure |
Figure | ::: figure src="..." |
item |
Child item | ::: item flex-1 |
Attributes:
| Format | Example | Meaning |
|---|---|---|
key=value |
gap-4 |
Utility or key-value attribute |
.class |
.shadow-lg |
CSS class |
#id |
#user-profile |
Element ID (only one) |
"quoted string" |
title="User Profile" |
Attribute value with spaces |
Example:
::: card #user-profile .shadow-lg bg-muted p-4 lang="en"
This is a card with an ID, classes, and attributes.
:::
Llux uses streaming arrows to make dataflow explicit and directional.
| Operator | Role | Data Flow | Example |
|---|---|---|---|
= |
Configuration | N/A | min=0 max=100 |
<- |
Inbound Stream | Source → UI | @text <- app.count |
<-> |
Bi‑directional Sync | Source ↔ UI | @value <-> app.search_query |
-> |
Outbound Stream | UI → Target | @press -> app.increment |
The @ Rule: @ marks a component property as live and reactive. It belongs on the target (the component’s property key), not on the source.
| Syntax | Meaning |
|---|---|
@text |
The text property is a live stream |
@value |
The value property is a live stream |
@press |
The press event is a live stream |
min=0 |
min is static configuration (not live) |
Examples:
# Configuration (static, no dataflow)
::: Slider { min=0 max=100 step=1 }
# Inbound Stream: Label's @text pulls from app.count
::: Label { @text <- app.count }
# Static text + live stream
::: Label { @text <- "Count: " + app.count }
# Bi‑directional Sync: Input's @value syncs with app.search_query
::: Input { @value <-> app.search_query placeholder="Search..." }
# Outbound Stream: Button's @press pushes to app.increment
::: Button { @press -> app.increment label="+1" }
Comments capture intent without affecting rendering:
::: ! This is an intent comment. It will not be rendered.
Metadata can be attached to the parent container:
::: [timing] # Key only (label/definition)
::: [duration](3.5s) # Key-value pair
Llux Markdown is designed to be compatible with Pandoc’s header_attributes extension wherever possible:
| Pandoc Syntax | Llux Syntax | Compatible? |
|---|---|---|
{#identifier .class key=value} |
{@text <- app.count label="+1"} |
✅ Yes |
{#identifier .class key=value} |
{@text : app.count} |
❌ No (colon in key) |
Recommendation: Use key=value format for Pandoc compatibility.
*.llux)Implementation files contain the formal logic of the application:
state definitionsaction definitionsbind expressionsview structuresState is mutable data, explicitly scoped.
app state {
count: int = 0
user: string | null = null
}
session state {
logged_in: bool = false
token: string | null = null
}
component state {
expanded: bool = false
selected: int | null = null
}
State References:
app.count # Application-level state
session.user # Session-level state
component.value # Component-level state
Actions are state-modifying operations.
action Increment(amount: int = 1) {
app.count += amount
}
action Login(username: string, password: string) -> Result {
# Precondition: Must be logged out
requires: session.logged_in == false
# Postcondition: If successful, logged_in == true
ensures: session.logged_in == true if result.success
# Implementation
# ...
}
Action Attributes:
| Attribute | Purpose | Example |
|---|---|---|
requires: |
Precondition | requires: session.user != null |
ensures: |
Postcondition | ensures: count == old.count + amount |
error: |
Error handling | error: count = count |
Derived data or reactive binding.
bind double = app.count * 2
bind is_logged_in = session.user != null
bind greeting = "Hello, " + session.user.name
Visual structure.
view RootView {
Panel {
Label { text: "Count: " + app.count }
Button { press: Increment(1) label: "+1" }
}
}
Components are reusable, self-contained units.
component Button {
# Outlets (action holes)
outlet press: () -> void
# Properties (configuration)
prop label: string = "Button"
prop variant: string = "primary"
prop disabled: bool = false
# State (component-local)
state {
hovered: bool = false
pressed: bool = false
}
# View
view {
# ...
}
}
Component Outlets:
| Component | Outlet | Meaning |
|---|---|---|
Button |
press: () -> void |
When pressed |
Slider |
change: (value: float) -> void |
When value changes |
Toggle |
flip: (state: bool) -> void |
When toggled |
Input |
submit: (text: string) -> void |
When submitted |
TabGroup |
select: (tab: int) -> void |
When tab selected |
Outlets use intent-based names (press, slide, flip, submit, select) rather than developer-centric names (onClick, onChange) to align with designer language.
Services are external capabilities.
service api {
endpoint: "https://api.example.com"
timeout: 30s
method GetUser(id: int) -> User
method SaveUser(user: User) -> Result
}
External modules can be imported:
import "components/button.llux"
import "services/api.llux"
import "design/tokens.llux" as tokens
The contract should include intent documentation for state and actions:
app state {
# The current count displayed to the user
# Initially 0, increases when the user presses the button
count: int min=0 max=100 = 0
}
action Increment(amount: int between 1 and 10 = 1) {
# When the user presses the "+" button, this action is called
# It increases the count by the specified amount
# Precondition: The user must be logged in
# Postcondition: count == old.count + amount
}
The compiler generates:
| Artifact | Purpose | Format |
|---|---|---|
| Binary | Rendered UI + transit logic | .so/.dylib/.dll |
| Header | C API for the developer | .h |
| Manifest | Semantic description of the Contract | JSON |
app.llux.md (Surface)::: page
::: flex gap-4
# Static: Label displays a static message
::: Label { text: "Welcome to the counter app" }
# Inbound: Label's @text pulls from app.count
::: Label { @text <- "Count: " + app.count }
# Configuration: Slider properties
# Sync: Slider's @value syncs with app.count
::: Slider { @value <-> app.count min=0 max=100 step=1 }
# Action: Button's @press pushes to app.increment
::: Button { @press -> app.increment label="+1" }
:::
:::
app.llux (Contract)app state {
# Current count shown to the user
count: int min=0 max=100 = 0
}
action Increment(amount: int between 1 and 10 = 1) {
# Called when the user presses the "+" button
requires: session.user != null
ensures: count == old.count + amount
app.count += amount
}
action Reset() {
# Called when the user presses the "Reset" button
app.count = 0
}
view RootView {
Panel {
Label { text: "Count: " + app.count }
Button { press: Increment(1) label: "+1" }
Button { press: Reset() label: "Reset" }
}
}
| Construct | Syntax | Purpose |
|---|---|---|
| State | app state { count: int = 0 } |
Mutable data |
| Action | action Increment(amount: int = 1) { ... } |
State-modifying operation |
| Bind | bind double = app.count * 2 |
Derived data |
| View | view RootView { ... } |
Visual structure |
| Component | component Button { ... } |
Reusable UI unit |
| Outlet | outlet press: () -> void |
Action hole in a component |
| Prop | prop label: string = "Button" |
Component configuration |
| Layout container | ::: flex gap-4 |
Markdown layout container |
| Inbound stream | @text <- app.count |
Live read‑only stream |
| Bi‑directional sync | @value <-> app.search_query |
Live two‑way sync |
| Outbound stream | @press -> app.increment |
Live output / action |
| Configuration | min=0 max=100 |
Static property |
| Intent comment | ::: ! This is an intent comment |
Non‑rendering comment |
| Metadata key | ::: [timing] |
Metadata on parent |
| Precondition | requires: session.user != null |
Action precondition |
| Postcondition | ensures: count == old.count + amount |
Action postcondition |
New extensions can be added without modifying the core compiler:
| Extension Type | Purpose | Example |
|---|---|---|
| Layout Container | New ::: type |
::: carousel |
| Component | New UI component | ::: Chart |
| Importer | New source format | --from=figma |
| Exporter | New target format | --target=vue |
| Recipe | AI-suggested pattern | MQTT integration |
Changes should be additive wherever possible. If a feature must be deprecated, public docs should mark it clearly and preserve older context outside the launch-facing pages.
| Older term | Current direction |
|---|---|
computed |
Prefer bind. |
command |
Prefer action. |
layout |
Prefer view for .llux; use Layout Markdown for .llux.md. |
resource / effect |
Treat as draft design space until re-specified. |
Llux: Tool for independent creators. Build once. Run anywhere. Own it.