Warning
This project has been archived and will no longer be maintained. There are many alternatives on the market, including:
Powerful styling without leaving Clojure/ClojureScript - f**k CSS.
FkCSS is a minimal CLJ->CSS
library without the weight of CLJSS
and other alternatives.
- Styles scoped by Clojure namespace
- Fonts and animations via
@font-face
and@keyframes
- Concise syntax, but more expressive property values where desired
- Auto-prefixing
- Custom property handlers
- Only a few hundred lines of code
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;
}
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.
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.
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.
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"}})
Use gen-css
to generate CSS for all registered styles.
(def css (ss/gen-css))
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 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.
margin-x/margin-y
shorthandpadding-x/padding-y
shorthandborder-<edge>-radius
shorthand (top/right/bottom/left
)box-shadow
map 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 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.
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.