Browse Learn

Static Sites

Build a complete static site from one configuration: your pages and shared partials, your own CSS and JavaScript, a theme, static files and a live preview.

Get the tool

In a Layout UI checkout, run ./layout from the repository root. Elsewhere, download a release archive for your platform and keep its library directory beside the layout binary. Release downloads require access to the Layout UI repository. layout version shows which library a command will use.

layout version

Organize your site

Keep deployable sources in one root directory. Each page includes the <!-- layout:assets --> marker in its head, where the build inserts component styles and scripts.

my-site/
  layout.json
  public/
    index.html
    about/index.html
    404.html
    _partials/header.html
    css/site.css
    js/site.js
    fonts/brand.woff2
    images/
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>About · My Site</title>
  <!-- layout:assets -->
  <link rel="stylesheet" href="/css/site.7f8593d739202a04.css">
</head>
<body>
  <!-- layout:include header.html current="about" -->
  <main class="container stack">
    <h1>About</h1>
    <a class="btn" href="/contact">Get in Touch</a>
  </main>
  <script src="/js/site.js" defer></script>
</body>
</html>

Configure the build

Create layout.json beside the root. Pages resolve and emit relative to root, so public/about/index.html becomes dist/about/index.html.

{
  "schemaVersion": 1,
  "root": "public",
  "pages": ["**/*.html"],
  "copy": true,
  "exclude": ["drafts/**"],
  "siteAssets": { "inline": ["css/site.css"], "hash": true },
  "splitting": "shared",
  "outDir": "dist",
  "report": ".layout/build.json"
}
  • pages accepts paths and patterns. * stays within one folder, ** spans folders and !pattern removes matches.
  • copy copies images, fonts and other files from the root. exclude skips files from pages and copies.
  • report keeps the build report out of the deployable output.
  • Discovery adds only the components your HTML uses. Plain buttons get Button styles and no JavaScript.

Share markup with partials

Put shared headers, footers and repeated blocks in public/_partials. Parameters fill {{name}} placeholders with escaped text. Partials are never copied to the output.

<header class="container cluster justify-between">
  <a href="/">My Site</a>
  <nav aria-label="Main" data-current="{{current}}">
    <a href="/about">About</a>
    <a href="/contact">Contact</a>
  </nav>
</header>

Inline or hash your CSS and JavaScript

siteAssets.inline turns the listed stylesheets and scripts into <style> and <script> elements. siteAssets.hash writes other referenced files with content hashes so hosts can cache them long term. Production builds minify both. Keep deferred scripts at the end of the body when inlining them.

Set a theme

A theme sets one fixed mode, color tokens, corners and self-hosted WOFF2 fonts. The build adds @font-face rules, font preloads and the page’s data-theme. Custom tokens such as code become CSS custom properties.

"theme": {
  "mode": "dark",
  "colors": {
    "bg": "#0a0a0b", "surface": "#111113", "soft": "#141417",
    "ink": "#ededef", "muted": "#a1a1aa", "line": "#26262b",
    "accent": "#5ee38a", "accent-hover": "#8cf0ab", "tint": "#12301d",
    "on-accent": "#04210f", "code": "#b4f0c6"
  },
  "radius": 10,
  "fonts": [
    { "family": "Brand", "src": "fonts/brand.woff2", "weight": "400 700", "preload": true }
  ],
  "fontFamily": "\"Brand\", ui-sans-serif, system-ui, sans-serif",
  "contrast": [["code", "bg"]]
}

Text colors must reach 4.5:1 against their backgrounds. Add your own pairs to contrast. A failing pair stops the build before any output changes:

layout: theme contrast check failed for 1 pair:
  muted #4a4a50 on bg #0a0a0b: 2.37:1 (needs 4.5:1)

Build and preview

Run these from the folder that contains layout.json. layout dev builds, serves the output at localhost:8080 and rebuilds when your sources, configuration or the library change. Build errors appear in the page until you fix them. Deploy the dist directory from layout build.

layout dev --port 8080
layout build --json

Each build removes outputs it no longer produces and waits for any other build of the same directory. Discovery reads configured HTML, not JavaScript-created markup or framework templates; declare runtime-only components in the configuration.

Deploy from CI

Download a pinned release in your workflow instead of committing build output. Release downloads need a token with read access to the Layout UI repository, stored as a secret such as LAYOUT_UI_TOKEN. Pin the archive checksum from the release’s SHA256SUMS.

env:
  LAYOUT_VERSION: 0.1.0
  LAYOUT_TARGET: x86_64-unknown-linux-musl
  LAYOUT_SHA256: <checksum from the release SHA256SUMS>
steps:
  - uses: actions/checkout@v4
  - name: Install Layout UI
    env:
      GH_TOKEN: ${{ secrets.LAYOUT_UI_TOKEN }}
    run: |
      name="layout-$LAYOUT_VERSION-$LAYOUT_TARGET"
      gh release download "layout-v$LAYOUT_VERSION" --repo vividmachine/layoutui --pattern "$name.tar.gz" --dir "$RUNNER_TEMP"
      echo "$LAYOUT_SHA256  $RUNNER_TEMP/$name.tar.gz" | sha256sum --check
      tar -xzf "$RUNNER_TEMP/$name.tar.gz" -C "$RUNNER_TEMP"
      echo "$RUNNER_TEMP/$name" >> "$GITHUB_PATH"
  - run: layout build --json

Build configuration guide