Build HTML from plain Phel data: vectors are elements, maps are attributes, and values auto-escape for XSS protection. No template language to learn, just the data structures you already use.
Syntax#
html from phel.html generates HTML:
(ns my-namespace
(:require phel.html :refer [html]))
(html [:span {:class "foo"} "bar"])
;; Evaluates to <span class="foo">bar</span>
Forms:
[tag body+]
[tag attributes body+]
First item: tag name (keyword or string). Second item: optional attribute map. Rest: body (strings, nested vectors, lists).
(ns my-app
(:require phel.html :refer [html]))
(html [:div]) ; Evaluates to "<div></div>"
(html ["div"]) ; Evaluates to "<div></div>"
(html [:text "Lorem Ipsum"]) ; Evaluates to "<text>Lorem Ipsum</text>"
(html [:body [:p] [:br]]) ; Evaluates to "<body><p></p><br /></body>"
(html [:div {:id "foo"}]) ; Evaluates to "<div id=\"foo\"></div>"Classes and styles#
Phel enhances class and style attributes.
Use a map for styles instead of a string. Both forms equivalent:
(ns my-app
(:require phel.html :refer [html]))
(html [:div {:style "background:green;color:red;"} "bar"])
(html [:div {:style {:background "green" :color "red"}} "bar"])
;; Both evaluate to
;; "<div style=\"background:green;color:red;\">bar</div>"
Class lists: vector or map. Map keys are class names; only truthy keys appear in the final list.
(ns my-app
(:require phel.html :refer [html]))
(html [:div {:class [:a]}]) ; <div class=\"a\"></div>
(html [:div {:class [:a "b"]}]) ; <div class=\"a b\"></div>
(html [:div {:class [:a :b]}]) ; <div class=\"a b\"></div>
(html [:div {:class {:a true :b false}}]) ; <div class=\"a\"></div>Conditional rendering#
Use if:
(ns my-app
(:require phel.html :refer [html]))
(html [:div [:p "a"] (if true [:p "b"] [:p "c"])])
;; Evaluates to "<div><p>a</p><p>b</p></div>"
(html [:div [:p "a"] (if false [:p "b"] [:p "c"])])
;; Evaluates to "<div><p>a</p><p>c</p></div>"Rendering sequential data#
for over vectors, lists, sets:
(ns my-app
(:require phel.html :refer [html]))
(html [:ul (for [i :range [0 3]] [:li i])])
;; Evaluates to "<ul><li>0</li><li>1</li><li>2</li></ul>"
(html [:ul (for [i :in [3 4 5]] [:li i])])
;; Evaluates to "<ul><li>3</li><li>4</li><li>5</li></ul>"Raw HTML#
Values auto-escape for XSS protection. For unescaped output, use raw-string:
(ns my-app
(:require phel.html :refer [html raw-string]))
(html [:span (raw-string "<a></a>")])
;; Evaluates to "<span><a></a></span>"Doctypes#
Use doctype for the document doctype:
(ns my-app
(:require phel.html :refer [html doctype]))
(html (doctype :html5) [:div])
;; Evaluates to "<!DOCTYPE html>\n<div></div>"
Supported values: :html5, :xhtml-transitional, :xhtml-strict, :html4.
Composing reusable fragments#
Because elements are just vectors, a function that returns a vector is a reusable component. Compose them like any other Phel value, then pass the result to html once at the end.
(ns my-app
(:require phel.html :refer [html]))
(defn nav-link [url label]
[:a {:href url} label])
(defn layout [title content]
[:html
[:head [:title title]]
[:body
[:nav (nav-link "/" "Home") (nav-link "/about" "About")]
content]])
(html (layout "Home" [:p "Welcome"]))
;; Evaluates to
;; "<html><head><title>Home</title></head><body>
;; <nav><a href=\"/\">Home</a><a href=\"/about\">About</a></nav><p>Welcome</p></body></html>"
Return a fragment from a route handler to produce the response body. See Request and Response and Routing for wiring fragments into responses.
Next steps#
- Request and Response - send rendered HTML as a response body
- Routing - map URLs to handlers that return HTML
- html API reference - full list of helpers