Guide
The language
Values, reactive state, functions, conditions, loops, errors, imports and raw JavaScript — the core of Kaury.
Kaury is a complete language, not a template syntax: it has variables, functions, loops, errors, modules, and it can run programs without any page (kaury run). It compiles to plain JavaScript.
Blocks and comments
A block is a line followed by lines indented by 2 spaces. No braces, no semicolons.
// a comment
if score > 10
print "well done"
Values
let tax = 8.1 // a fixed value: never changes
state count = 0 // a reactive state: the page follows it
let total = price * quantity // reads a state → recomputed by itself
delivery = 7 // assignment (creates the variable if needed)
count += 1
Texts are always between double quotes, with {…} interpolation:
let name = "Ada"
print "Hello {name}, you have {count * 2} points"
Numbers can carry a unit: 24px, 2s, 300ms, 50%, 100vh, 90deg, 20/s. Other values: true, false, none, lists [1, 2], objects { name: "Tulip", price: 30 }, ranges 1..5.
Names may contain hyphens: total-price. Write subtraction with spaces: a - b.
Functions
function double x then x * 2 // one line
function total-price cart // a block: the last line is the result
sum cart, a -> a.price * a.quantity
Call with or without parentheses: double(4), print total, sum cart, a -> a.price.
Anonymous functions: x -> x * 2, (a, b) -> a + b, -> count += 1.
Conditions
if total > 50
delivery = 0
else if total > 20
delivery = 4
else
delivery = 7
let label = if age >= 18 then "adult" else "child"
Comparisons: == != < > <= >=. Logic: and or not. Membership: x in list.
Loops
for p in products
print p.name
for p, i in products
print "{i}: {p.name}"
for i in 1..10
print i
while lives > 0
lives -= 1
break, continue and return value work as expected.
Lists, texts and objects
let cheap = products.filter(p -> p.price < 20)
let names = products.map(p -> p.name).join(", ")
let sorted = products.sort(p -> p.price)
cart.add product
cart.remove product
Other methods: .find, .contains, .count, .unique(), .take n, .sum fn, .upper(), .lower(), .replace a, b, .split sep, .trim(), .starts-with x… Properties: .length, .first, .last, .keys, .values.
Built-in functions
print, load url, send url, data, sum, average, min, max, round x, 2, random 1, 6, pick list, shuffle, range a, b, now(), format-date d, price 12.5, every 2s, -> …, later 1s, -> …, persist "key", state, copy text, confetti(), vibrate, scroll-to "id", share {…}, json-ld {…}, slug "Hello world", await 2s. See the reference.
Asynchronous code and errors
try
products = await load "/api/products"
catch e
print e.message
In a page, await load never blocks the display: the list fills in when the data arrives.
Modules
In card.kaury, export makes a component (or a function) usable by other files:
export component Card name
card name
Raw JavaScript
When you really need it, a js block is copied as is:
js
console.log(navigator.userAgent)
French keywords
Every keyword has a French alias, accents optional: soit (let), état (state), si (if), pour (for), titre (title)… Errors follow the language of your system.
Errors that explain
site.kaury:4:10 — "conut" does not exist. Did you mean "count"?
4 | button "+1" -> conut += 1
^^^^^
Every error has the line, the underlined word and a fix. kaury check --json gives the same errors to editors and AI assistants.
Nothing passes in silence:
- Habits from other languages get the Kaury form.
font-size 20answersin Kaury: size 20;h1 "Title"answersin Kaury: title "…";&&answers"&&" is written "and". The same goes fordiv,onClick,=>,===,var,elif, a:after an option, a forgotten comma. - Values are checked. An option that takes a number refuses a word (
size large), an option that takes a color refuses what is not one (color FF0000), a function called with too few or too many values says which ones it takes. - The pages are rendered.
kaury checkandkaury builddisplay every page: what only fails at that moment (calling something on an empty value) is reported at its.kauryline, and the build stops instead of publishing a page with a hole in it.
Empty values
none is the empty value. Reading a property of an empty value gives nothing: user.name shows nothing while user is none, and the page fills in when the value arrives. Calling something on an empty value is a mistake, said with its line: test first.
state user = none
page "/"
title "Hello {user.name}"
if user
text user.name.upper()
button "Log in" -> user = { name: "Ada" }