Variables, mixins and JavaScript

css-pp variables do more than hold a value. A variable can be a plain value, a mixin with parameters, or a JavaScript expression evaluated in the page. Add a ! and it recalculates whenever the window is resized.

Defining and using a variable#

Define a variable on its own line, outside any rule, with a $, a name and the value in braces. Use it with $name. Definitions can go anywhere in the stylesheet, before or after the rules that use them.

CSS
$brand{#0ea5e9}
$gap{24px}

.button { background: $brand; margin-bottom: $gap; }

Wrap the name in brackets when it touches other characters, so css-pp can tell where the name ends:

CSS
$size{1280}

.hero { background-image: url(/img/hero-$[size].jpg); }
.box  { width: $[size]px; }

Native CSS custom properties can't do this. A url() can't be assembled from var() pieces, but a css-pp variable is substituted as text before the browser sees it.

JavaScript expressions#

If a value is valid JavaScript, css-pp evaluates it; otherwise it is used as written. That makes calculations and logic one line long:

CSS
$half{css.vars.w() / 2}          /* half the viewport width, as a number */
$cols{Math.max(1, Math.floor(css.vars.w() / 320))}

.panel { width: $[half]px; }
.grid  { grid-template-columns: repeat($cols, 1fr); }

A value can hold several statements. The value of the last expression is the result:

CSS
$img!{
  if (css.vars.w() > 1600) "1920";
  else if (css.vars.w() > 1024) "1600";
  else "1024";
}
.hero { background-image: url(/img/hero-$[img!].jpg); }

These helpers are available inside expressions:

Helper Returns
css.vars.w() Viewport width in pixels
css.vars.h() Viewport height in pixels
css.vars.w('.selector', n) Width of the n-th element matching the selector (default 0)
css.vars.h('.selector', n) Height of the n-th matching element

Any global JavaScript works too, such as Math, Date, document or your own functions.

Security note. Expressions run with eval(), just like code in a <script> tag. Only process stylesheets you control. If your Content Security Policy blocks 'unsafe-eval', plain values, nesting and the state and content selectors still work, but expressions and built-ins such as $[index] do not.

Mixins with parameters#

A variable can hold a block of declarations. List default parameter values in parentheses and refer to them as &a, &b, &c and so on, in order:

CSS
$shadow(0, 8px){box-shadow: &a &b 24px rgb(0 0 0 / .18)}
$truncate(1){display: -webkit-box; -webkit-line-clamp: &a; -webkit-box-orient: vertical; overflow: hidden}

.card    { $shadow(0, 12px); }
.excerpt { $truncate(3); }
.title   { $truncate; }          /* uses the default: 1 line */

Arguments are positional and comma-separated. When you pass arguments, pass all of them; the defaults apply only when you call the mixin without parentheses.

The current selector: &self#

Pass &self as an argument and it becomes the selector of the rule that uses the variable, as a quoted string. That lets a variable measure or count the elements it is styling:

CSS
$count!(&self){document.querySelectorAll(&a).length}

/* split the row evenly, however many columns there are */
.cols > div { width: calc(100% / $[count!]); }

The ! makes the count update after the page has loaded and when the window is resized (see below). This particular one is also built in as $[count].

Auto-updating variables#

End a variable's name with ! and css-pp recalculates it after the page finishes loading, whenever the window is resized and when the orientation changes. Every rule that uses it updates in place. Include the ! when you use it:

CSS
$vw!{css.vars.w()}
$vh!{css.vars.h()}

.full   { height: $[vh!]px; }
.label::after { content: "$[vw!]px wide"; }

Recalculation is throttled by css.defaults.resizing (100 ms by default).

Variables that reference variables#

A value can use other variables. css-pp resolves them before evaluating:

CSS
$base{16}
$large{$base * 1.5}

h1 { font-size: $[large]px; }

Arrays: one value per element#

If a variable evaluates to an array, each matched element gets the next item. The values map to element positions, like :nth-child():

CSS
$palette{['#ef4444', '#f59e0b', '#10b981', '#3b82f6']}

.tags > li { background: $palette; }

For values computed from an element's position, $[index] is usually simpler.

Changing variables at runtime#

Call css.set(sheet, name, value) to change a variable after the page has loaded. Every rule that uses it updates immediately, without reloading the stylesheet. sheet is the stylesheet name: the file name without its extension, or the id you passed to css.open('#id').

JavaScript
css.set('site', 'brand', '#e11d48');   // recolor every rule that uses $brand
css.get('site', 'brand');              // "#e11d48"

Pass values as strings. css.set() ignores empty values such as 0 or '', so write '0' instead. More in the JavaScript API.

Comments#

Both comment styles are supported:

CSS
/* block comment */
.a { color: red; } // line comment, to the end of the line

A // counts as a comment only at the start of a line or after a space, ;, { or }. URLs such as url(https://cdn.example.com/a.png) and url(//cdn.example.com/a.png) are left alone.