March 14, 2020

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