Surfaces

stable

Farn's theming system has two orthogonal axes. data-theme sets an absolute light or dark context on any element. data-surface sets a relative depth within that context — base, layer, or overlay — and resolves automatically to the correct palette token for the current theme. They compose freely: a dark panel inside a light page needs only one attribute pair.

For the token lookup table and copy-ready snippets, see Styles › Theming.

The Two-Axis Model

Most theming systems give you a single switch — light or dark. Farn gives you two independent controls: an absolute theme axis and a relative depth axis.

The absolute axis (data-theme) sets the entire context for an element and all its descendants. The relative axis (data-surface) expresses depth within whatever theme context is currently active. Because depth is relative, a data-surface="layer" on a dark panel resolves to Iron Night iron, and on a light panel it resolves to Birch Mist mist — the same semantic intent, the right palette value.

This is why Farn palettes are designed in complementary pairs: Iron Night and Birch Mist mirror each other's depth levels. Iron Night void = dark page; Birch Mist birch = light page. Iron Night iron = dark card; Birch Mist mist = light card. The surface system is an expression of the palette story, not a separate mechanism bolted on.

Depth Reference

Each surface overrides --color-bg (and where relevant --color-bg-panel) so that any element using background: var(--color-bg) picks up the right depth automatically.

data-surface Light --color-bg Dark --color-bg Use
base --bm2-birch --in0-void Resets to page bg; useful inside a deeper surface when you need to escape back up
layer --bm1-mist --in1-iron Cards, sidebars, panels — one step raised from the page
overlay --bm0-sand --in2-slate Modals, dropdowns, floating elements — highest depth level

Live Demo

The two columns below are forced to their respective themes via data-theme on the column wrapper. Each surface renders as a sibling block so you can compare all three depths side by side in both themes simultaneously.

Light theme

base

Page background — birch

layer

Cards, panels — mist

overlay

Modals, dropdowns — sand

Dark theme

base

Page background — void

layer

Cards, panels — iron

overlay

Modals, dropdowns — slate

Responds to the page toggle

Without a forced data-theme the surfaces adapt automatically. Use the moon/sun button in the nav above to see all three levels update in place.

Current page theme

base

Page background

layer

Cards, panels

overlay

Modals, dropdowns

Composition

Combine data-theme and data-surface on the same element to create a surface that is pinned to a specific theme regardless of the page. Both panels below stay constant as you toggle the page theme.

data-theme="dark" data-surface="layer"

Always a dark panel

data-theme="light" data-surface="layer"

Always a light panel

FOWT Prevention

A Flash of Wrong Theme (FOWT) occurs when the browser renders a frame in the default light mode before JavaScript runs and reads the stored preference. On a fast connection it is a brief flicker; on a slow one it can persist for a noticeable moment.

The fix is a tiny synchronous inline script placed in <head> — before <body> — that sets data-theme on <html> before the browser paints anything:

<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>

Three rules for the script to work:

In Astro, use is:inline on the script tag to prevent bundling: <script is:inline>...</script>. For a practical setup walkthrough, see Getting Started › FOWT.