Hiccup is a Clojure DSL for writing HTML. Here is a quick tutorial for the busy. It contains most of the information you’re going to need in a typical project.
Introduction
Hiccup is a domain-specific language for generating HTML in Clojure. In other words, it turns Clojure into HTML. Let’s look at an example:
As you can see, it uses literal data structures — in fact, it’s just Clojure’s vectors containing keywords (representing tags) and maps (for HTML properties). The above example compiles down to:
…which is simple div with a single button which increments some atom counter. Everything is represented as a plain Clojure data-structure.
The advantages
- No need to create separate files for templates. Hiccup is typically used in Reagent/re-frame projects, where views are just Clojure functions returning HTML (or functions returning functions).
- HTML represented as plain vectors which can be easily processed and generated using all the tools of the host language. This means that repetitive code can be abstracted away into functions and mechanically manipulated.
- A thin layer of abstraction allows improving HTML semantics while not being new enough to require re-learning.
The gist of it
Vectors and maps
If you’re building front-end using Reagent/re-frame, you will probably spend most of the time using Hiccup’s literals. In Hiccup, HTML nodes are represented as Clojure’s vectors. The first element of the vector is always a keyword (e.g. :div
) designating HTML tag. Immediately after the tag, we specify special options which act as a syntactic sugar (explained below). The second argument is an optional map of the tag’s attributes. We will specify here things such as inline styles, classes, event listeners, and so on. Lastly, we pass arguments to the element itself, such as a string for the label. Putting it all together, we have:
For example:
Tip: Because Hiccup uses vectors to represent HTML trees, use lists to represent sequences. Mixing vector sequences with HTML vectors may confuse Hiccup.
Constructor functions
Hiccup provides constructors for many of the standard HTML elements. Use them to remove some of the boilerplate. For example, instead of slightly verbose:
…we can do:
For a comprehensive listing, consult the API documentation.
Inline styling
Styles can be added on the fly to elements via the :style
map:
By default, integers are interpreted as pixels. If you want to use a different unit, use a string instead: {:padding "50rem"}
.
Sugar for the id and class attribute
Hiccup provides a convenient syntax for adding id
and class
attributes to an element. The following code:
…can be rewritten as:
The word after the # denotes the element’s ID, and each word after a dot corresponds to one of the element’s classes. The ID must always come first.
Tip: Specify permanent classes using sugar and dynamic ones via :class
. Sometimes you will use a particular class only in certain conditions. For example, the class "active"
may depend on the Boolean active?
:
(Strings to :class
are concatenated, so we need whitespace to separate classes). My suggestion is to specify classes that are static using sugar notation and leave everything else in the attribute map:
Conditional rendering
nil
values are ignored. This means that we don’t have to worry about them slipping into our HTML. For example, Clojure’s function (when test & body)
will return body
if test
is true and nil
otherwise. Therefore, we can do:
If is-true?
is false, the value of the expression will simply be ignored, and only element a
will end up in the output.
Sugar for nested tags
To avoid repetitive nesting of HTML tags:
…you can use the > sugar:
Escape strings with h
to avoid XSS
Hiccup concatenates strings without asking questions. Therefore, whenever you receive a string from unsafe sources (such as user form or foreign API) you should escape the string with hiccup.core/h
:
Abstraction
One of the biggest advantages of using Hiccup is the ability to abstract away all the repetitiveness of manual HTML-writing. Instead of typing each li
, we can use a loop:
Often, it’s a good idea to write a generator function for a commonly occurring element. For example:
…which yields a button with an internal counter variable. We can stack and nest those endlessly. However, as Eric Normand points out, there is one problem:
[..] because the Hiccup compiler doesn’t do a full examination of your code, it can’t compile everything. It inserts run time fallbacks for stuff it can’t handle at compile time which will interpret it at run time. So, for instance, if you’re calling a function that returns some Hiccup, it can’t compile that automatically. It has to wait till the function returns to know what it is. That is, unless . . .
The fix: The way to get Hiccup to compile something is with the
hiccup.core/html
macro. That’s the macro that does the compilation and it will do it anywhere.
For most projects, this shouldn’t be a problem; however, a large codebase may lose some performance. To avoid this, we can do:
Rendering
To force HTML generation directly, use hiccup.core/html
function:
That’s it — the gist of Hiccup. Thanks for reading. Take a look at the resources below if you want to delve deeper and maybe my other stuff. If you have any questions, please feel free to send me an e-mail or leave a comment.
Resources
- https://github.com/weavejester/hiccup/wiki
- http://weavejester.github.io/hiccup/index.html
- https://github.com/yokolet/hiccup-samples
- https://purelyfunctional.tv/mini-guide/hiccup-tips/
- http://www.rkn.io/2014/03/13/clojure-cookbook-hiccup/
Thanks to Marcin Struś.
And if you need a Clojure task-force team…
Let’s talk!As the Principal Software Engineer at Makimo, Iwo Herka thrives in the rich terrain of Elixir and functional programming. He employs a scientific approach to demystify its complexities, chronicling his findings in in-depth articles, insightful talks, and informative videos. When he steps away from the pulsating world of code, Iwo can be found trekking nature's trails or engrossed in a good book. For Iwo, programming isn't merely a job — it's a daily adventure he passionately embarks on.