Skip to content

Styling a vzk site

Use this guide when changing how a vzk site looks: the classes its markup wears, the site’s stylesheet and tokens, and a file’s own <style>. Start from describe_vocabulary topic=looks for a whole stylesheet.

Every class a file writes out is compiled into the site’s stylesheet, so a class name is one whole literal. A name joined from a value, such as "text-" + size or class="text-{size}", is refused.

<article class={["card", lead && "card-lead"]}>

class takes a literal, or a list whose missing and false entries drop out. Where a class varies with a value, choose between whole names with a lookup:

<h1 class={{ calm: "soft", bold: "loud" }[fields.tone]}>{fields.cta}</h1>

A class whose names cannot be read before a page is drawn (a row’s value, a function’s answer) is a warning, VZK0304: no rule is made for those names, so the element may be drawn unstyled. A visitor’s text is never a class.

A class given to a component lands on its root element: <Frame class="aspect-square rounded-surface" alt="Dan beside his van" />.

The site’s scale is its own stylesheet.css: read it with read_files. Prefer its tokens to fixed numbers:

  • spacing, used by utilities such as gap-md and py-xl;
  • page rhythm such as px-gutter, pt-band-top and pb-band-bottom;
  • measures such as max-w-page, max-w-headline and max-w-lead;
  • colour roles such as bg-surface, bg-raised, text-ink, text-body and text-muted;
  • type registers such as text-display, text-title, text-lead, font-heading and font-strong.

A component class is a decision the look owns: btn-primary, btn-compact, btn-outline, btn-block, link-underline, link-quiet, frame, frame-empty and eyebrow. A file never branches on which look is active: a new look changes the stylesheet while the markup keeps the same tokens and component classes. The primary responsive breakpoint is md:.

<div id="prices" class="px-gutter md:px-gutter-lg pt-band-top md:pt-band-top-lg max-w-page mx-auto">
<p class="eyebrow text-muted">Prices</p>
<h1 class="font-heading font-strong text-display md:text-display-lg text-ink text-balance">What it costs, before I start.</h1>
</div>

stylesheet.css is one whole file, replaced by write_files on a branch: font imports, tokens in @theme, look-owned components in @utility, base rules in @layer base, and any site-wide rule utilities cannot express. @plugin, @config, @source, @reference and non-URL imports are refused, and so is Tailwind’s built output. It must compile before the write is accepted.

A file’s <style> is CSS for the markup of that file and nothing else. It comes after the markup:

<script server>
const { tone = "plain" } = props
</script>
<div class={["callout", tone == "warm" && "callout-warm"]}>
<p class="callout-title"><slot name="title">Note</slot></p>
<p><slot /></p>
</div>
<style>
.callout {
border-left: 4px solid currentColor;
padding: 0.5rem 1rem;
p {
margin: 0;
}
}
.callout-warm {
border-color: #b45309;
}
.callout-title {
font-weight: 700;
}
</style>
  • It is plain CSS, with nesting. A rule reaches only the markup written in its own file, wherever that is drawn, so two files may both style .card.
  • A component’s root is styled from the file that uses it by giving the component a class: <Callout class="wide" /> and .wide { ... }.
  • :global(...) is the way out. .prose :global(p) reaches what markdown(...) draws, and .menu:global(.open) a class a script adds.
  • It may hold style rules, @media, @supports, @container, @layer with a block, @starting-style, @keyframes, and @apply inside a rule. @import, @font-face and @property belong in stylesheet.css.
  • A selector that matches nothing in its file is a warning, and a style that cannot be read as CSS is an error.

Use a file’s style for a rule one file alone owns, and the stylesheet for what several files share. style="width: {n}%" on an element takes a number or plain text as each value, for a visual value one element alone owns.