Craft cleaner, more concise HTML with Haml

Parallel to the ideas behind Sass and CoffeeScript, Haml turns your markup into veritable haiku. Nick Walsh of Envy Labs shows you how to get started, with or without Ruby

Before the birth of Sass and CoffeeScript, Haml hit the Rails scene as a markup template alternative to ERB / RHTML. Following tenants evangelised by the meteoric Ruby framework, Haml (HTML abstraction markup language) acts in its simplest form as an HTML preprocessor – believing that markup should be beautiful, clear, well-indented, and free of repetition.

Though its CSS- and JavaScript-compiling counterparts have since passed it in popularity, the preprocessor movement has facilitated the creation of tools that also lower the Haml entry bar. As we'll explore shortly, the command line and Ruby installation can now be avoided completely.

At its core, Haml is terse and strictly-indented HTML, so this tutorial assumes a strong familiarity with HTML. As you might expect, if you plan to use it dynamically within Rails/Sinatra/similar, Ruby basics and knowledge of that framework will be necessary.

Ultimately, the easiest way to appreciate Haml is to see it:

!!! 5%html{lang: 'en'} %head %title Sample Haml %body %h1 Archaeopteryx

This snippet will compile down to:

<!DOCTYPE html><html lang='en'> <head> <title>Sample Haml</title> </head> <body> <h1>Archaeopteryx</h1> </body></html>

A brief history

Courtesy of Hampton Catlin (who is more synonymous with Sass these days), Haml made its first appearance back in 2006. For a time, Sass and Haml were packaged together in the same gem; Sass has since achieved more ubiquitous use outside of Rails, while Haml still enjoys popularity from its inclusion as part of "The Rails Way."

The project currently stands at version 3.1.4 and is maintained by Nathan Weizenbaum. Haml is MIT-licensed and the source is available on GitHub.

Prep work and highlighting

As with other preprocessors (LESS, Sass, CoffeeScript and so on), there are a few quick steps needed to get Haml into your workflow. You'll produce files ending in .haml, but one of the following methods is necessary for HTML conversion prior to browser rendering:

With Ruby

If Ruby is installed on your machine (it comes pre-installed on OS X), the gem is downloadable via the command line:

gem install haml

With the gem successfully installed, you gain the ability to convert Haml files to HTML with the haml command, replacing input and output with appropriate filenames:

haml input.haml output.html

This is a great way to experiment and familiarise yourself with the syntax, but it doesn't scale efficiently. Unlike Sass, the Haml gem doesn't come packaged with a watch function for automatic conversion. When possible, it's preferred to use one of the techniques below.

With Rails

Using Rails is the best-case scenario for implementation. Simply add the haml-rails gem to your Gemfile and utilise bundler (bundle install or simply bundle). Views generated by the framework will switch to a .html.haml extension, and all compiling is handled for you.

Sans Ruby

In addition to supporting a number of congruent preprocessed languages, CodeKit and LiveReload facilitate setup without the command line for compiling Haml files on save (amongst other useful features). If you want to steer clear of Ruby or avoid the command line, this is the best path.

Highlighting

Before jumping in, head over to the editor support page to ensure proper syntax highlighting in your favourite text editor.

Basic tags and doctypes

HTML tag elements are created using the percent sign (%) followed by the tag name (%h1, for instance).

!!! 5%html

Compiles to:

<!DOCTYPE html><html></html>

As you might have noticed, there's no Haml equivalent of a closing tag – they're added for you. This snippet also includes shorthand provided for simple Doctype inclusion, many more of which are available in the documentation.

Nesting content and tags

Since Haml is devoid of closing tags, nesting is controlled via strict whitespace and indenting standards. Content can be included on the same line as the tag, or indented inside on the following line. Nested tags must be indented on a new line.

%h1 Gallimimus%h2 Cretaceous Period%h3 %strong Discovered in 1963

Compiles to:

<h1>Gallimimus</h1><h2> Cretaceous Period</h2><h3> <strong>Discovered in 1963</strong></h3>

While you can choose between including content on the same line or nesting it, an error will be raised if both are attempted at the same time. The number of spaces or tabs used must be consistent, or the compiler will also return with an error.

-# Returns with an error: content is on the same line and nested inside%h4 Max Height: %em 9m-# Returns with an error: inconsistent indentation%p Present in the same period as the %span Tyrannosaurus

This segment would not compile successfully.

Adding Attributes

Attributes can be added to tags via a Ruby hash (a group of key-value pairs, very similar to an associative array), or added normally within parentheses appended to the tag.

%a{href: 'http://en.wikipedia.org/wiki/Spinosaurus', title: 'Spinosaurus'} Spinosaurus%a(href="http://en.wikipedia.org/wiki/Spinosaurus" title="Spinosaurus") Spinosaurus

Compiles to:

<a href='http://en.wikipedia.org/wiki/Spinosaurus' title='Spinosaurus'>Spinosaurus</a><a href='http://en.wikipedia.org/wiki/Spinosaurus' title='Spinosaurus'>Spinosaurus</a>

IDs, classes, and divs

