Syntax Reference

NML uses 4-space indentation to express nesting. No closing tags. No angle brackets.

Elements & Attributes

Every line is an element. Attributes chain with dot notation. Text content follows a pipe character.

                                div.class("container").id("main")
                            

Boolean attributes (like disabled, checked) need no value.

Variable Interpolation

Use double curly braces to output context values. Values are HTML-escaped by default. Dot notation accesses nested properties.

                                p | Hello, {{ name }}!
                            

If a variable is not found, the placeholder renders verbatim — no crash, no empty string.

Loops — @each

                                ul
                            
Condition Result
Non-empty array Renders children N times
Empty array [] Renders nothing
Missing key Renders nothing silently

Nested loops work naturally — inner loops can access outer item variables

                                @each(categories as category)
                            

Conditionals — @if

                                @if(user.isAdmin)
                            

NML uses Pythonic truthiness — empty arrays, empty strings, 0, null, and undefined all evaluate to false

Value Truthy?
null / undefined / false ✗ false
0 / "" / [] / {} ✗ false
any non-empty value ✓ true

Filter Pipeline

                                
                                    0.00
                                    {{ html|raw }}
                                    div.x-data("")
                                
                            
Filter Description
uppercase Converts to UPPERCASE
lowercase Converts to lowercase
trim Strips leading/trailing whitespace
json Serialises to JSON (raw, not HTML-escaped)
default("x") Returns "x" if value is falsy
raw Bypasses HTML escaping entirely

Partials — @include

                                doctype.html
                            

Partials inherit the full parent context automatically.

Partials can include other partials. Circular includes throw a parse error.

The readFile option accepts any async function — filesystem, R2, D1, or in-memory.