styles.md

August 22, 2026 ยท View on GitHub

Styles

defstyle

Use defstyle for static reusable styles, and combine it with :class-name on elements.

๐Ÿ“š Documentation Index

Styles are represented in HashMap so it's very trival to extend with merge and if:

def style-a $ {}
  :line-height 1.6
  :color (respo.util.format/hsl 0 0 80)

def style-b $ merge style-a
  {}
    :font-size "|16px"
  if (> size 0)
    {}
      :font-weight "|bold"

The keys have to be keywords, the values can be either of keywords, numbers or strings. Also I prepared a function called hsl as a helper.

In Respo, style updates are defined with direct accessing to el.style:

defn add-style (target op)
  let
      style-name (dashed->camel (name (key op)))
      style-value (val op)
    aset (.-style target) style-name style-value

defn rm-style (target op)
  let
      style-name (dashed->camel (name op))
    aset (.-style target) style-name nil

defn replace-style (target op)
  let
      style-name (dashed->camel (name (key op)))
      style-value (val op)
    aset (.-style target) style-name style-value

For convenience, I collected my frequent used styles in a package called respo-ui. You can find more in the source code.

Static Styles

A macro respo.css/defstyle has been added for add <style>...</style> referred with :class-name. It's less dynamic, which means you cannot pass parameters to styles in this way. It will insert into <head>...</head> a <style>...</style> element. It runs before main! and reload!.

define style:

; ns app.demo
  :require
    respo.css :refer $ defstyle

respo.css/defstyle style-input $ {}
  |& $ {} (:font-size |16px)
    :line-height |24px
    :padding "|0px 8px"
    :outline :none
    :min-width |300px
    :background-color "|#f0f0f0"
    :border :none

Use string selectors such as |&, |&:hover, or |input&. Avoid writing bare symbol & as the selector key in defstyle.

When to use defstyle

defstyle works best for static styles: fixed font sizes, colors, gaps, borders, paddings, hover rules, and reusable layout rules. Keep runtime-dependent values in :style, for example dynamic width, position, height, or values computed from state.

defstyle style-card $ {}
  |& $ {} (:padding |12px 16px)
    :border-radius |12px
    :background-color $ respo.util.format/hsl 0 0 100

div $ {}
  :class-name style-card
  :style $ {}
    :width $ &max 200
      + 24 $ text-width title 16 |BlinkMacSystemFont

text-width returns 0 when Canvas is unavailable, so the same immutable style calculation is safe during SSR and Node.js tests. A nil inline style value is normalized to an empty string, which clears the CSS declaration without serializing nil into DOM or HTML output.

Extract from :style

When moving an inline style map into defstyle, keep the change mechanical:

  1. locate the target props node;
  2. extract only the static style map;
  3. wrap the extracted map with defstyle using |& as the selector key;
  4. replace the original :style usage with :class-name.
div $ {}
  :class-name $ str-spaced css/row style-preview-row
defstyle style-preview-row $ {}
  |& $ {} (:gap |8px) (:flex-wrap :wrap)
    :align-items :flex-start

Be careful with string values like |4px 10px, |1px solid , or long text literals. If the code is generated or transformed by CLI tools, prefer the exact serialized form that the tool prints, instead of hand-editing token boundaries.

Calcit CLI workflow

When the source is stored in calcit.cirru, a stable workflow is:

# 1. locate the inline style leaf
calcit query search ':style' --filter app.comp.container/comp-env-card

# 2. inspect the surrounding props and find the actual style map path
calcit tree show app.comp.container/comp-env-card --path '3.2.4.2'

# 3. extract the style map itself, not the :style leaf
calcit edit split-def app.comp.container/comp-env-card --path '3.2.4.2.1.2.1' --name style-env-card-preview

After extraction, the new definition is often still a raw map. Wrap it into a defstyle definition:

calcit query def app.comp.container/style-env-card-preview

If the style contains tricky string values, prefer a snippet file instead of shell inline code:

calcit edit def app.comp.container/style-env-card-preview --overwrite --file .calcit-snippets/style-env-card-preview.cirru

Then switch the original node from :style to :class-name:

calcit tree replace app.comp.container/comp-env-card --path '3.2.4.2.1' --code 'quote $ {}
  :class-name $ str-spaced css/row-middle css/gap8 style-env-card-preview'

Validate the extraction after each batch:

calcit query search ':style' --filter app.comp.container/comp-env-card
calcit js

|& will be replace by a string of className. So if you want to add rules for :hover, use the string selector |&:hover.

input $ {} (:placeholder "|Text")
  :value $ :draft state
  :class-name style-input
  :style $ {}
    :width $ &max 200
      + 24 $ text-width (:draft state) 16 |BlinkMacSystemFont

Internally, a definition of respo.app.comp.task/style-done generates className as style-done__respo_app_comp_task(with help of a new API &get-calcit-running-mode), so it's still unique across files and modules. During hot code swapping, the hashmap will be compared to previous hashmap to decide whether or not update.

Since it's not a GC-based solution, <style>..</style> created before hot code swapping remains in the DOM tree and will alway occupy memory. This solution is far from perfect, but it's supposed to cover current needs in Respo.

Node.js rendering

During HTML rendering in Node.js , styles are collected in a list in respo.css/*style-list-in-nodejs. It's an unstable design but you can get styles from it.