Websites

The website template is a Hugo-style variant of the page template for building marketing and product sites: you bring the *design* (the HTML/CSS — a Figma export, a Claude artifact, a hand-built theme, an externally-built bundle) and slot wdoc-rendered content into it. The split is deliberate — HTML/CSS for the fancy shell, wdoc for the words.

Deploy the built-in template with default_template = :website, or author your own template (recommended for a real design — see below). Scaffold a ready-to-edit project with wcl init website ./my-site.

§ 1Three new pieces

On top of the ordinary template mechanism (Sites & Templates), the website workflow adds:

§ 2Authoring a layout

A template is a function returning fundamentals. For a website, return one Raw fundamental holding the design, with the slots interpolated — ${c.title}, ${c.content}, and ${wdoc_region(c, "name")} for each region. Use the interpolating heredoc form ($<<HTML):

wcl
template site_main {
  render = fn(c: TemplateCtx) -> list<HtmlFundamental>
    flatten([
      // Head content the layout owns (the site's `stylesheets` /
      // `scripts` add the rest).
      wdoc_head_meta("viewport", "width=device-width, initial-scale=1"),
      [ HtmlFundamental::Raw { html: $<<HTML
        <header class="nav">
          <a class="brand" href="index.html">${c.title}</a>
          <nav><a href="index.html">Home</a></nav>
        </header>
        <section class="hero">${wdoc_region(c, "hero")}</section>
        <main class="content">${c.content}</main>
        <footer class="foot">${wdoc_region(c, "footer")}</footer>
      HTML
      } ],
    ])
}

Heredoc terminator

The closing HTML delimiter must sit alone on its line — HTML } ], on one line is an unterminated heredoc. Put } on the next line.

§ 3Authoring content with regions

A page joined to a :website site renders normally; wrap any part you want to slot separately in a region. Everything else becomes ${c.content}.

wcl
page index { sites = [:web]  start = true
  region "hero" {
    h1 "Build something great"
    p "The design lives in CSS; the words live here."
  }

  h2 "About this page"
  p "Everything outside a region lands in the default content slot."

  region "footer" {
    p "© Built with WCL + wdoc."
  }
}

§ 4Wiring the design's assets

Ship the design's stylesheet and script with the site. assets copies a folder verbatim into the output (resolved relative to the document); stylesheets / scripts / fonts link files into every page <head> (hrefs are emitted verbatim, so they may be a copied asset, a shipped file, or a URL):

wcl
site web {
  default_template = :site_main
  title       = "Acme"
  root        = true
  assets      = ["assets"]                 // copies ./assets/ → _site/assets/
  stylesheets = ["assets/site.css"]        // <link> in every <head>
  scripts     = ["assets/app.js"]          // deferred <script> in every <head>
  fonts       = ["https://fonts.example/Inter.css"]
}

Because assets copies the folder unchanged, the same field takes an externally-built bundle — point it at a Vite or webpack dist/ and reference the hashed files it emits.

§ 5Head from the layout

When a region or the site fields aren't enough, a layout (or a part it calls) can emit <head> content directly by returning a Head fundamental at the top level of its list. The wdoc_head_* helpers wrap the common cases:

HelperAdds to the page head
wdoc_head_stylesheet(href)<link rel="stylesheet" href>
wdoc_head_script(src)deferred <script src>
wdoc_head_font(href)a web-font <link rel="stylesheet">
wdoc_head_meta(name, content)<meta name content>
wdoc_head_raw(html)verbatim head HTML

Top level only

A Head is hoisted only when returned at the layout's top level. A Head nested inside the body renders to nothing — it never leaks into <body>.

§ 6The default layout

default_template = :website uses a built-in, theme-aware shell (sticky header with the title + menu, optional banner and hero regions, the content with an optional sidebar region, and a footer region). When the site sets search = true / theme_toggle = true, the header grows a right-side controls cluster with the search box and the standard light/dark toggle; with neither flag the header is unchanged. It's a fine starting point and a reference to copy: wdoc_website_layout(c) is its whole body, composed from wdoc_part_website_css(), website_header(c), and the region parts.

§ 7Scaffold

wcl init website ./my-site generates a complete project: main.wcl (the site + head assets), theme.wcl (a custom raw-HTML layout), components.wcl (starter landing components — hero, feature cards, steps, footer — to edit or replace), content.wcl (the page, built from those components + regions), and assets/site.css + assets/app.js. Build it with wcl wdoc build my-site/main.wcl --out my-site/_site.

§ 8Site fields

PropertyTypeRequiredDescription
nameidentifiernoOptional site name (the label slot). Required with more than one site; names the output subdirectory referenced by Page.sites.
rootboolnoWhen true, this site renders at / (others go under /<site>/).
default_templatesymbolno:webpage (header + top nav), :book (sidebar + TOC), :presentation (slide deck), or :ai_skill (skill folder, Markdown-only — build with wcl wdoc skill).
titleutf8noSite title shown in the header / sidebar.
summaryutf8noOne-line site description. Surfaced by included_sites(...) (not rendered by the built-in templates).
iconutf8noPath to a favicon image (svg/png/ico), resolved relative to the document and copied into _wdoc/. Absent ⇒ a default WCL icon is used.
stylesheetslist<utf8>noExternal stylesheet hrefs added to every page <head> as <link rel="stylesheet"> (verbatim — URL, copied asset, or shipped file).
scriptslist<utf8>noScript srcs added to every page <head> as deferred <script src=… defer> (verbatim hrefs).
fontslist<utf8>noWeb-font stylesheet hrefs added to every page <head> as <link rel="stylesheet"> (e.g. a Google Fonts URL).
assetslist<utf8>noFolders copied verbatim (recursively) into the site output — e.g. a Vite dist/. Resolved relative to the document; reference copied files by their output path.
theme_toggleboolnoWhen true, adds a light/dark toggle button.
searchboolnoWhen true, adds a client-side full-text search box (book and webpage templates).
themesymbolnoSymbol naming a colour theme block (:nord …) — see Styling.
accentsymbolnoSymbol naming the accent hue (:red..:pink); default :blue.
ui_themesymbolnoUI theme for wf_* wireframe elements (the mocked app's theme), separate from the document theme. Falls back to theme.
ui_accentsymbolnoAccent hue for wf_* wireframe elements; falls back to accent.
ui_modesymbolnoMode for wf_* wireframe elements — :dark (default) or :light.

Child blocks

SlotAcceptsMultipleDescription
toctocnoChapter tree for the book template.
menumenunoTop navbar entries for the webpage template.
sidebar_footersidebar_footernoPinned buttons at the bottom of the book sidebar.
deckdecknoSlide grid for the presentation template.
skillskillnoSkill metadata for the :ai_skill target — populates SKILL.md's front matter.