Slab is a programmable markup language to generate HTML.

This project is still very young. Features demonstrated on this page work as shown but they don't always generalize as expected.

Introduction

Slab is an alternative syntax for writing HTML, plus some programming language features (often found in templating languages, like conditionals and loops). slab is a command-line tool to process .slab files and render them into HTML.

Slab revolves around the concept of fragments. Fragments are pieces of HTML that work like functions. You can define a fragment and then call it:

frag page
  doctype html
  body
    content

page
  h1 A title
  p A paragraph.

The example above defines a fragment named page. It then calls page, passing a block of HTML (containing a heading and a paragraph) as an argument (implicitly called content).

The slab render --pretty command can be used to transform the above code into the following HTML:

<!DOCTYPE HTML>

<body>
    <h1>
        A title
    </h1>
    <p>
        A paragraph.
    </p>
</body>

When not pretty-printed, it looks like this instead:

<!DOCTYPE HTML>
<body><h1>A title</h1><p>A paragraph.</p></body>

Basic syntax

Elements

At its core, Slab is a short-hand notation to represent HTML. An HTML element is written as its tag name, followed by text content, or by other elements. The tree structure is captured by indenting child elements instead of using closing tags. This syntax is identical to the one used in Pug, which inspired Slab.

ul
  li Item
  li Item
  li Item
<ul>
    <li>
        Item
    </li>
    <li>
        Item
    </li>
    <li>
        Item
    </li>
</ul>

Note: we show on the resulting HTML pretty-printed. In a production setting, we would instead generate the following:

<ul><li>Item</li><li>Item</li><li>Item</li></ul>

IDs and classes

To assign an ID to an element, the regular attribute notation can be used (see below). Slab also supports tacking the ID directly to the element name with a # character.

h1#title A title
<h1 id="title">
    A title
</h1>

Classes work similarly and use and the . character for the short notation instead.

h1.hero A title
p.lede.muted Catching summary.
<h1 class="hero">
    A title
</h1>
<p class="lede muted">
    Catching summary.
</p>

An ID or a class can also be used on their own. In that case, a div element is assumed.

.playground
  #app
<div class="playground">
    <div id="app">
    </div>
</div>

Attributes

Attributes (including IDs and classes) can be assigned to an element using a syntax similar to HTML itself, by writing them in parenthesis, directly after the element name.

form(action='/login', method='POST')
<form action="/login" method="POST">
</form>

Comments

Comments are introduced with -- or ---, either with the comment itself on the same line, or as an indented text block on the floowing lines.

-- A regular comment.

--- A comment in the generated HTML.

---
  Another comment
  in the generated
  HTML.

--
  Another
  regular
  comment.
<!-- A comment in the generated HTML. -->
<!-- Another comment
in the generated
HTML. -->

Fragments

Fragments are blocks of HTML that can be named and reused.

Basics

To define a new fragment, the frag keyword is used, followed by the name of the fragment. The body of the fragment is made of the following indented lines. To call a fragment, its name is used like a regular element.

frag card
  h1 Card
  content

card
  frag content
    p A paragraph.
<h1>
    Card
</h1>
<p>
    A paragraph.
</p>

In the above example, the fragment card is defined. In its body, it itself calls another fragment, content. That fragment is passed as an argument when card called. Arguments are given by defining fragments with the corresponding name, indented below the call.

Implicit content argument

To make a call to a fragment look more similar to regular HTML elements, indented lines below the call are passed as an argument called content when an expicit fragment with the same name is not defined. I.e. the following example is equivalent to the example above:

frag card
  h1 Card
  content

card
  p A paragraph.
<h1>
    Card
</h1>
<p>
    A paragraph.
</p>

Default arguments

Fragments can define default contents for their arguments (making them optional) by using the default keyword before calling an argument.

frag card
  h1 Card
  default content
    p Default paragraph.

card
  p Another paragraph.

card
<h1>
    Card
</h1>
<p>
    Another paragraph.
</p>
<h1>
    Card
</h1>
<p>
    Default paragraph.
</p>

Expression arguments

