Skip to content
Raphael Martin Schindler edited this page Dec 13, 2022 · 9 revisions

Introduction

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.

Rules

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 descendant 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}"

As in Less/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}"

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}"

Selectors

garden.selectors namespace defines a CSSSelector record. It doubles as both a function and a literal (when passed to the css-selector). When the function is called it will return a new instance that possesses the same properties. All arguments to the function must satisfy ICSSSelector.

garden.selectors namespace also defines these macros that create a selector record: defselector, defclass, defid, defpseudoclass and defpseudoelement.

garden.selectors namespace also defines many CSSSelector instances such as:

  • Type selectors a, abbr, address and more
  • Pseudo-classes active, checked, disabled and more
  • Language and negation pseudo-classes lang and not
  • Structural pseudo-classes nth-child, nth-last-child, nth-of-type and nth-last-of-type
  • Pseudo-elements after, before, first-letter and first-line
  • Attribute selectors attr=, attr-contains, attr-starts-with, attr-starts-with*, attr-ends-with and attr-matches
  • Combinators descendant, +, - and >
  • Special selector &

and allows to compose complex selectors such as this:

(defselector *)
(defpseudoclass host [x] x)
(defpseudoelement content)
(> (host (attr :flipped)) content (* last-child))
;; => :host([flipped]) > ::content > *:last-child

An example of comma-separated selectors with pseudo-elements:

(css [[:* [(&) (& before) (& after) {:box-sizing "inherit"}]]])
;; => *, *::before, *::after {\n  box-sizing: inherit;\n}

garden.selectors namespace also defines a CSS3 selectors's specificity function:

(specificity "#s12:not(FOO)")
;; => 101
(specificity (a hover))
;; => 10

Declarations

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.

Properties

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}"

Values

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

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}"
Maps

In some cases it would be useful to be able to 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}"
Vectors and Lists

Finally we have vectors and lists which are handled in the same manner when used as a declaration value. The semantics of these values increases the level of complexity somewhat, so be sure that 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}"