Docs

Defaults and what follows what

This page lists each default Fluid ships, why it is the default, and how a site changes it. Each default has a section with Rule / Why / Change it. The headings are stable, so a person or an AI can link to one or quote it. Where a default was the maintainer's call, the date says when.

In short, the theme picks one good answer, and a site changes it with one attribute or one variable. Nothing is decided by counting links, guessing what kind of site it is, or detecting the device.

Alignment follows the page

Rule. The page sets the alignment. Everything inside follows it unless it sets its own. The nearest setting wins, in this order: page, header, nav, the nav's menu.

  • The nav's wrapped link rows (on phones, or in a narrow bar) inherit text-align. So <body data-align="center"> centres them.
  • data-align="center" on the .nav centres the whole masthead: the brand on its own row, and the links and actions centred under it. On the body or a header, the same attribute centres two kinds of nav: one that is its direct child, and one in a header that is the body's direct child. Any other nav needs it on the .nav itself.
  • data-align="start" on a .nav__menu keeps its rows at the start, even inside a centred nav.

Why. A site that chose centred should not get one left-aligned block in its header, and a site that chose start should not get a centred one. One setting, read once, beats a rule for every component. the maintainer, 2026-10-29: "the nav should follow… the whole point is to not overcomplicate things."

Change it. Set data-align on whichever level should differ. To change only the nav's wrapped rows, set --nav-wrap-align: start | center | end on the nav.

The nav is the page's other scheme