Fragments can also take (non-fragment) arguments. The expressions that can be used in Slab are documented in the Expression language section.

A fragment is defined to take (non-fragments) arguments using curly braces directly after its name, mentioning parameters names. Similarly, when calling a fragments, (non-fragments) arguments are passed between curly braces.

frag hello{name}
  .greeting
    p Hello, #(name).
    content

hello{'Alice'}
  p Some content.
<div class="greeting">
    <p>
        Hello, Alice.
    </p>
    <p>
        Some content.
    </p>
</div>

Expression language

In addition of its lightweight syntax equivalent to HTML, Slab supports some constructs usually found in programming languages.

Variable declarations

Variables are declared using the let keyword. A variable (or a more general expression) can be used as the content of an element by following the element name directly with a = character.

let a = 1
let b = "Alice"
p= a
p= b
<p>
    1
</p>
<p>
    Alice
</p>

A current limitation is that each declaration requires its own let line.

Expressions

Simple values (booleans, integers and string literals) are supported alongside simple operations: addition, substraction, multiplication, and division for integers, and comparison for all types.

let a = 1
p= a + 2 * 3
<p>
    7
</p>

Strings can be concatenated with the + operator. Integers can be converted to strings using the show function. Strings can be "converted" to booleans using the null function.

let a = 1
p= show (a + 2) + '.'
<p>
    3.
</p>

String interpolation

Expressions can be used within the text content of an element using the the #(...) syntax.

let name = "Bob"
p Hello, #(name).
<p>
    Hello, Bob.
</p>

JSON support

Static JSON files can be loaded by using their relative path.

Assuming a file with the following content:

[
  {
    "username": "Alice",
    "email": "alice@example.com"
  },
  {
    "username": "Bob",
    "email": "bob@example.com"
  }
]
let values = ../../data/values.json
ul
  for value in values
    li= value['username']
<ul>
    <li>
        Alice
    </li>
    <li>
        Bob
    </li>
</ul>

Control flow

Slab supports conditionals and loops using if and for blocks.

Conditionals

The else branch is optional.

if true
  p A.
else
  p B.
<p>
    A.
</p>

Loops

It is possible to iterate on lists and objects. In the following example, the index and the key are optional.

ul
  for val, index in [1, 2]
    li val: #(val), index: #(index)

ul
  for val, key in {1: 'one', 2: 'two'}
    li val: #(val), key: #(key)
<ul>
    <li>
        val: 1, index: 0
    </li>
    <li>
        val: 2, index: 1
    </li>
</ul>
<ul>
    <li>
        val: one, key: 1
    </li>
    <li>
        val: two, key: 2
    </li>
</ul>

Modularity

Slab code can be split in multiple files in two ways: include statements and imports.

Includes

An include works by inlining the content of the included file in the including file. When the included file has a .slab extension, it is interpreted as if its code was present in the including file. In particular, any toplevel code that is not let expressions or fragment definitions will render as-if directly written in the including code, and definitions can be used in the including file.

The include syntax allows to specify a filter. Currently the only supported filter is :escape-html. It can be used when you want to show HTML code and also to show Slab code (i.e. disabling its interpretation). This is how the example code on this page is shown.

include basic.slab
<ul>
    <li>
        Item
    </li>
    <li>
        Item
    </li>
    <li>
        Item
    </li>
</ul>
include:escape-html basic.slab
ul
  li Item
  li Item
  li Item

Imports

An import makes it possible to call a Slab file as if it was a fragment. Its body is the content of the file. As with fragments, it is possible to pass fragments as arguments.

Assuming a file called aux-imported.slab with the following content:

div
  default first
div
  default second

It can be called as a fragment:

import aux-imported.slab
  frag first
    h1 A title
  frag second
    p A paragraph.
<div>
    <h1>
        A title
    </h1>
</div>
<div>
    <p>
        A paragraph.
    </p>
</div>

Open source

Slab is an open source project released under the terms of the 2-Clause BSD license. The source code and issue tracker are available on GitHub.

The Haskell package is available on Hackage.