CSS-in-CLJS library
(ns my.app
(:require [uix.core :as uix :refer [defui $]]
[uix.css :refer [css]]
[uix.css.adapter.uix]))
(defn button []
($ :button {:style (css {:font-size "14px"
:background "#151e2c"})}))
- Discuss at #uix on Clojurians Slack
{:deps {com.github.roman01la/uix.css {:mvn/version "0.2.1"}}}
I love inline styles, unfortunately they are quite limited, there's no way to specify states (hover, active, etc) or use media queries. Essentially, inline styles won't let you use all of CSS.
uix.css
is a successor of cljss, similar library that I created some years ago.
The library relies on shadow-cljs to generate and bundle CSS. css
macro accepts a map of styles and returns a tuple of a class name and a map of dynamic inline styles.
In the example below I'm using UIx. The library can be used with any other React wrapper as long as a proper adapter is provided (take a look at uix.css.adapter.uix
ns to learn how to build adapters). The adapter takes care of applying whatever css
macro returns.
(ns my.app
(:require [uix.core :as uix :refer [defui $]]
[uix.css :refer [css]]
[uix.css.adapter.uix]))
(def border-color "blue")
(defn button []
($ :button {:style (css {:font-size "14px"
:background "#151e2c"
:padding "8px 32px"
:border (str "1px solid " border-color)
:&:hover {:background "green"}
"@media (max-width: 800px)" {:padding "4px 12px"}
"& > strong" {:font-weight 600}})}))
uix.css/hook
build hook takes care of generating CSS and creating a bundle.
;; shadow-cljs.edn
{:deps true
:dev-http {8080 "public"}
:builds {:website
{:target :browser
:build-hooks [(uix.css/hook {:output-to "public/website.css"})]
:modules {:website {:entries [my.app]}}}}}
When compiled, static part of the styles map is dumped into CSS bundle, but dynamic part of it (see border-color
example above) is deferred to runtime, where values are assigned via CSS Variables API.
css
macro takes arbitrary number of styles
(defui button [{:keys [style children]}]
($ :button
{:style (css {:color :red
:padding "8px 16px"}
style)}
children))
($ button {:style (css {:background :yellow})}
"press me")
When a map of styles is passed at runtime, it will be applied as normal inline styles. This behaviour exists specifically for a case when you have UI styled with inline CSS and want to migrate to uix.css
gradually.
In this example existing button
component was updated with css
macro, but all usage places are still passing inline styles, meaning that updating internals of the component won't break its users.
(defui button [{:keys [style children]}]
($ :button
{:style (css {:color :red
:padding "8px 16px"}
style)}
children))
;; these styles will be applied as inline styles
($ button {:style {:background :yellow}}
"press me")
Styles passed under :global
keyword are not scoped to current element, also global styles do not support dynamic values. This exists as a convenience, to avoid creating CSS file just for global styles.
(defui app []
($ :div {:style (css {:width "100vw"
:min-height "100vh"
:background "#10121e"
:color "#d7dbf1"
:global {:html {:box-sizing :border-box}
"html *" {:box-sizing :inherit}
:body {:-webkit-font-smoothing :antialiased
:-moz-osx-font-smoothing :grayscale
:-moz-font-feature-settings "\"liga\" on"
:text-rendering :optimizelegibility
:margin 0
:font "400 16px / 1.4 Inter, sans-serif"}}})}))
While generated class names are quite descriptive (auix-core-L18-C20
— ns + line + column), we also generate CSS source maps to improve debugging experience.
uix.css
tries to inline constant values and pure expressions to reduce the number of dynamic styles, this is especially useful when you have a set of shared design tokens in code, like colors, font sizes, spacing, etc.
In this example the value of border-color
var will be inlined, as well as (str "1px solid " border-color)
expression. css
macro analyzes the code and evaluates well known functions given that their arguments are constant values.
(def border-color "blue")
(def m-xl 64)
(css {:border (str "1px solid " border-color)
:margin m-xl})
- Pluggable CSS linting
- Server-side rendering on JVM