My Garden CSS has ascended

Table of Contents

I’ve been continually seeking the answer to Garden CSS’s title question: “what’s possible when you trade a preprocessor for a programming language?” I have used Garden exclusively for years, happily wielding garden-gnome to implement hot-loading on my front-end for an excellent development experience. Writing CSS with Clojure data-structures is a hands-down win over raw css-writing, and Garden also gives useful shortcuts that some of the preprocessors also have like lighten, darken, & selectors, and more. Still, though, I felt something missing: things were beautifully data-driven with Garden, but the front-end/back-end gap meant that I was still not capturing my dream of truly functional CSS development (e.g. the front-end couldn’t send args to control the styles).

A passing remark on Twitter the other day implanted an idea: how about generating the styles on the front-end and sticking them into the <style> element of the page? There are pros-and-cons to doing this; here are some I’ve thought about:

Pros Cons
Functional (can take args)
Leverages existing hot-loading (Figwheel or Shadow)
Unlike backend Garden, allows easy name-spacing of all your styles Requires extra for sharing styles between pages
Renders styles without back-end calls for each css file Defeats file caching
Can work in conjunction with traditional back-end files
Is a middle-ground between maintenance-unfriendly in-line styles and static stylesheets

After literally dreaming about this idea the other day, I spent an hour this afternoon implementing it, and it feels good! Once I’ve tried this for a while and have a better feel for the ins-and-outs, I might code it up as a micro-library; in the meanwhile, below is all the code involved.

The actual style definition (cljs, since Garden is all cljc):

(ns centrifuge.views.styles.article
  (:require [garden.core :as garden :refer [css] ]
            [garden.units :as u :refer [px em rem percent]]
            [garden.color :as c :refer [hex->hsl hsl->hex hsl hsla]]))

(defn article
  "Style for article page"
  [{:keys [line-height]}] ;; This is where any args necessary to make the styles responsive would go
  (css  ;; css renders the datastructure to a string, not necessary for back-end Garden
   [:h1 {:background "blue"
         :line-height line-height}]))

Then the magic js interop to make it work:

(defn clear-styles!
  "Remove existing style elements from the document <head>"
  []
  (let [styles (.getElementsByTagName js/document "style")
        style-count (.-length styles)]
    (doseq [n (range style-count 0 -1)] ;; deleting backward avoids any funny-business because the HTMLCollection in `styles` is a live list and changes under our feet
      (.remove (aget styles (dec n))))))


(defn mount-style
  "Mount the style-element into the header with `style-text`, ensuring this is the only `<style>` in the doc"
  [style-text]
  (let [head (or (.-head js/document)
                 (aget (.getElementsByTagName js/document "head") 0))
        style-el (doto (.createElement js/document "style")
                   (-> .-type (set! "text/css"))
                   (.appendChild (.createTextNode js/document style-text)))]
    (clear-styles!)
    (.appendChild head style-el)))

Then I just stick a call to (mount-style) in your page element someplace and you’re set.

(mount-style (styles/article {:line-height 3))

Future Questions

  • Could I have actually reagent-mounted a style component into my <head>? Perhaps, which would have spared me some of that js interop, but then I would have clobbered all the other things already in the head (webjar assets, fonts and style links, session info, etc)

  • I haven’t considered any cross-browser issues, besides willfully ignoring cautions that things are a touch more complicated on IE < 8.

  • There is still some thought available for where to put the (css) call that renders the text; I chose to put it in the style ns itself so that I don’t need to touch the dependencies of other namespaces, but there could be arguments to do it elsewhere.

Tory Anderson avatar
Tory Anderson
Full-time Web App Engineer, Digital Humanist, Researcher, Computer Psychologist