Docs
Next

HTML template

A plain HTML starter that uses Systhema's tokens, utility classes and vanilla JavaScript bundle.

On this page

The HTML template is for static landing pages and small marketing sites that don't need React or any framework. Use it for design tokens and CSS utility classes through Tailwind CSS v4. Its vanilla JavaScript listeners have a known browser load failure.

PrerequisitesLink to this section

  • Node.js 20.9 or newer and pnpm 10.
  • A GitHub personal access token (classic) with the read:packages scope. See Registry access.

Quick startLink to this section

1. Create the projectLink to this section

Create a folder with this package.json, plus the index.html, systhema.config.js and src/style.css files described under Project structure and How it works:

package.json
{
  "name": "my-static-site",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "sync": "systhema-core sync",
    "build": "tailwindcss -i ./src/style.css -o ./dist/style.css --optimize",
    "dev:css": "tailwindcss -i ./src/style.css -o ./dist/style.css --watch --optimize",
    "dev:serve": "serve -l 3000 .",
    "dev": "concurrently \"pnpm run dev:css\" \"pnpm run dev:serve\""
  },
  "dependencies": {
    "@systhemaui/core": "^1.7.5",
    "@tailwindcss/cli": "^4.3.3",
    "tailwindcss": "^4.3.3"
  },
  "devDependencies": {
    "concurrently": "^10.0.5",
    "serve": "^14.2.4"
  }
}

2. Configure registry accessLink to this section

Add the .npmrc and GITHUB_TOKEN from Registry access, then export the token.

3. Install dependenciesLink to this section

pnpm install

The template's package.json pulls in:

  • @systhemaui/core, design tokens, the Tailwind plugin, and the bundled JS.
  • @tailwindcss/cli and tailwindcss, Tailwind v4 CLI.
  • concurrently and serve, for the dev workflow.

4. Add design tokensLink to this section

Place exported token JSON files in src/tokens/. They come out of the Figma plugin.

5. Sync tokens and typesLink to this section

pnpm sync   # = systhema-core sync

Re-run this whenever tokens or systhema.config.js change.

6. Start the dev serverLink to this section

pnpm dev

This runs two processes in parallel via concurrently:

  • pnpm dev:css, Tailwind in watch mode (@tailwindcss/cli rebuilds dist/style.css on change).
  • pnpm dev:serve, serve on port 3000 to serve the static files.

Open http://localhost:3000 to view the page.

For one-off builds:

pnpm build   # = tailwindcss -i ./src/style.css -o ./dist/style.css --optimize

Project structureLink to this section

my-static-site/
├── index.html                # entry HTML; loads dist/style.css and the Systhema JS bundle
├── package.json
├── systhema.config.js        # Systhema configuration (CommonJS)
└── src/
    ├── style.css             # Tailwind entry; @import '@systhemaui/core/tailwind'
    └── tokens/               # design-token JSON exported from Figma
        └── manifest.json     # token index

How it worksLink to this section

Tailwind entryLink to this section

Use paths relative to your stylesheet; the bundled template's monorepo source path does not exist in a consumer project:

src/style.css
@import '@systhemaui/core/tailwind';

@source '../index.html';
@source '../**/*.html';
@source './tokens/';

/* Custom CSS for this project goes in the `app` layer, */
/* which follows the generated component and utility layers. */
@layer app {
  @media (min-width: theme('screens.md')) {
    .accordion-title {
      @apply flex-row-reverse justify-end;
    }
  }
}

The single @import '@systhemaui/core/tailwind' brings in Tailwind v4 plus all of Systhema's utilities, components, and design-token-derived classes. The @source directives scan your HTML and token files for class names. Core supplies its component safelist when packages.react is enabled. Project-specific overrides belong in @layer app { … }. See Cascade layers for the full layer order.

systhema.config.jsLink to this section

Use CommonJS and enable the component safelist with packages.react: true. This does not install React:

systhema.config.js
const manifest = require('./src/tokens/manifest.json')

