Combinators for generating CSS

Usage no npm install needed!

<script type="module">
  import dashkiteQuark from 'https://cdn.skypack.dev/@dashkite/quark';



Combinators for generating CSS

Preprocessors, scoped CSS, atomic CSS, and CSS in JS are among the many ways developers and designers have tried to encapsulate, reuse, and manage CSS. Quark represents a new approach: encapsulating CSS in functions, which may then be composed into stylesheets.

css = render styles [
  select "main > article", [
    reset "block"
    margin "bottom left"
    article [ "h1", "p", "lists", "blockquote", "figure" ] ] ]

This will produce CSS that looks like the following:

main > article {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
main > article > h1 {
  font-family: 'sans-serif';
  font-weight: 'bold';
  font-size: '6.4rem';
  line-height: '8rem';
  color: '#111';
main > article > p {
  /* ... and so on  */

Obviously, the Quark code doesn’t look much like CSS, but that’s the point: we encapsulate resuable CSS as functions. In turn, those functions are composed to build up more sophisticated functions. As with Atomic CSS, we start out with simple building blocks. But unlike Atomic CSS, our building blocks are composable functions.

Let’s start with the building blocks. We have three:

  • styles creates a new stylesheet.
  • select places a new selector on the stack.
  • set sets a property.

We also rely on Garden’s pipe and pipeWith functions for composition and Katana to manage our context stack. However, most of the time, you won’t need to worry about them.

With these simple functions, we can already build up arbitrarily complex stylesheets. But it’s a bit tedious, so we define simple combinators that are the Quark equivalent of atomic CSS. For example, this is bold:

bold = set "font-weight", "bold"

This is plain, which provides a simple illustration of composition :

plain = pipe [
  set "font-style", "normal"
  set "font-weight", "normal"
  set "text-decoration", "none"
  set "text-transform", "none"

The set function is curried, so we can also just provide shortcut functions for setting a property:

mt = set "margin-top"

which we can later use in our stylesheet definitions like this:

mt "4rem"

or to build up new combinators, like this:

# margin-bottom large
mtl = mt "4rem"

If we want to be less cryptic, we can introduce lookup tables. The lookup helper makes this easy:

margin = lookup
  "top large": set "margin-top", "4rem"
  "top medium": set "margin-top", "2rem"
  "top small": set "margin-top", "1rem"
  # ... and so on

We can now use margin like this:

margin "top large"

Sometimes you simply want to lookup a value, like a color, and set a property using that value. We can do this using pipe, lookup, and any, which tries functions until one returns a defined value. This example tries a lookup for predefined color values, falling back to the value passed into it:

# colors is a lookup table for color values
color = pipe [
  any [
    lookup colors
  set "color"

Sometimes you want to do a bunch of lookups and compose them together. We can do this with Garden's pipeWith:

article = pipeWith lookup {h1, h2, p, ul, ol, li, blockquote}

which allows us to write:

aritcle [ "h1", "p" ]

Of course, since these are still just functions, we can simply write them out when we need something funky. Here’s a combinator that allows us to express the font size relative to the line-height:

text = curry (lh, r) ->
  pipe [
    set "line-height", lh
    set "font-size", "calc(#{lh} * #{r})"

Similarly, themes are just a set of combinators. Since it’s tedious to pass theme functions to every combinator that might need it, we can bind an entire set at once. Given a collection of related combinators, we can provide a bind function:

{article} = Article.bind {color, type}

The combinators returned from the bind function will now use our theme functions.

Combinators may introduce nested selectors, which allows us to go beyond collections of properties. For example, the Article H1 combinator we referenced in our example introduces a nested selector:

h1 = tee pipe [
  select "> h1", [
    type "heading"
    color "near-black"  

We use tee here so that subsequent combinators won’t end up being applied to our H1 selector.




Create a new stylesheet.


Push a selector onto the stack.


Set a property for a given rule.


Takes a dictionary and returns a function that will return the result of dereferencing its argument using the dictionary.

margins = lookup "none": "none", "small": "1rem", "medium": "2rem", "large": "3rem"
assert.equal "2rem", margins "medium"

Since this is a curried function, you can use it define simple presets:

type = lookup
  heading: pipe [ sans, bold, text (rem 8), 4/5 ]"


Returns a function that, given a value, will apply each given function with that value until one returns a value that isn’t undefined. In combination with lookup, this allows you to implement common patterns like prefixed properties:

width = any [
    stretch: pipe [
      set "width", "-webkit-fill-available"
      set "width", "stretch"
  # fallback...
  set "width"


Given a stylesheet object, returns CSS.


Given a stylesheet function, executes it and calls toString.

assert.equal render styles [ select "main", [ width "90%" ] ],
    "main { width: 90%; }"


px, pct, em, rem, vh, vw

Converts a number into CSS units.

# set width to 20rem
width rem 20

Height and Width

width, height

Set with units, or other CSS value, ex: min-content. Prefixes stretch.

width pct 1/3
width "fit-content(20rem)"

min, max

Modifies width or height.

# set the max width
max width "34em"


Sets the width, min-width, and max-width to ensure the readability of the text. Equivalent to:

width "stretch"
min width "20em"
max width "34em"

Based on Tachyon’s measure classes.



Sets the color property with color name based on the HTML or Tachyon color.

color "white-20"


Sets the background property with a color name based on the HTML or Tachyon color or any valid background shorthand.


italic, bold, underline, strikeout, capitalize, uppercase, plain

Sets the corresponding text property.

sans, serif, monospace

Sets the font family.


Sets the line-height and relative font-size.

# set the line-height to 8rem and the font-size to 5.33rem
text (rem 8), 2/3


Sets font/text properties based on standard presets.

type "extra large heading"


Preset Line Height (rem) Font Size (rem) Font Size (px)
banner 18 14.4 96
extra large heading 14 11.2 74 2⁄3
large heading 10 8 53 1⁄3
heading 8 6.4 42 2⁄3
small heading 7 5.6 37 1⁄3
extra small heading 6 4.8 32
extra large copy 8 5⅓ 35 5⁄9
large copy 7 4⅔ 31 1⁄9
copy 6 4 26 2⁄3
small copy 5 3⅓ 22 2⁄9
extra small copy 4 2⅔ 17 8⁄9

Pixel values are based on a rem size of 6 2⁄3 pixels.



Sets the border using an array of presets. Color will be inherited unless set with borderColor.

border [ "round" ]
border [ "top", "bottom" ]
border [ "pill" ]
  • top, bottom, left, right
  • dotted, dashed, thin, thick, thicker, thickest
  • box, round, rounder, pill
  • any value accepted by color

Background Size

cover, contain

Sets the background-size property.



Set the opacity using a percentage.


Set the opacity to 100%.


Set the opacity to 0%.


rows, columns

Set up a row or column-based container.


Sets a flex container to wrap content. (Not wrapping is the default.)


Presets for a variety of form elements:

  • h1
  • section
  • input




Presets for a variety of resets:

  • body, block, list, blockquote, table