• Stars
    star
    455
  • Rank 96,175 (Top 2 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created almost 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Clojure Style Sheets — CSS-in-JS for ClojureScript

Clojure Style Sheets

cljss logo

CSS-in-JS for ClojureScript

Clojars cljdoc badge CircleCI

Ask questions on #cljss chat at Clojuarians Slack

[clj-commons/cljss "1.6.4"]

Table of Contents

Why write CSS in ClojureScript?

Writing styles this way has the same benefits as writing components that keep together view logic and presentation. It all comes to developer efficiency and maintainability.

Thease are some resources that can give you more context:

Features

  • Automatic scoped styles by generating unique names
  • CSS pseudo-classes and pseudo-elements
  • CSS animations via @keyframes at-rule
  • CSS Media Queries
  • Nested CSS selectors
  • Injects styles into <style> tag at run-time
  • Debuggable styles in development (set via goog.DEBUG)
  • Fast, 1000 insertions in under 100ms

How it works

defstyles

defstyles macro expands into a function which accepts an arbitrary number of arguments and returns a string of auto-generated class names that references both static and dynamic styles.

(defstyles button [bg]
  {:font-size "14px"
   :background-color bg})

(button "#000")
;; "-css-43696 -vars-43696"

Dynamic styles are updated via CSS Variables (see browser support).

defstyled

defstyled macro accepts var name, HTML element tag name as a keyword and a hash of styles.

The macro expands into a function which accepts an optional hash of attributes and child components, and returns a React element. It is available for the Om, Rum and Reagent libraries. Each of them are in corresponding namespaces: cljss.om/defstyled, cljss.rum/defstyled and cljss.reagent/defstyled.

A hash of attributes with dynamic CSS values as well as normal HTML attributes can be passed into the underlying React element. Reading from the attributes hash map can be done via anything that satisfies cljs.core/ifn? predicate (Fn and IFn protocols, and normal functions).

NOTE: Dynamic props that are used only to compute styles are also passed onto React element and thus result in adding an unknown attribute on a DOM node. To prevent this it is recommended to use keyword or a function marked with with-meta so the library can remove those props (see example below).

  • keyword value — reads the value from props map and removes matching attribute
  • with-meta with a single keyword — passes a value of a specified attribute from props map into a function and removes matching attribute
  • with-meta with a collection of keywords — passes values of specified attributes from props map into a function in the order of these attributes and removes matching attributes
(defstyled h1 :h1
  {:font-family "sans-serif"
   :font-size :size ;; reads the value and removes custom `:size` attribute
   :color (with-meta #(get {:light "#fff" :dark "#000"} %) :color)} ;; gets `:color` value and removes this attribute
   :padding (with-meta #(str %1 " " %2) [:padding-v :padding-h])) ;; gets values of specified attrs as arguments and remove those attrs

(h1 {:size "32px" ;; custom attr
     :color :dark ;; custom attr
     :padding-v "8px" ;; custom attr
     :padding-h "4px" ;; custom attr
     :margin "8px 16px"} ;; normal CSS rule
    "Hello, world!")
;; (js/React.createElement "h1" #js {:className "css-43697 vars-43697"} "Hello, world!")

predicate attributes in defstyled

Sometimes you want toggle between two values. In this example a menu item can switch between active and non-active styles using :active? attribute.

(defstyled MenuItem :li
  {:color (with-meta #(if % "black" "grey") :active?)})

(MenuItem {:active? true})

Because this pattern is so common there's a special treatment for predicate attributes (keywords ending with ?) in styles definition.

(defstyled MenuItem :li
  {:color "grey"
   :active? {:color "black"}})

(MenuItem {:active? true})

pseudo-classes

CSS pseudo classes can be expressed as a keyword using parent selector syntax & which is popular in CSS pre-processors or as a string if selector is not a valid keyword e.g. &:nth-child(4).

(defstyles button [bg]
  {:font-size "14px"
   :background-color blue
   :&:hover {:background-color light-blue}
   "&:nth-child(3)" {:color "blue"}})

Nested selectors

Sometimes when you want to override library styles you may want to refer to DOM node via its class name, tag or whatever. For this purpose you can use nested CSS selectors via string key.

(defstyles error-form []
  {:border "1px solid red"
   ".material-ui--input" {:color "red"}})

[:form {:class (error-form)} ;; .css-817253 {border: 1px solid red}
 (mui/Input)] ;; .css-817253 .material-ui--input {color: red}

:css attribute

:css attribute allows to define styles inline and still benefit from CSS-in-JS approach.

NOTE: This feature is supported only for Rum/Sablono elements

(def color "#000")

[:button {:css {:color color}} "Button"]
;; (js/React.createElement "button" #js {:className "css-43697 vars-43697"} "Button")

defkeyframes

defkeyframes macro expands into a function which accepts arbitrary number of arguments, injects @keyframes declaration and returns a string that is an animation name.

(defkeyframes spin [from to]
  {:from {:transform (str "rotate(" from "deg)")
   :to   {:transform (str "rotate(" to "deg)")}})

[:div {:style {:animation (str (spin 0 180) " 500ms ease infinite")}}]
;; (js/React.createElement "div" #js {:style #js {:animation "animation-43697 500ms ease infinite"}})

font-face

font-face macro allows to define custom fonts via @font-face CSS at-rule. The macro generates CSS string and injects it at runtime. The syntax is defined in example below.

The macro supports referring to styles declaration in a separate *.clj namespace.

(require '[cljss.core :refer [font-face]])

(def path "https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE")

(font-face
  {:font-family "Patrick Hand SC"
   :font-style "normal"
   :font-weight 400
   :src [{:local "Patrick Hand SC"}
         {:local "PatrickHandSC-Regular"}
         {:url (str path ".woff2")
          :format "woff2"}
         {:url (str path ".otf")
          :format "opentype"}]
   :unicode-range ["U+0100-024F" "U+1E00-1EFF"]})

inject-global

inject-global macro allows to defined global styles, such as to reset user agent default styles. The macro generates CSS string and injects it at runtime. The syntax is defined in example below.

The macro supports referring to styles declaration in a separate *.clj namespace.

(require '[cljss.core :refer [inject-global]])

(def v-margin 4)

(inject-global
  {:body     {:margin 0}
   :ul       {:list-style "none"}
   "ul > li" {:margin (str v-margin "px 0")}})

CSS Media Queries

The syntax is specified as of CSS Media Queries Level 4 spec.

(require [cljss.core :as css])

(defstyles header [height]
  {:height     height
   ::css/media {[:only :screen :and [:max-width "460px"]]
                {:height (/ height 2)}}})
  • Supported media types: #{:all :print :screen :speech}
  • Modifiers: #{:not :only}

More examples of a query:

Boolean query

[[:monochrome]]

Simple conditional query

[[:max-width "460px"]]

Multiple (comma separated) queries

[[:screen :and [:max-width "460px"]]
 [:print :and [:color]]]

Basic range query

Supported operators for range queries #{'= '< '<= '> '>=}

'[[:max-width > "400px"]]

Complex range query

'[["1200px" >= :max-width > "400px"]]

Usage

(defstyles name [args] styles)

  • name name of a var
  • [args] arguments
  • styles a hash map of styles definition
(ns example.core
  (:require [cljss.core :refer-macros [defstyles]]))

(defstyles button [bg]
  {:font-size "14px"
   :background-color bg})

[:div {:class (button "#fafafa")}]

(defstyled name tag-name styles)

  • name name of a var
  • tag-name HTML tag name as a keyword
  • styles a hash map of styles definition

Using Sablono templating for React

(ns example.core
  (:require [sablono.core :refer [html]]
            [cljss.rum :refer [defstyled]]))

(defstyled Button :button
  {:padding "16px"
   :margin-top :v-margin
   :margin-bottom :v-margin})

(html
  (Button {:v-margin "8px"
           :on-click #(console.log "Click!")}))

Dynamically injected CSS:

.css-43697 {
  padding: 16px;
  margin-top: var(--css-43697-0);
  margin-bottom: var(--css-43697-1);
}
.vars-43697 {
  --css-43697-0: 8px;
  --css-43697-1: 8px;
}

Composing styles

Because CSS is generated at compile-time it's not possible to compose styles as data, as you would normally do it in Clojure. At run-time, in ClojureScript, you'll get functions that inject generated CSS and give back a class name. Hence composition is possibly by combining together those class names.

(defstyles margin [& {:keys [x y]}]
  {:margin-top    y
   :margin-bottom y
   :margin-left   x
   :margin-right  x})

(defstyles button [bg]
  {:padding "8px 24px"
   :background-color bg})

(clojure.string/join " " [(button "blue") (margin :y "16px")]) ;; ".css-817263 .css-912834"

Development workflow

If you see that styles are not being reloaded, add (:require-macros [cljss.core]), this should do the job.

When developing with Figwheel in order to deduplicate styles between reloads it is recommended to use Figwheel's :on-jsload hook to clean injected styles.

:figwheel {:on-jsload example.core/on-reload}
(ns example.core
  (:require [cljss.core :as css]))

(defn on-reload []
  (css/remove-styles!)
  (render-app))

NOTE: don't forget that once styles were removed you have to re-inject defkeyframes, font-face and inject-global declarations

Production build

Set goog.DEBUG to false to enable fast path styles injection.

{:compiler
 {:closure-defines {"goog.DEBUG" false}}}

NOTE: production build enables fast pass styles injection which makes those styles invisible in <style> tag on a page.

Roadmap

  • Server-side rendering

Contributing

  • Pick an issue with help wanted label (make sure no one is working on it)
  • Stick to project's code style as much as possible
  • Make small commits with descriptive commit messages
  • Submit a PR with detailed description of what was done

Development

A repl for the example project is provided via lein-figwheel.

$ cd example
$ lein figwheel

If using emacs cider - you can also launch the repl using M-x cider-jack-in-clojurescript.

Testing

cljss uses a combination of Clojure and ClojureScript tests. Clojure tests are run via lein test and ClojureScript tests are run via doo. ClojureScript tests require a valid environment in order to run - PhantomJS being the easiest to install.

Once a valid environment is setup, ClojureScript tests can be run like so:

$ lein doo phantom test once

Or with file watching:

$ lein doo phantom test

To run Clojure and ClojureScript tests at once use the test-all task:

$ lein test-all

Supporters

License

Copyright © 2017 Roman Liutikov

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

More Repositories

1

aleph

Asynchronous streaming communication for Clojure - web server, web client, and raw TCP/UDP
Clojure
2,544
star
2

kibit

There's a function for that!
Clojure
1,765
star
3

seesaw

Seesaw turns the Horror of Swing into a friendly, well-documented, Clojure library
Clojure
1,445
star
4

manifold

A compatibility layer for event-driven abstractions
Clojure
1,017
star
5

etaoin

Pure Clojure Webdriver protocol implementation
Clojure
913
star
6

marginalia

Ultra-lightweight literate programming for clojure inspired by docco
Clojure
816
star
7

secretary

A client-side router for ClojureScript.
Clojure
773
star
8

hickory

HTML as data
Clojure
634
star
9

claypoole

Claypoole: Threadpool tools for Clojure
Clojure
607
star
10

pretty

Library for helping print things prettily, in Clojure - ANSI fonts, formatted exceptions
Clojure
597
star
11

rewrite-clj

Rewrite Clojure code and edn
Clojure
576
star
12

potemkin

some ideas which are almost good
Clojure
568
star
13

pomegranate

A sane Clojure API for Maven Artifact Resolver + dynamic runtime modification of the classpath
Clojure
504
star
14

gloss

speaks in bytes, so you don't have to
Clojure
483
star
15

camel-snake-kebab

A Clojure[Script] library for word case conversions
Clojure
476
star
16

clooj

clooj, a lightweight IDE for clojure
Clojure
421
star
17

byte-streams

A Rosetta stone for JVM byte representations
Clojure
417
star
18

durable-queue

a disk-backed queue for clojure
Clojure
381
star
19

useful

Some Clojure functions we use all the time, and so can you.
Clojure
365
star
20

metrics-clojure

A thin façade around Coda Hale's metrics library.
Clojure
343
star
21

virgil

Recompile Java code without restarting the REPL
Clojure
302
star
22

citrus

State management library for Rum
Clojure
274
star
23

ordered

Ordered sets and maps, implemented in pure clojure
Clojure
253
star
24

clj-ssh

SSH commands via jsch
Clojure
227
star
25

dirigiste

centrally-planned object and thread pools
Java
204
star
26

iapetos

A Clojure Prometheus Client
Clojure
176
star
27

primitive-math

for the discerning arithmetician
Clojure
170
star
28

humanize

Produce human readable strings in clojure
Clojure
156
star
29

digest

Digest algorithms (md5, sha1 ...) for Clojure
Clojure
156
star
30

clj-yaml

YAML encoding and decoding for Clojure
Clojure
120
star
31

byte-transforms

methods for hashing, compressing, and encoding bytes
Clojure
104
star
32

ring-buffer

A persistent ring-buffer in Clojure
Clojure
96
star
33

tentacles

An Octocat is nothing without his tentacles
Clojure
80
star
34

fs

File system utilities for Clojure. (forked from Raynes/fs)
Clojure
72
star
35

lein-marginalia

A Marginalia plugin to Leiningen
HTML
69
star
36

meta

A meta-repo for clj-commons discussions
46
star
37

ring-gzip-middleware

GZIP your Ring responses
Clojure
41
star
38

rewrite-cljs

Traverse and rewrite Clojure/ClojureScript/EDN from ClojureScript
Clojure
41
star
39

formatter

Building blocks and discussion for building a common Clojure code formatter
36
star
40

vizdeps

Visualize Leiningen dependencies using Graphviz
Clojure
33
star
41

zprint-clj

Node.js wrapper for ZPrint Clojure source code formatter
Clojure
13
star
42

infra

Infrastructure for clj-commons
Clojure
2
star
43

clj-commons.github.io

Clojure Commons Site
HTML
1
star