Getting Started

Farn ships as a single CSS file — no build step, no runtime, no framework assumptions. Add one <link> tag and you have all tokens.

CDN Install

Link directly from jsDelivr. Replace @0.1.0 with the latest version tag.

<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/gh/jabopiti/farn-theme@0.1.0/dist/farn.css">

dist/farn.css is a single concatenated file containing palette tokens, semantic tokens, typography, spacing, and base resets.

What Gets Loaded

farn.css bundles five layers in order:

  1. colors.css — 17 palette tokens across four palettes
  2. typography.css — font-family tokens and scale reference
  3. spacing.css — space scale, layout widths, border radius, z-index
  4. dark-light.css — semantic token layer with dark/light switching
  5. base.css — box-sizing reset, smooth scroll, focus ring, reduced-motion

Fonts are not bundled in farn.css. Load them separately via Google Fonts.

Load Fonts

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Instrument+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
/>

Load fonts via a <link> tag in <head>, not via @import url() inside CSS — the @import approach delays rendering.

Prevent Flash of Wrong Theme (FOWT)

Add this inline script in <head> before any CSS loads. It reads the stored preference and sets data-theme on <html> before the browser paints.

<script>
  (function () {
    const stored = localStorage.getItem('farn-theme');
    const system = window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark' : 'light';
    document.documentElement.setAttribute('data-theme', stored ?? system);
  })();
</script>

Dark / Light Mode

Theme mode is controlled via a data-theme attribute on <html>:

<html data-theme="light"> … </html>
<html data-theme="dark"> … </html>

Toggle it with JavaScript and persist the choice:

function toggleTheme() {
  const current = document.documentElement.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', next);
  localStorage.setItem('farn-theme', next);
}

Default (no attribute) is light mode. System preference is respected when no manual choice is stored.

Surface Overrides

Apply data-surface to any element to force a surface context independently of the page theme:

<section data-surface="dark">
  <!-- All children use dark surface tokens automatically -->
  <button>Primary action</button>
</section>

Available values: light, dark, tinted. All child components inherit the re-scoped tokens with no extra markup.

Using Tokens

Reference semantic tokens in your component CSS — never raw palette tokens:

.btn-primary {
  background: var(--color-accent);      /* Fern (light) or Sage (dark) */
  color: var(--color-accent-text);      /* always Parchment */
  border-radius: var(--radius-md);      /* 6px */
  padding: 0 var(--space-md);
  font-family: var(--font-body);
  font-weight: 600;
}

.card {
  background: var(--color-bg-elevated); /* Linen (light) or Iron (dark) */
  border-radius: var(--radius-lg);      /* 8px */
  padding: var(--space-md);
}

Fraunces Headings

font-variation-settings: 'opsz' is mandatory on every Fraunces heading — the font renders incorrectly without it:

h1 { font-family: var(--font-display); font-variation-settings: 'opsz' 72; }
h2 { font-family: var(--font-display); font-variation-settings: 'opsz' 24; }
h3 { font-family: var(--font-display); font-variation-settings: 'opsz' 20; }

Minimal Example

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/gh/jabopiti/farn-theme@0.1.0/dist/farn.css">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link rel="stylesheet"
    href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Instrument+Sans:wght@400;500;600&display=swap">
  <script>
    (function(){
      const s = localStorage.getItem('farn-theme');
      const p = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
      document.documentElement.setAttribute('data-theme', s ?? p);
    })();
  </script>
  <style>
    body {
      font-family: var(--font-body);
      background: var(--color-bg);
      color: var(--color-text);
      padding: var(--space-xl);
    }
    h1 {
      font-family: var(--font-display);
      font-variation-settings: 'opsz' 72;
      font-weight: 800;
    }
  </style>
</head>
<body>
  <h1>Hello, Farn</h1>
  <p>Token-first design system.</p>
</body>
</html>