Skip to content

Flawless/cljss

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

88 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clojure Style Sheets

cljss logo

CSS-in-JS for ClojureScript

Clojars

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
  • Injects styles into <style> tag at run-time
  • Debuggable styles in development (set via goog.DEBUG)

How it works

defstyles

defstyles macro expands into a function which accepts 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 optional hash of attributes and child components, and returns React element. It is available for 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 underlying React element. Reading from 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 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!")

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

Installation

Add to project.clj: [org.roman01la/cljss "1.5.2"]

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 [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;
}

Production build

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

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

Roadmap

  • Media Queries syntax
  • 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.

License

Copyright © 2017 Roman Liutikov

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

About

Clojure Style Sheets — CSS-in-JS for ClojureScript

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Clojure 99.6%
  • HTML 0.4%