leaf-lang

write html with braces instead of angle brackets. leafc compiles .leaf files to .html.

install

requires node.js 14+

npm install -g leaf-lang

check it worked:

leafc --version

quick start

create index.leaf:

leaf {
  head {
    title "my page"
    meta charset="UTF-8"
  }
  body {
    h1 "hello world"
    p "my first leaf page"
    a href="https://github.com" target="_blank" "github"
  }
}

compile it:

$ leafc index.leaf
ok  index.leaf -> index.html  1.2 KB

open index.html in your browser.

index.leaf
leaf {
  body {
    h1 "hello"
    p "world"
  }
}
index.html
<!DOCTYPE html>
<html>
  <body>
    <h1>hello</h1>
    <p>world</p>
  </body>
</html>

commands

commandwhat it does
leafc file.leafcompile to file.html (same folder)
leafc file.leaf -o out.htmlcompile to a specific path
leafc --watch file.leafwatch and recompile on save
leafc file.leaf --stdoutprint html to terminal
leafc --versionprint version
leafc --helpshow help

watch mode

recompiles every time you save the file. press Ctrl+C to stop.

$ leafc --watch index.leaf
ok  index.leaf -> index.html  1.2 KB
watching index.leaf

structure

leaf is the root — it becomes <html>. nest tags with { }.

leaf {
  head {
    title "page title"
  }
  body {
    div class="container" {
      h1 "heading"
      p "paragraph"
    }
  }
}

text

put text in quotes after the tag name.

h1 "page title"
p "paragraph text"
span "inline"
strong "bold"
em "italic"

attributes

write attributes between the tag name and the text content.

div class="box" id="main" { }

a href="https://example.com" target="_blank" "click"

p style="color:red" "red text"

boolean attributes (no value):

input type="checkbox" checked
video controls autoplay

void tags

self-closing — no braces needed.

brhrimginputmetalinksourcetrackwbrareabasecolembedparam
img src="photo.jpg" alt="photo"
meta charset="UTF-8"
meta name="viewport" content="width=device-width,initial-scale=1"
br
hr
input type="text" placeholder="name"

comments

start a line with #. stripped from output.

# this is a comment
div {
  # p "this won't render"
  p "this will"
}

style & css

use a style tag in head:

head {
  style "
    body { font-family: sans-serif; padding: 2rem; }
    h1   { color: #1D9E75; }
  "
}

inline with style=:

div style="background:#1D9E75;padding:2rem;border-radius:8px" {
  p style="color:#fff" "green box"
}

external stylesheet:

link rel="stylesheet" href="style.css"

images

img src="photo.jpg" alt="a photo"

img src="photo.jpg" alt="photo" style="width:100%;border-radius:8px"

# image as link
a href="https://example.com" {
  img src="banner.jpg" alt="banner"
}

# with caption
figure {
  img src="photo.jpg" alt="photo"
  figcaption "caption goes here"
}

forms

form action="/submit" method="post" {
  label "name"
  input type="text" name="name" placeholder="your name"

  label "pick one"
  select name="choice" {
    option "option a"
    option "option b"
  }

  label "message"
  textarea name="msg" rows="4"

  button type="submit" "send"
}

tables

table {
  thead {
    tr { th "name"  th "age" }
  }
  tbody {
    tr { td "ahmad"  td "20" }
  }
}

node.js api

const { compile, parse } = require("leaf-lang");

// compile to html string
const html = compile(`
  leaf {
    head { title "test" }
    body { h1 "hello" }
  }
`);

// parse to ast (no render)
const ast = parse('div { p "hi" }');
console.log(ast.nodes);
compile() throws if the source is empty or not a string.

all tags

document

leafheadbodytitlemetalinkstylescript

layout

divspannavheaderfootermainsectionarticleaside

text

h1h2h3h4h5h6pstrongemcodepreblockquotebrhr

media

imgvideoaudiosourcefigurefigcaptioniframecanvas

links & lists

aulollidldtdd

forms

forminputtextareaselectoptionlabelbuttonfieldsetlegendprogress

table

tabletheadtbodytfoottrthtd

other

detailssummarydialogmarkdelinssubsup