Rule. Every .nav bar, at every width, uses the page's other colour scheme: a dark bar on a light page, and a light bar on a dark page.

  • tokens.css flips every colour role on the bar. Its text, plain text and icons, the current-page mark, the dropdown, the compact "+" panel and the focus ring all flip with it.
  • A <nav class="nav container"> that is the page's top bar keeps its content on the column, and its bar reaches both screen edges. Its parent must be the body, or a header that is the body's child. In forced colours the bar is the system background.
    • The paint at the sides is only paint: on a sticky bar, content scrolled under those sides can still be clicked.
    • A .nav.container inside a narrower column (a sidebar layout) stays the column's width.
  • To keep the page's colours, use data-bg="plain".
  • data-variant="transparent" has no bar, for a nav over a hero: its brand, links and marks take the surrounding text colour (the hero's).
  • data-bg="inverse" forces the flipped bar, for example on a transparent nav.

Why. The bar is where people look for the way around the site. A solid, contrasting bar is easy to find at a glance on any page, and it is the pattern the big shops use for their headers (Amazon, Walmart). the maintainer liked it on the mock-ups and picked it for every width (2026-10-29).

Change it.

  • Page colours: add data-bg="plain" to the nav.

  • A brand colour: set both variables of the pair; setting only one of them fails contrast in one of the two schemes. The recipe below does two things:

    • It resolves the fills on :root, so the bar matches the page's own buttons exactly.
    • It applies the pair only to the flipped bar, so plain and transparent navs keep the page's colours.
    :root { --brand-bar: var(--accent-fill); --brand-ink: var(--on-accent-fill); }
    .nav:not([data-bg="plain"], [data-variant="transparent"]) {
      --nav-bg: var(--brand-bar);
      --nav-ink: var(--brand-ink);
    }
    /* The CTA and the icon-mode logo mark are accent fills: on an accent bar,
       swap them so they still show. */
    .nav:not([data-bg="plain"], [data-variant="transparent"]) :is(.nav__cta, .nav__brand-mark) {
      background-color: var(--nav-ink);
      color: var(--nav-bg);
    }
    

What --nav-ink colours:

  • the bar's text, links and plain text;
  • the dropdown;
  • the toggles and the compact "+" and "×";
  • the current-page mark;
  • the focus ring.

It does not colour fills, which keep the accent:

  • .nav__cta;
  • buttons and badges;
  • the icon-mode logo mark.

An outline or ghost button, or a badge, placed in .nav__actions on an accent bar needs its own colours.

Roles set on :root stop at the bar. The bar declares every colour role again for its own scheme. A theme that sets a role on :root, such as --accent-ink or --accent-fill, changes the page but not the bar. Change the bar through --nav-bg and --nav-ink, or set the role on .nav as well (as legacy-1x.css does for the accent fills).

Current marks use the readable accent shade

Rule. These marks use --accent-ink, never --accent:

  • the current-page underline in the nav;
  • the pagination's current page;
  • the tab indicator.

Why. --accent is tuned for a light page. On a dark bar it measured 2.17:1, under the 3:1 a non-text mark needs. --accent-ink is the accent's readable shade in each scheme, and it measures 9.5:1 on the dark bar.

Change it.

  • Every mark: change --accent. --accent-ink is derived from it.
  • The nav bar only: set --nav-ink together with --nav-bg.
  • Pagination and tabs only: an --accent-ink set on :root reaches them, but not the nav or the dark bands, which declare it again.

The nav stays at the top

Rule. The site's links are in the header, at the top of the page, on every screen. There is no bottom tab bar.

  • On phones: the links take their own row under the brand. The space between links tightens first, down to 16px (8px of padding on each side, --nav-gap-min). Then they wrap in balanced rows: four links wrap as two and two, never three and one.
  • The exception: a nav that still has the 1.x .nav__toggle button folds its menu behind that button. Remove the toggle to get the visible row.

Why. Research (2026-10-29) found:

  • The big shops keep one top header, with the cart at the top right (Baymard).
  • Visible links beat hidden ones. In NN/g's testing, 86% of people found them, against 57% for hidden links.
  • Fixed bottom bars on websites conflict with iOS Safari's own toolbar.
  • No study found a bottom bar beating a top header on a website.

the maintainer's decision: "keep it where it belongs."

Change it. The opt-in nav modes (dist/fluid-nav.min.css) give other shapes:

  • data-nav="icon";
  • data-nav="compact" (a "+" panel);
  • data-sticky="links".

The TOC dock is tinted off the page

Rule. On phones, the article TOC sits in a dock pinned to the bottom of the screen. It is --color-surface in light mode and --color-surface-raised in dark mode, so it reads as its own layer. The reading-position highlight is measured above the dock, so the section it lights up is one you can see.

Why. With --color-surface alone, the dock was 1.07:1 against a dark page, which is almost invisible.

Change it. Set --toc-dock-bg on .toc or :root.

Jump links land below a sticky nav

Rule. On a page with a data-sticky nav, a jump to #section stops with the target below the bar. :root gets a top scroll padding of 5rem on wide screens and 13rem under 64rem, where the links may wrap into more rows. Targets keep their own scroll margin on top of that (2rem, or --space-l for headings).

Why. WCAG 2.4.11: a focused or jumped-to element must not be hidden under a sticky header. CSS cannot read the bar's height: it measured 72-78px on one row and up to 207px when a phone wraps the links with a dropdown and a CTA. So the clearance is generous. Too much leaves a gap; too little hides the heading.

Change it. A site whose bar height is known sets --nav-sticky-size on :root (for example 4.75rem). That one value applies at every width.

One-page sites mark no current link

Rule. On a one-page site, a nav of #anchor links has no aria-current, so no link is underlined as current. The theme adds nothing for it.

Why. "Current page" means the page you are on, and on a one-page site that is every link. Marking the section in view needs a script, and that is the TOC's job, not the nav's.

Change it. A site that wants it sets aria-current="true" from its own script.

Core stays small; modes are add-ons

Rule. The build enforces a size limit on dist/fluid.min.css (core): 24,500 B gzip. The history:

  • 24,000 B until 2026-10-29.
  • +200 B for alignment, the inverse nav and the accent-ink marks.
  • +200 B for the audit's fixes to that default, the same day (the jump-link clearance fits inside it).
  • +100 B on 2026-10-30 for the gallery's Firefox fallback (47 B): Firefox sized a gallery of bare <img> figures about 142px a row too tall.

Core measures about 24,442 B on the Mac Studio and 24,460 B on Linux and CI (Node's bundled zlib differs by platform). Optional shapes ship as their own files, linked after core:

  • the TOC (fluid-toc.min.css);
  • the nav modes (fluid-nav.min.css);
  • the fonts.

Why. Every site pays for core; only the sites that use an add-on pay for it. the maintainer (2026-10-29): the limit may grow by what a new core default costs, rounded up to the next 100 B, and never for an option.

Change it. Put a new option in an add-on file instead. Raise the limit only for a new default, in scripts/build-css.mjs, with the reason in its comment.