Garden is a library for rendering CSS in Clojure and ClojureScript. Conceptually similar to Hiccup, it uses vectors to represent rules and maps to represent declarations. It is designed for stylesheet authors who are interested in what's possible when you trade a preprocessor for a programming language.
Add the following dependency to your project.clj
file:
[garden "1.1.3"]
Garden syntax is very similar to Hiccup. If you're familiar with Hiccup you should feel right at home working with Garden. If not, don't sweat it! Garden's syntax is fairly simple.
From your project's root directory start up a new REPL and try the following:
user=> (require '[garden.core :refer [css]]))
nil
user=> (css [:body {:font-size "16px"}])
"body{font-size:16px}"
First you'll notice the use of the css
function. This function takes
an optional map of compiler flags, any number of rules, and returns a
string of compiled CSS. We'll start off by discussing rules then
follow up with declarations and Garden's other utilities. Then we'll
demonstrate the use of compiler flags.
As mentioned, vectors represent rules in CSS. The first n non-collection
elements of a vector depict the rule's selector where n > 0. When n = 0 the
rule is not rendered. To produce a rule which selects the <h1>
and <h2>
HTML
elements for example, we simply begin a vector with [:h1 :h2]
.
user=> (css [:h1 :h2 {:font-weight "none"}])
"h1,h2{font-weight:none}"
To target child selectors nested vectors may be employed:
user=> (css [:h1 [:a {:text-decoration "none"}]])
"h1 a{text-decoration:none}"
user=> (css [:h1 :h2 [:a {:text-decoration "none"]])
"h1 a, h2 a{text-decoration:none}"
A slightly more complex example demonstrating nested vectors with multiple selectors:
user=> (css [:h1 :h2 {:font-weight "normal"}
[:strong :b {:font-weight "bold"}]])
"h1,h2{font-weight:normal}h1 strong,h1 b,h2 strong,h2 b{font-weight:bold}"
As in Sass, Garden also supports selectors prefixed with the &
character allowing you to reference a parent selector.
user=> (css [:a
{:font-weight 'normal
:text-decoration 'none}
[:&:hover
{:font-weight 'bold
:text-decoration 'underline}]])
"a{text-decoration:none;font-weight:normal}a:hover{text-decoration:underline;font-weight:bold}"
Clojure maps represent CSS declarations where map keys and values represent CSS properties and values respectively. Garden's declaration syntax is a bit more involved than rules and understanding it is important to make the most of the library.
Declaration map keys should either be a string, keyword, or symbol:
user=> (css [:h1 {"font-weight" "normal"}])
"h1{font-weight:normal}"
user=> (css [:h1 {:font-weight "normal"}])
"h1{font-weight:normal}"
user=> (css [:h1 {'font-weight "normal"}])
"h1{font-weight:normal}"
Be aware Garden makes no attempt to validate your declarations and will not raise an error if other key types are used.
user=> (css [:h1 {30000 "nom-nom"}])
"h1{30000:nom-nom}"
We've already seen strings used as declaration map values, but Garden also supports keywords, symbols, numbers, maps, vectors, and lists in addition.
Strings, keywords, symbols, and numbers are rendered as literal CSS values:
user=> (css [:body {:font "16px sans-serif"}])
"body{font:16px sans-serif}"
Be warned, you must escape literal string values yourself:
user=> (css [:pre {:font-family "\"Liberation Mono\", Consolas, monospace"}])
"pre{font-family:\"Liberation Mono\", Consolas, monospace}"
In some cases it would be useful target several properties in a "group" of
properties without having to type the same prefix several times. To do this
with Garden we use maps. Maps as declaration values are used to denote a
property suffix (IE. -family
or -weight
) and may be nested as deeply as
you like.
Here are a few practical examples of where this technique might be handy:
user=> ;; Working with vendor prefixes:
user=> (css [:.box
{:-moz {:border-radius "3px"
:box-sizing "border-box"}}])
".box{-moz-border-radius:3px;-moz-box-sizing:border-box}"
user=> ;; Creating DRY "mixins":
user=> (def reset-text-formatting
{:font {:weight "normal" :style "normal" :variant "normal"}
:text {:decoration "none"}})
#'user/reset-text-formatting
user=> (css [:a reset-text-formatting])
"a{font-variant:normal;font-style:normal;font-weight:normal;text-decoration:none}"
user=> (defn partly-rounded
([r1] (partly-rounded r1 r1))
([r1 r2]
{:border {:top-right-radius r1
:bottom-left-radius r2}}))
#'user/partly-rounded
user=> (css [:.box (partly-rounded "3px")])
".box{border-bottom-left-radius:3px;border-top-right-radius:3px}"
Finally we have vectors and lists which are handled in the same manor when used as a declaration value. The semantics of these values increment the level of complexity somewhat so be sure you understand their behavior before you use them. When you use a vector/list as a value you are asking Garden for a comma separated list.
user=> (css [:p {:font ["16px" "sans-serif"]}])
"p{font:16px,sans-serif}"
When you nest a vector/list you are asking for a space separated list.
user=> (css [:p {:font [["16px" 'Helvetica] 'Arial 'sans-serif]}])
"p{font:16px Helvetica,Arial,sans-serif}"
So far we've got all the core pieces to start building basic stylesheets. But it would be useful to have something for working with one of CSS's most fundamental data types: units!
Fortunately, Garden has built in support for working with all the major CSS
units. This includes creation, conversion, and arithmetic. To start using units
use/require the garden.units
namespace.
user=> (require '[garden.units :as u :refer [px pt]])
nil
For demonstration purposes we're only refer
ing the px
and pt
units but
Garden supports all of the usual suspects. Also we'll use the css
macro to
render the units as strings but note this is not necessary when authoring a
stylesheet.
Unit creation is straightforward:
user=> (px 16)
#garden.types.CSSUnit{:unit :px, :magnitude 1}
To see the value as it would appear in CSS require the garden.repl
namespace.
user=> (require 'garden.repl)
nil
user=> (px 16)
16px
Unit functions take a number n and construct a new garden.types.CSSUnit
record
with n as the magnitude. Unit functions also accept other units as values
returning their conversion if possible. This makes working with unit values
very flexible.
user=> (px (px 16))
16px
user=> (px (pt 1))
1.3333333333px
Unit arithmetic is available via the - spoiler alert- unit arithmetic functions.
user=> (require '[garden.units :refer (px+ px* px- px-div)])
nil
user=> (px+ 1 2 3 4 6)
16px
user=> (px-div 2 4)
0.5px
user=> (px* 2 2)
4px
Since the arithmetic functions use the primary unit functions in their definitions, conversion occurs seamlessly (when possible):
user=> (px* 2 (pt 1))
2.6666666666px
You might be wondering, which units can be converted to another? This
depends on the type of unit you are working with. The CSS spec outlines a few
categories of units but only absolute (px
, pt
, pc
, in
, cm
, and mm
),
angle (deg
, grad
, rad
, turn
), time (s
, ms
), and frequency
(Hz
, kHz
) units may be freely converted and only between their respective
groups. This means you cannot, for example, convert px
to rad
or Hz
to
cm
. Doing so will raise an error.
In the future, some exceptions to this rule might apply for working with em
s
since it's technically possible to compute their contextual value.
What would a stylesheet be like with out color? No fun. That's what it
would be like. And the person who's interested in writing a stylesheet
in Clojure probably wants tools for working with color. Who wants to
write a stylesheet where colors are strings that look like "#A55"
?
No one. That's who.
Since 0.1.0-beta5
Garden comes with a (mostly) complete set of
functions for dealing with colors. If you've worked with Sass you'll
be pleased to know many of the same color functions are available in
Garden.
Garden's color functions are available in the garden.color
namespace.
user=> (require '[garden.color :as color :refer [hsl rgb]])
Let's create a color to work with.
user> (def red (hsl 0 100 50))
#'user/red
user> red
#ff0000
We've defined red
in terms of the HSL value for pure red with the
hsl
function (rgb
is also available). When we evaluate the value
of red
at the REPL we notice it is displayed in the familiar
hexadecimal format.
Let's apply some color functions to it. By the way, if you're
using Emacs, try turning on rainbow-mode
to see the colors
highlighted.
;; Make dark red.
user> (color/darken red 25)
#800000
;; Make light red.
user> (color/lighten red 25)
#ff8080
But, wait! There's more!
;; Make an orange color...
user> (def orange (color/hsl 30 100 50))
;; ...and mix it with red.
user> (color/mix red orange)
#ff4000
;; Make a green color...
user> (def green (hsl 120 100 50))
;; ...and add it to red to get yellow.
user> (color/color+ red green)
#ffff00
;; Get a set of analogous colors.
user> (color/analogous red)
(#ff0000 #ff8000 #ffff00)
As with units, colors can be added, subtracted, divided and
multiplied with color+
, color-
, color*
, and color-div
respectively. There are several other nice functions available for
finding color complements, triads, tetrads, and more.
Now that we have a solid understanding of how units and colors
operate, we can talk about Garden's generic arithmetic operators.
While working with functions like px+
, color+
, etc. have their
advantages, sometimes they can get in the way. To get around this
you can use the operators in the garden.arithmetic
namespace.
(ns user
;; Unless you want to see a bunch of warnings add this line.
(:refer-clojure :exclude '[+ - * /])
(:require '[garden.arithmetic :refer [+ - * /]]))
This will allow you to perform operations like this:
user> (+ 20 (color/hsl 0 0 0) 1 (color/rgb 255 0 0))
#ff1515
user> (- 20 (px 1) 5 (pt 5))
7.333333333500001px
Authoring stylesheets these days without media queries is somewhat
like having prime rib without horseradish. Garden provides the
at-media
function available in the garden.stylesheet
namespace.
user=> (require '[garden.stylesheet :refer [at-media]])
nil
user=> (css (at-media {:screen true} [:h1 {:font-weight "bold"}]))
"@media screen{h1{font-weight:bold}}"
user=> (css
(at-media {:min-width (px 768) :max-width (px 979)}
[:container {:width (px 960)}])
"@media (max-width:979px) and (min-width:768px){container{width:960px}}"
Media queries may also be nested:
user=> (css [:a {:font-weight "normal"}
[:&:hover {:color "red"}]
(at-media {:screen true}
[:&:hover {:color "pink"}])])
and will out put the equivalent CSS:
a {
font-weight: normal;
}
a:hover {
color: red;
}
@media screen {
a:hover {
color: pink;
}
}
To understand how media expressions are interpreted refer to this table:
Map | Interpretation |
---|---|
{:screen true} |
screen |
{:screen false} |
not screen |
{:screen true :braille false} |
screen and not braille |
{:screen :only} |
only screen |
{:min-width (px 768) :max-width (px 959)} |
(min-width: 768px) and (max-width: 959) |
The css
function optionally takes a map of compiler flags.
Often you are interested in saving a fully expanded result of
compilation for development and a compressed version for production.
This is controlled by the :pretty-print?
flag which may either be
true
or false
. By default this flag is set to true
.
Assuming:
(def styles
[[:h1 {:font-weight "normal"}]
[:a {:text-decoration "none"}]])
(css {:pretty-print? true} styles)
results in the following output when
compiled:
h1 {
font-weight: normal;
}
a {
text-decoration: none;
}
(css {:pretty-print? false} styles)
results in the following output when
compiled:
"h1{font-weight:normal}a{text-decoration:none}"
For Clojure generated stylesheets are compressed using the YUI Compressor which yeilds much better results when compared with the previous versions.
user=> (css {:pretty-print? false} [:body {:background "#ff0000"}])
"body{background:#f00}"
user> (css {:pretty-print? false} [:div {:box-shadow [[(px 0) (px 0.5) (hsl 0 0 0)]]}])
"div{box-shadow:0 .5px #000}"
For ClojureScript compression mostly consists of whitespace elimination wherever possible.
Note: This is currently not available for ClojureScript, but support is planned.
To save a stylesheet to disk simply set the :output-to
flag to the
desired path.
user=> (css {:output-to "foo.css"} [:h1 {:font-weight "normal"}])
Wrote: foo.css
"h1 {\n font-weight: normal;\n}"
Vendor prefixing can be a pain but Garden can help with that in some
cases if you set the :vendors
flag. The value is expeced to be a
vector of prefixes (ie ["webkit" "moz" "o"]
). By specifying this
Garden will automatically prefix declarations tagged with the
^:prefix
meta and @keyfames
.
This:
(require '[garden.def :refer [defrule defkeyframes]])
(defkeyframes pulse
[:from
{:opacity 0}]
[:to
{:opacity 1}])
(css {:vendors ["webkit"]
:output-to "foo.css"}
;; Include our keyframes
pulse
[:h1
;; Notice we don't need to quote pulse.
^:prefix {:animation [[pulse "2s" :infinite :alternate]]}])
will produce
@keyframes pulse {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-webkit-keyframes pulse {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
h1 {
-webkit-animation: pulse 2s infinite alternate;
animation: pulse 2s infinite alternate;
}
Listed by first commit:
If you're interested in automatically compiling your stylesheets be
sure to check out the
lein-garden
plugin.
A big thank you goes out to @weavejester for creating Hiccup, @briancarper for creating gaka, and @paraseba for creating cssgen. I learned a lot from studying the source code of these libraries (and borrowed several ideas from them). Writing this library would have been significantly more difficult without the hard work of these individuals.
I'd also like to thank @jhardy for putting up with random questions and pushing me to keep working on this library.
Thanks to everyone in #clojure
on IRC for answering my questions and being
patient with me. If you're looking for an example of a great community, look no
further.
Copyright © 2013 Joel Holdbrooks
Distributed under the Eclipse Public License, the same as Clojure.