Files
pugz/docs/syntax.md
Ankit Patial aaf6a1af2d fix: add scoped error logging for lexer/parser errors
- Add std.log.scoped(.pugz) to template.zig and view_engine.zig
- Log detailed error info (code, line, column, message) when parsing fails
- Log template path context in ViewEngine on parse errors
- Remove debug print from lexer, use proper scoped logging instead
- Move benchmarks, docs, examples, playground, tests out of src/ to project root
- Update build.zig and documentation paths accordingly
- Bump version to 0.3.1
2026-01-25 17:10:02 +05:30

3.9 KiB

Pugz Template Syntax

Complete reference for Pugz template syntax.

Tags & Nesting

Indentation defines nesting. Default tag is div.

div
  h1 Title
  p Paragraph

Output:

<div><h1>Title</h1><p>Paragraph</p></div>

Classes & IDs

Shorthand syntax using . for classes and # for IDs.

div#main.container.active
.box
#sidebar

Output:

<div id="main" class="container active"></div>
<div class="box"></div>
<div id="sidebar"></div>

Attributes

a(href="/link" target="_blank") Click
input(type="checkbox" checked)
button(disabled=false)
button(disabled=true)

Output:

<a href="/link" target="_blank">Click</a>
<input type="checkbox" checked="checked" />
<button></button>
<button disabled="disabled"></button>

Boolean attributes: false omits the attribute, true renders attr="attr".

Text Content

Inline text

p Hello World

Piped text

p
  | Line one
  | Line two

Block text (dot syntax)

script.
  console.log('hello');
  console.log('world');

Literal HTML

<p>Passed through as-is</p>

Interpolation

Escaped (safe)

p Hello #{name}
p= variable

Unescaped (raw HTML)

p Hello !{rawHtml}
p!= rawVariable

Tag interpolation

p This is #[em emphasized] text
p Click #[a(href="/") here] to continue

Conditionals

if / else if / else

if condition
  p Yes
else if other
  p Maybe
else
  p No

unless

unless loggedIn
  p Please login

String comparison

if status == "active"
  p Active

Iteration

each

each item in items
  li= item

with index

each val, index in list
  li #{index}: #{val}

with else (empty collection)

each item in items
  li= item
else
  li No items

Objects

each val, key in object
  p #{key}: #{val}

Nested iteration

each friend in friends
  li #{friend.name}
  each tag in friend.tags
    span= tag

Case / When

case status
  when "active"
    p Active
  when "pending"
    p Pending
  default
    p Unknown

Mixins

Basic mixin

mixin button(text)
  button= text

+button("Click me")

Default parameters

mixin button(text, type="primary")
  button(class="btn btn-" + type)= text

+button("Click me")
+button("Submit", "success")

Block content

mixin card(title)
  .card
    h3= title
    block

+card("My Card")
  p Card content here

Rest arguments

mixin list(id, ...items)
  ul(id=id)
    each item in items
      li= item

+list("mylist", "a", "b", "c")

Attributes pass-through

mixin link(href, text)
  a(href=href)&attributes(attributes)= text

+link("/home", "Home")(class="nav-link" data-id="1")

Template Inheritance

Base layout (layout.pug)

doctype html
html
  head
    title= title
    block styles
  body
    block content
    block scripts

Child template

extends layout.pug

block content
  h1 Page Title
  p Page content

Block modes

block append scripts
  script(src="extra.js")

block prepend styles
  link(rel="stylesheet" href="extra.css")

Includes

include header.pug
include partials/footer.pug

Comments

HTML comment (rendered)

// This renders as HTML comment

Output:

<!-- This renders as HTML comment -->

Silent comment (not rendered)

//- This is a silent comment

Block Expansion

Colon for inline nesting:

a: img(src="logo.png")

Output:

<a><img src="logo.png" /></a>

Self-Closing Tags

Explicit self-closing with /:

foo/

Output:

<foo />

Void elements (br, hr, img, input, meta, link, etc.) are automatically self-closing.

Doctype

doctype html

Output:

<!DOCTYPE html>