/** @type {import('@systhemaui/core').SysthemaConfig} */
module.exports = {
  manifest,
  spacing: [
    {
      function: '%value% / %screen% * 100vw',
      include: ['grid', 'gap', 'container', 'section', 'article', 'feature'],
      exclude: ['feature.mediaMinHeight'],
    },
    {
      function: '--spacing(%value% / 4)',
      include: false,
      exclude: '*borderWidth',
      swap: true,
    },
  ],
  // Mandatory companion to the fluid rule above: it expresses every layout token
  // as a ratio of its breakpoint's design width, and core ships no
  // responsiveSizing values for xl/2xl/3xl — so without these the `lg` ratios
  // would scale unbounded past 1192px. See core.md → spacing.
  customTokens: {
    responsiveSizing: {
      xl: {
        grid: { default: { count: '24', gap: '20px', margin: '142px' } },
        container: { width: '1156px', margin: '{grid.default.margin}' },
        section: { paddingY: '96px' },
        article: { paddingX: '147px' },
      },
      '2xl': {
        grid: { default: { count: '24', gap: '24px', margin: '300px' } },
        container: { width: '1320px', margin: '{grid.default.margin}' },
        section: { paddingY: '120px' },
        article: { paddingX: '168px' },
      },
      '3xl': {
        grid: { default: { count: '24', gap: '24px', margin: '620px' } },
        container: { width: '1320px', margin: '{grid.default.margin}' },
        section: { paddingY: '120px' },
        article: { paddingX: '168px' },
      },
    },
  },
  packages: {
    react: true,
  },
}

The packages.react: true flag is what enables the safelist for component classes (button-primary, card, accordion, etc.), even though there's no React in the project, the same class names work on plain HTML elements.

Using Systhema utility classesLink to this section

Apply Systhema's classes directly on HTML elements. Use the class structure documented for each component:

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Company site</title>
    <link rel="stylesheet" href="dist/style.css" />
  </head>
  <body class="bg-layout-main" data-theme="default">
    <a class="skip-link" href="#main-content">Skip to content</a>
    <header class="header header-sticky">
      <div class="header-container container">
        <a href="/" class="header-logo">
          <span class="text-xl font-bold">Systhema</span>
        </a>
        <nav class="header-navigation">
          <a href="index.html" class="header-navigation-item">Home</a>
          <a href="about.html" class="header-navigation-item">About</a>
        </nav>
      </div>
    </header>

    <main id="main-content">
      <article class="article" data-theme="default">
        <section class="hero-simple" data-theme="default">
          <div class="hero-simple-container container">
            <h1 class="text-h1 color-heading">Hello world!</h1>
          </div>
        </section>
      </article>
    </main>
  </body>
</html>

data-theme="default" is how you switch color modes on a region; the same attribute drives the theme prop in React.

The bundled JavaScriptLink to this section

The shipped listener entry imports every listener. Each module initializes itself on import or on DOMContentLoaded. The ESM bundle at @systhemaui/core/js has tree-shaking disabled; importing one named export does not opt out of the other listeners.

The IIFE output is dist/js/bundle.global.js, with the global name SysthemaJS. The dist/js/bundle.js file is ESM and is not a classic-script entry. After the browser load bug is fixed, copy the IIFE into a public asset directory rather than serving all of node_modules in production:

mkdir -p public
cp node_modules/@systhemaui/core/dist/js/bundle.global.js public/systhema.js
<script src="public/systhema.js" defer></script>

These paths describe the shipped outputs, not a workaround for the load failure. Use Vanilla JS bundle for listener APIs, and verify them against the release you deploy.

Adding more pagesLink to this section

Create additional HTML files at the project root (or in subfolders) and link to them. Tailwind's content scanner picks up classes from anywhere in the project (controlled by the @source directives in src/style.css).

# Add another page.
cp index.html about.html
# Edit about.html, then:
pnpm dev

Differences from React/Next templatesLink to this section

  • No bundler. Tailwind compiles CSS; everything else is plain HTML and the Systhema JS bundle.
  • No TypeScript configuration is required. Keep the pnpm lockfile for repeatable installs.
  • packages.react: true in systhema.config.js, explicitly enables the React-component-class safelist so the same class names work on raw HTML.
  • The HTML and Payload starters use a fluid %value% / %screen% * 100vw layout rule with wide-screen overrides. The Next.js starter uses its configured defaults. See Responsive sizing.
  • Component instances are markup, not React components, read @systhemaui/react for the canonical class structure of each component (the same class names apply on plain HTML).

Ongoing maintenanceLink to this section

  • Re-run pnpm sync after any design-token update.
  • Keep your GITHUB_TOKEN active.
  • After updating @systhemaui/core, re-run pnpm sync and redeploy.