A small language for useful programs.
Reflex uses readable indentation, direct assignments, arrays and template strings. The installed 0.1 alpha is a small, standalone native command-line language: it runs ordinary core .rflx programs without Python, Node, Rust, Cargo, or a source checkout.
Examples tagged Native alpha run with the released standalone CLI. Examples tagged Reference runtime are implemented by the earlier Python development runtime and are not yet part of the native release. This distinction is deliberate so the docs never promise features an installed alpha cannot provide.
Use it now Native alpha
Scripts, functions, lists, strings, loops, conditions, collection helpers, and the reflex CLI.
Growing paths Reference runtime
Local web apps, HTML/CSS composition, WebView desktop output, and JSON worker bridges remain on the migration path.
Install once. Then run your code.
reflex run hello.rflx is not an installation step. The installer is the installation step: it places the reflex command on your system. run simply tells that already-installed command to execute the file you wrote. It is the normal everyday workflow—like opening a document after installing the app that reads it.
# hello.rflx
greeting = "Hello"
name = "World"
say(person) => `${greeting}, ${person}!`
print(say(name))Save the file as hello.rflx. In the folder that contains it, use:
reflex run hello.rflxYou should see Hello, World!. You can omit run for a short form: reflex hello.rflx.
Normal users install the package once and use reflex run your-file.rflx. The Linux source and macOS package-builder pages exist for contributors and maintainers, not for ordinary use.
Command line
| Command | Purpose | Example |
|---|---|---|
reflex run <file> | Parse and execute a Reflex program. | reflex run hello.rflx |
reflex <file> | Short form of run. | reflex hello.rflx |
reflex check <file> | Validate syntax without executing the program. | reflex check hello.rflx |
reflex --version | Show the installed release version. | reflex --version |
reflex --help | Show the supported commands. | reflex --help |
The CLI reads ordinary UTF-8 text files. The .rflx extension is the project convention and helps editor support recognize the language.
From file to result.
The native CLI follows a deliberately plain model. It reads one UTF-8 .rflx file, turns it into a program, and evaluates that program from top to bottom. A name exists after its assignment or function definition has run. For the clearest code, define helpers before the first place that calls them.
| Command | What Reflex does | When to use it |
|---|---|---|
reflex check app.rflx | Lexes and parses the file. It reports syntax, delimiter, and indentation problems but does not execute print, loops, or functions. | Every time you have edited structure or are unsure whether an error is syntax. |
reflex run app.rflx | Parses, then evaluates the statements. Output from print goes to the terminal. | When the file checks and you want its real behavior. |
reflex app.rflx | Uses the same run behavior in a shorter form. | When you prefer the compact command. |
On success, the CLI exits normally. A read, syntax, or runtime problem is printed as a Reflex error and returns a failure status; malformed CLI usage returns a usage failure. This makes it practical to use check in an editor task or a small automated script later.
It does not download packages, start a web server, compile Rust, invoke Python, or make network calls merely because you run a core program. A normal core run is just the Reflex file you asked it to evaluate.
Syntax and values
Statements live one per line. Blocks use a trailing : and spaces on the next line. Use spaces consistently—tabs are rejected for indentation. Both # and // start a comment.
| Kind | Write it as | Notes |
|---|---|---|
| String | "hello", 'hello' | Use quotes for ordinary text. |
| Template string | `Hello, ${name}!` | Backticks interpolate expressions. |
| Number | 42, 3.14 | Numbers are used for arithmetic and ranges. |
| Boolean | true, false | True and False are accepted too. |
| Empty value | null | None is also accepted. |
| List | ["Ada", "Lin"] | Ordered, mutable collection. |
Use parentheses for grouping. A template can hold expressions such as `Total: ${price * quantity}`.
Precise behavior worth knowing.
Order, equality, and truth
Expressions use familiar precedence: grouping first, then unary not/-/+, multiplication/division/remainder, addition/subtraction, comparisons, equality, then and and or. Use parentheses when an expression deserves a second look.
price = 12
quantity = 3
discount = 5
subtotal = price * quantity - discount
eligible = subtotal >= 30 and quantity > 0
print(`subtotal: ${subtotal}`)
print(`eligible: ${eligible}`)== compares values. String and list membership is written with in: "re" in "Reflex" and "Ada" in names. Use .includes() when the receiver-first form reads better.
Truthiness in branches
false, null, 0, an empty string, and an empty list are false in a condition. Non-empty strings/lists and non-zero numbers are true. Be explicit when the distinction matters: if name != "": documents the intent more clearly than relying on truthiness alone.
Mutation is visible
push(...items) changes a list and returns its new length. pop() changes a list and returns the last item, or null when the list is empty. By contrast, map, filter, split, upper, lower, trim, and replace produce a result you should assign or pass on.
tasks = ["write"]
tasks.push("check")
last = tasks.pop()
loud = "reflex".upper()
print(tasks.join(", "))
print(last)
print(loud)The native 0.1 core has no indexing syntax such as items[0], no object literals, imports, classes, exception handling, package installer, or debugger command yet. For an initial/last list value use .first()/.last(); handle missing values deliberately. These are roadmap items, not hidden syntax.
Variables and constants
A first assignment creates a variable; later assignment updates it. let is allowed when you prefer an explicit declaration. Use const for a binding that must not be reassigned.
name = "Maya"
score = 10
score += 5
let city = "Regina"
const language = "Reflex"
print(`${name}: ${score}`)Compound updates are available for +=, -=, *=, and /=. A const rejects reassignment; use a normal binding when the value is supposed to change.
Functions and scope
For a small expression, use an arrow function. For more than one action, use a named block function with return. Function parameters may have defaults. The legacy fun name(...) spelling still works; fun(x) => ... is useful for a short callback.
# Expression function
square(number) => number * number
# Block function with a default parameter
greet(name, punctuation = "!"):
return `Hi, ${name}${punctuation}`
print(square(6))
print(greet("Ada"))Functions close over the values visible where they are created. Blocks create their own local scope; declare a value outside an if or loop if you need it after that block.
Conditions and loops
Use if, elif, and else for branches. Use for item in items: to visit a list or a string, and while for a condition-controlled loop. break stops a loop; continue skips to the next turn.
names = ["Ada", "Lin", "Maya"]
for name in names:
if name.includes("a") or name.includes("A"):
print(`${name} has an a`)
else:
print(`${name} does not have an a`)
count = 0
while count < 3:
count += 1Empty strings, empty lists, 0, false, and null behave as false in a condition. Other values behave as true.
Lists and strings
Lists carry useful methods. Most methods return a new value; push and pop change the list. String methods return strings or lists as appropriate.
numbers = range(1, 8)
squares = numbers.map(fun(n) => n * n)
even = squares.filter(fun(n) => n % 2 == 0)
print(even.join(", "))
print(even.first())
print(even.length)| Receiver | Available members |
|---|---|
list | .length, .map(fn), .filter(fn), .join(separator), .push(...items), .pop(), .includes(value), .first(), .last() |
string | .length, .upper(), .lower(), .trim(), .split(separator?), .includes(text), .replace(from, to) |
join() uses a comma by default. split() without an argument splits whitespace. Calling a member on the wrong kind of value produces a useful runtime error.
Built-ins and operators
| Built-in | What it does |
|---|---|
print(...values) | Writes values to standard output, separated by spaces. |
range(end) | Creates 0 through end - 1. |
range(start, end, step?) | Creates a numeric list; the end is excluded and the step cannot be zero. |
len(value) | Counts characters in a string or elements in a list. |
str(value) | Turns a value into display text. |
number(value) | Turns a numeric string or a number into a number. |
Arithmetic: +, -, *, /, %. Comparisons: ==, !=, <, <=, >, >=. Logic: and, or, not. Membership: item in list and text in string.
Web apps, HTML, and CSS
The original development runtime can create a local HTTP app, register routes, return HTML or JSON, and include CSS inline or from a nearby file. These APIs are documented for exploration and migration planning; the installed native 0.1 CLI does not yet include web(), web serving, or object/JSON feature parity.
# Python reference runtime only — not in the standalone 0.1 CLI
app = web()
app.style("body { font: 18px system-ui; padding: 2rem; }")
home(request):
return page("Hello", `<h1>Hello from Reflex</h1>`)
app.route("/", home)
app.start(3000)| Reference API | Purpose |
|---|---|
web() | Create the application. |
app.route(path, handler), app.get, app.post | Register a route. |
app.use(handler) | Run middleware before route matching. |
page(title, html), json(value, status?) | Build an HTML or JSON response. |
app.style(css), app.style_file(path) | Add inline or external CSS. |
app.export(folder), app.start(port?) | Export static output or start local development. |
Web request handling in more detail.
In the Python reference runtime, a route handler receives a request object. The documented fields are request.path, request.method, request.query, and—on POST requests—request.body. A handler returns page(...) for HTML or json(...) for an API response. Middleware registered with app.use(...) runs before route matching and can enrich a request or return a JSON response early.
app = web()
log(request):
print(`${request.method} ${request.path}`)
app.use(log)
status(request):
return json({ ok: true, language: "Reflex" })
app.get("/api/status", status)
app.start(3000)Inline CSS belongs in app.style("""..."""); a separate file belongs in app.style_file("site.css"). The supplied CSS is included when page(title, html) creates the document. For static output, app.export(folder) generates HTML for registered routes. These APIs are useful reference material, but none should be pasted into a native-alpha program yet.
Desktop apps
The reference runtime has a WebView-oriented desktop generator: native(title), desktop.html(...), desktop.style(...), desktop.window(width, height), and desktop.build(folder). It writes an HTML/CSS page and a pywebview-based launcher. It is not a fully compiled native UI backend, and it is not part of the standalone alpha release yet.
Reflex can be a pleasant place to author HTML and CSS for desktop-like interfaces today in the reference runtime. Native standalone desktop packaging is later work, not an advertised 0.1 CLI capability.
Connect to other languages
The reference runtime’s connect(language, target) starts a trusted local worker and exchanges one JSON message on standard input/output for each call. Built-in launch names include Python, JavaScript/Node, Lua, C/C++ native executables, C#/.NET, Ruby, PHP, R, and an explicit command option for other runtimes.
# Python reference runtime only
math = connect("python", "bridges/python_math.py")
answer = math.call("add", 20, 22)
print(answer)A worker receives {"function":"add","args":[2,3]} and prints either {"result":5} or {"error":"explanation"}. A bridge executes a real local process: only connect to code you trust and never turn untrusted web input into a bridge target.
Bridge protocol, failure modes, and safety.
A Reflex bridge is intentionally a small JSON protocol over standard input and output. It does not make another language magically part of the native 0.1 runtime. The reference runtime starts the configured local worker, sends a JSON request, and expects exactly one JSON response.
{"function":"add","args":[20,22]}{"result":42}If the worker cannot perform the request, return a readable error object instead:
{"error":"unknown function: add"}- A worker must not print logs to standard output alongside the JSON response; send diagnostics to standard error instead.
- Use an exact, reviewed file path or command. A bridge executes a process, so never construct its target from untrusted data.
- Test the worker directly with one fixed JSON input before debugging the Reflex side.
- Install the external runtime required by that worker—Node for a JavaScript worker, Lua for Lua, .NET for a managed C# DLL, and so on.
What 0.1 alpha means
The native CLI is intentionally small. Its tested release scope is declarations, implicit bindings, functions, conditionals, loops, expressions, strings/templates, lists, ranges, printing, and the collection methods documented above. It is designed to be easy to install and straightforward to inspect—not to pretend it already has a package registry, deployment system, async runtime, source maps, or every feature of the reference implementation.
Available in the release
A genuine Linux x86_64 package, a native executable inside it, run, check, --version, and the core language.
Not released as native parity
Web, desktop, bridges, modules, package management, LSP, mobile targets, and Windows/macOS installer artifacts are later milestones or platform-specific work.
Write code that stays easy to repair.
Reflex is most comfortable when one small program tells a direct story: name the input, transform it in a function, and print or return a result. Prefer a short named function to clever nested expressions. Keep one indentation level per idea.
# score.rflx
names = ["Ada", "Lin", "Maya"]
format_name(name):
clean = name.trim()
return clean.upper()
for name in names:
print(format_name(name))Name values by their job
Prefer total, names, and clean_name over x, data, and thing.
Keep functions narrow
A function that formats a name should not also print a report and change unrelated state. Small functions are faster to check and debug.
Use templates for messages
`Saved ${count} files` avoids manual conversion and keeps diagnostic output readable.
Check before a run
Use reflex check after reshaping code; use run once the structure is valid.
There is no required project template, configuration file, or build command for a core program. A file named hello.rflx next to the terminal command is a perfectly valid beginning.
Editing, checking, and keeping changes safe.
The included VS Code language support recognizes .rflx, highlights Reflex syntax, provides snippets, and offers run/check commands. Basic syntax definitions also exist for Sublime Text and Vim/Neovim. An editor helps with spelling and indentation, but the CLI remains the source of truth for what the native runtime accepts.
- Set indentation to spaces, not tabs. Four spaces is a readable convention; the important part is being consistent within each block.
- Save the file before running it. A terminal only sees the saved version, not unsaved text in the editor.
- Run
reflex check current-file.rflxafter a structural edit. - Keep a tiny
hello.rflxin a new project so you can distinguish an installation issue from an application issue.
For the standalone alpha, tests can start as small executable .rflx files whose output you know. A built-in unit-test framework and language server are planned; do not assume they are present in 0.1.
Suggested project shape
my-project/
├── hello.rflx
├── app.rflx
├── styles.css # for reference-runtime web/desktop projects
└── bridges/ # trusted reference-runtime workers
└── python_math.pyStart with one .rflx file. Add another file only when the code becomes harder to read. The standalone alpha currently has no module/import system, so keep reusable core code in the program being run; do not document unsupported imports as if they work.
Something not working?
Use the diagnosis guide for syntax errors, runtime errors, broken paths, bridge failures, and a methodical bug report.