While id and class attributes can be added as described above, they're more commonly represented like CSS selectors appended to the tag. It's worth noting that if no HTML tag is explicitly defined, Haml inserts a <div>.

%section#content.container .stats.container

Compiles to:

<section class='container' id='content'> <div class='stats container'></div></section>

Filters

Filters, denoted with a leading colon, allow content nested inside to be filtered outside of Haml before being added to the output. Examples include :javascript, :markdown, and :cdata; the JavaScript filter, for instance, wraps nested content in <script> and CDATA tags.

:javascript $('.dinosaur').on('click', function(e) { alert('rawr'); });

Compiles to:

<script type='text/javascript'> //<![CDATA[ $('.dinosaur').on('click', function(e) { alert('rawr'); }); //]]></script>

We'll revisit the Markdown filter later, as it serves a useful role in overcoming Haml's major shortcoming.

Adding Ruby

Beyond simple HTML conversion, Ruby is easy to embed and output in your templates - features necessary when paired with Rails, Sinatra, and dynamic data in general. Lines to be evaluated begin with a hyphen (-), while lines to be output start with an equals sign (=).

If you're familiar with embedding Ruby in ERB or interspersing PHP into HTML, it's the same concept – the hyphen and equals signs take the place of constructs like <% %>, <%= %>, and <?php ?>

- title = 'Stegosaurus'%h1= title

Compiles to:

<h1>Stegosaurus</h1>

There's no end

Loops and conditional logic can also be added in this way. Like markup tags, it's unnecessary to close these statements – indentation controls what content falls within. The general preference within Rails (and other MVC frameworks) is to have as little logic as possible in your markup template; if it feels cumbersome or difficult to write, the segment should likely live elsewhere in your application.

- title = 'Pterodactyl'- if title == 'Stegosaurus' %h2 The Greatest Dinosaur Ever- else %h2 Some Random Dinosaur

Compiles to:

<h2>Some Random Dinosaur</h2>

What's all this good for?

Now that you have a grasp on the basic syntax, let's take a look at the reasons behind adding another acronym to your workflow:

  • Repetition and effort to produce HTML is significantly reduced. This isn't a strong reason on its own, as typing usually isn't our bottleneck; after using Haml for an extended period of time, though, you'll notice the difference when switching back.
  • Once you're familiar with the syntax, Haml files (especially large ones) tend to be easier to scan than their HTML equivalent – an important consideration for maintenance.
  • Strict nesting and indenting go a long way toward ensuring quality changes and additions from all contributors.
  • Server-side code integrates seamlessly when paired with something like Rails.

Nesting shortcomings

Haml's strictness with nesting and indenting can cause a few issues for those unfamiliar. Fortunately, it's pretty easy to circumvent these problems:

Content formatting

Formatting content (specifically, inline tags on blocks of text) is Haml's main weakness. Since multiple tags cannot be declared on the same line, you're forced to either:

  • Include manual line breaks before and after every inline tag nested in the parent.
  • Add standard HTML opening and closing tags (which is valid, and actually recommended).
  • Ensure Markdown is installed on your server/locally and utilise the :markdown filter to add Markdown-flavoured syntax to blocks of content.
%p Visit a local %em Museum or a %em Science Institute%p Visit a local <em>Museum</em> or a <em>Science Institute</em>:markdown Visit a local *Museum* or a *Science Institute*

Compiles to:

<p> Visit a local <em>Museum</em> or a <em>Science Institute</em></p><p>Visit a local <em>Museum</em> or a <em>Science Institute</em></p><p>Visit a local <em>Museum</em> or a <em>Science Institute</em></p>

Output wrangling

If the produced HTML isn't quite to your standards, operators such as <, >, and | are available for finer control over what the compiler outputs:

%h2< %span Remove whitespace in a tag%h2 %span Remove %span> whitespace around %span a tag%h2 Evaluate multiple lines | as one with the pipe character |

Compiles to:

<h2><span>Remove whitespace in a tag</span></h2><h2> <span>Remove</span><span>whitespace around</span><span>a tag</span></h2><h2> Evaluate multiple lines as one with the pipe character </h2>

Ugly

When Haml is paired with a Ruby framework, you might notice your HTML source lacks indenting in production mode. Amongst a number of possible options, the :ugly control determines whether to format the output after compiling. It's set to true by default in Rails, and serves to greatly improve rendering performance without effecting what the user sees.

Wrap-up

As mentioned more than once, Haml is fantastic for turning your HTML into well-formed markup with strict conventions – something that benefits both speed and collaborative dynamics. It's short work to pick up the syntax, and really shines when paired with a Ruby framework. While Haml has a few hangups in regards to content formatting, we have more than one way to cope acceptably. This was by no means an exhaustive list of everything available in Haml – definitely take a look at the reference for more concepts like commenting, interpolation, and self-closing tags once you become accustomed to it.

Further reading

  • Haml Reference: official documentation.
  • Tinkerbin: jsFiddle-like web app that lets you experiment with Haml.
  • Try Ruby: give Ruby a shot in the browser.
  • Adding Haml: my Future of Web Design '11 presentation with live conversion examples.