Skip to content

Plain text and interpolation

Any line whose first non-space character is not a HAML sigil is plain text. Plain text is emitted as-is at the current indent level.

1
2
3
%div
  Hello world
  Another line

renders to:

1
2
3
4
<div>
  Hello world
  Another line
</div>

Backslash escape

To produce text that begins with a HAML sigil, prefix the line with \. The backslash is stripped from the output.

1
2
3
\%foo
\.bar
\#hash

renders to:

1
2
3
%foo
.bar
#hash

Interpolation

#{ ... } evaluates a Raku expression, HTML-escapes the result, and emits it.

1
%p Hello, #{ $name }!

with :locals(%(name => 'world')) renders to:

1
<p>Hello, world!</p>

!{ ... } evaluates and emits the result without HTML-escaping. Use it when the value is already known-safe HTML.

1
%p !{ $raw }

with :locals(%(raw => '<b>bold</b>')) renders to:

1
<p><b>bold</b></p>

To produce a literal #{...} or !{...}, escape the leading sigil with a backslash:

1
%p \#{ literal }

renders to:

1
<p>#{ literal }</p>

Interpolation is recognized in tag content, plain text lines, and inside double-quoted attribute strings:

1
%a{ href: "/users/#{ $id }" } Profile

with :locals(%(id => 7)) renders to:

1
<a href='/users/7'>Profile</a>

Single-quoted attribute strings are literal — no interpolation is performed:

1
%a{ title: 'Hello #{name}' } go

renders to:

1
<a title='Hello #{name}'>go</a>

Escaping rules

Inside #{ ... }, the value is HTML-escaped before emission, so user input cannot inject markup:

1
%p #{ $tag }

with :locals(%(tag => '<b>')) renders to:

1
<p>&lt;b&gt;</p>

Inside !{ ... }, the value is emitted raw. Inside attribute values, the final attribute value is escaped once after interpolation, so #{} and !{} produce equivalent output for attributes.

Multi-line content

The legacy | line-continuation operator from Ruby HAML 3 is not supported. Wrap multi-line content in a tag or use embedded Raku for formatting:

1
2
3
%p
  First line
  Second line