Component recipes

Define named groups of rules, compose them, apply them at breakpoints, and override them per element.

1 min readv1

A recipe is a named attribute string in components. Referencing it by name (no =) expands it.

Defining#

TypeScript
components: {
  btn: "d=inline-flex; ali=center; g=8px; p=8px 16px; bdrd=8px; ftw=600; cur=pointer; ts=all .15s",
  "btn-primary": "btn; bgc=$color.brand; cl=#fff; hover:bgc=$color.brand-hover",
  "btn-danger":  "btn; bgc=#dc2626; cl=#fff; hover:bgc=#b91c1c",
  card: "p=24px; bdrd=12px; bd=1px solid #e5e7eb; dark:bd=1px solid #333",
  "card-hover": "card; ts=transform .2s; hover:tf=translateY(-4px)",
}

Composition#

A recipe can reference other recipes; they expand recursively, and modifiers on the outer reference flow into every nested rule. A recipe that refers to itself, directly or indirectly, is reported once as a circular reference.

Using#

HTML
Badge
<div nib="d=flex; g=10px; flxw=wrap">
  <button nib="btn-primary">Primary</button>
  <button nib="btn-outline">Outline</button>
  <span nib="badge">Badge</span>
</div>

Overriding#

Add rules after the recipe. For the same property, the rule that comes later in the attribute wins:

HTML
<button nib="btn-primary; bgc=#0891b2; hover:bgc=#0e7490">Teal variant</button>

Output: one class per recipe#

A recipe becomes a single class that holds all of its declarations, including its states:

CSS
.nib-1nfp073 { padding: 24px; border-radius: 12px }
.dark .nib-1nfp073 { border: 1px solid #333 }
.nib-1nfp073:hover { transform: translateY(-4px) }

With a breakpoint#

HTML
<section nib="md:card">

Recipes in dynamic code#

Recipes work everywhere the engine runs — including useNib("btn-primary"), v-nib, use:nib and [nib]. They do not exist as literal class names.

A recipe library to start from#

TypeScript
components: {
  container: "w=100%; mxw=1200px; mi=auto; px=24px",
  stack: "d=flex; flxd=column; g=16px",
  row: "d=flex; ali=center; g=12px",
  center: "d=grid; pli=center",
  input: "w=100%; p=8px 12px; bdrd=8px; bd=1px solid #cbd5e1; otl=none; focus:bdc=$color.brand; focus:bxsh=0 0 0 3px rgba(79,82,217,.2)",
  badge: "d=inline-flex; ali=center; p=2px 10px; bdrd=999px; fts=12px; ftw=600",
  "sr-only": "pos=absolute; w=1px; h=1px; p=0; m=-1px; ovf=hidden; clip=rect(0,0,0,0); whs=nowrap; bd=0",
}

Next steps#