Powerful styling without leaving Clojure/ClojureScript - f**k CSS.
FkCSS is a minimal CLJ->CSS library without the weight of CLJSS
and other alternatives.
Features
- Styles scoped by Clojure namespace
- Fonts and animations via
@font-faceand@keyframes - Concise syntax, but more expressive property values where desired
- Auto-prefixing
- Custom property handlers
- Only a few hundred lines of code
Usage
Most useful things are defined in fkcss.core, so require that in
your module.
(ns ... (:require [fkcss.core :as ss]))
Styles are represented by maps of properties and nested style maps.The key determines how FkCSS interprets a value in the style map:
- Keywords ending in
>denote a nested tag style - Keywords ending in
>>denote a nested pseudo-element style - Keywords ending in
?denote conditional properties - Strings denote some number of whitespace delimited classes
Here's an example:
{:div>
{:hovered?
{:color "red"}
:before>>
{:color "blue"}
"foo bar"
{:color "pink"}}}Which yields:
div:hover { color: red; } div::before { color: blue; } div.foo.bar { color: pink; }
Use a vector for more concise nesting.
{[:div> :before>>]
{:color "blue"}}Use a map for more concise sub-properties.
{:div>
{:margin {:left "1rem" :right "1rem"}}}Yields:
div { margin-left: 1rem; margin-right: 1rem; }
defclass
Use defclass to define namespace scoped classes, it'll bind the
given var name to the name of the generated class.
(ss/defclass my-class {:color "red" :hovered? {:color "blue"}}) (defn my-component [] [:div {:class my-class} "Hello"])
Properties at the root of a defclass apply to elements with the
defined class. Properties in a nested node within a defclass
apply to elements within an element with the defined class.
defanimation, reg-animation!
Namespace scoped animations can be defined with defanimation, or
animations with custom names can be registered with reg-animation!.
(ns example-ns) (ss/defanimation example-1 {:from {:opacity 0} :to {:opacity 1}}) (ss/reg-animation! "example-2" {0 {:opacity 0} 1 {:opacity 1}}) (ss/reg-animation "example-3" {"0%" {:opacity 0} "100%" {:opacity 1}})
This yields.
@keyframes example-ns-example-1 { from { opacity: 0; } to { opacity: 1; } } @keyframes example-2 { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes example-3 { 0% { opacity: 0; } 100% { opacity: 1; } }
Nested nodes aren't allowed in animation property maps.
reg-font!
Add fonts to the generated CSS with reg-font!.
(ss/reg-font! "Tangerine" [{:src "url(angerine-Regular.ttf) format('opentype')" :font-weight 400 :font-style "normal"} {:src "url(Tangerine-Bold.ttf) format('opentype')" :font-weight 700 :font-style "normal"}])
This yields.
@font-face { src: url(/fonts/Tangerine-Regular.ttf) format('opentype'); font-weight: 400; font-style: normal; font-family: 'Tangerine'; } @font-face { src: url(/fonts/Tangerine-Bold.ttf) format('opentype'); font-weight: 700; font-style: normal; font-family: 'Tangerine'; }
A single map can be given instead of the vector when only
one @font-face is needed.
reg-style!
Use reg-style! to register global styles. Properties at the
root of such style maps apply to all elements. reg-style!
requires a key in addition to the style map itself so it can do
the right replacement/cleanup when namespaces are reloaded.
(ss/reg-style! ::global {:a> {:color "blue" :text-decoration "none"}})
gen-css
Use gen-css to generate CSS for all registered styles.
fkcss.cljs/mount!
FkCSS can generate the CSS and add it to a style tag in
the DOM in one go, if running in a browser.
(ns ... (:require [fkcss.cljs :as ss-cljs])) (ss-cljs/mount!)
Use unmount! to remove it.
Property Handlers
Property handlers allow for custom translations from
FkCSS properties to CSS properties. FkCSS comes with
some builtin handlers in fkcss.render/default-property-handlers which handle vendor prefixing and allow for some conveniences like margin-x/margin-y properties. Custom handlers
can be passed into gen-css, but be sure to merge
them with the defaults if you want to keep the bultin ones.
(ss/gen-css {:property-handlers (merge fkcss.render/default-property-handlers {...custom handlers...})})
The map of property handlers should look like this:
{:property-name
(fn [property-value]
{:props
{:property-name property-value
:-webkit-property-name property-value
:-ms-property-name property-value}})}Where the :props map in the handlers result gives the
final CSS properties.
Built-in Property Handlers
margin-x/margin-yshorthandpadding-x/padding-yshorthandborder-<edge>-radiusshorthand (top/right/bottom/left)box-shadowmap value with explicit keys#{:offset-x :offset-y :inset? :blur-radius :spread-radius}- Vendor prefixes for appropriate properties
Example of more expressive box shadow syntax.
{:box-shadow {:inset? true :offset-x 0 :offset-y 2}}Predicates
Predicates allow for conditional rules without depending on how the test is implemented. Predicates are keys ending in ? within a style map. FkCSS has builtin predicates for the most
common cases, but custom predicates can also be given in gen-css.
(ss/gen-css {:predicates (merge fkcss.render/default-predicates {...custom predicates...})})
The predicates map should look like:
{:predicate-key?
{:selector <css-selector>
:exec <boolean-function>
:query <css-query>}}Any predicate field can be omitted, in which case it simply won't apply.
The :selector field should give a CSS selector to limit where the conditional rules will apply. For example :hover or .selected.
The :exec field should give a function to be executed when
the CSS is being generated; if the function returns false
then the conditional CSS simply won't be generated.
The :query field should give a @media or @supports query
to predicate the rule on.
Built-in Predicates
For CLJ and CLJS:
:hovered?, :active? :focused?, :focus-visible?,
:enabled?, :disabled?, :visited?, :checked?,
:expanded?, :current?, :screen-tiny?, :screen-small?,
:screen-large?, :screen-huge?, :pointer-fine?,
:pointer-coarse?, :pointer-none?, :hoverable?
For CLJS only: :touchable?
See fkcss.render/default-predicates for how these or implemented
and as examples for custom predicates.