Skip to content
PolyFish CSS

Documentation

The guide, split into six pages by topic. Components and the class reference have pages of their own.

Start here

Three ways in

Depending on whether you want to ship something today, understand the architecture, or look up a class.

Quick start

Three lines and a container

Core defines the tokens every other file reads, so it always loads first. Utilities loads last so its helpers win a specificity tie.

  • No npm, no bundler, no config file
  • Class names are prefixed pf- and collide with nothing
  • Fonts are optional — every stack falls back to a system face
index.htmlhtml
<link rel="stylesheet" href="/PolyFishCSS-Core.css?v=d3e4d1db">
<link rel="stylesheet" href="/PolyFishCSS-Headers.css?v=2b121128">
<link rel="stylesheet" href="/PolyFishCSS-Content.css?v=6e21fa2f">

<section class="pf-section">
  <div class="pf-container">…</div>
</section>

Load order matters

Core, then Headers, Content and Footers in any order, then Utilities. Loading Utilities early is the single most common mistake — its helpers stop overriding component rules.

Interactive

Most sections do something

Fifteen panels across the documentation, all of them radio inputs, checkboxes and :has(). No script runs any of it.

How the pieces fit

Which files does my page need?

Core is the only required file. Everything else maps to a part of a page, so the answer is usually visible from the design.

Typical loads. Past six files the bundle is smaller and one request — see installation.
Page you are building Files to link Minified Why
Sign-in or error screenCore, Content39 KBOne panel, no navigation, no footer
Marketing or landing pageCore, Headers, Content, Footers54 KBHero, sections, column footer
Documentation pageCore, Headers, Content, Footers, Space56 KBAdds sidebar navigation and spacing helpers
Application screenCore, Headers, Content, Forms, Components75 KBTables, tabs, menus, validation states
Anything using most of itThe bundle99 KBFewer requests and smaller gzipped past six files

The two-layer token idea

A palette names colours. Roles name jobs — background, surface, text, accent, border. Components only ever read roles, so a theme is eleven redeclared properties rather than a fork of the framework.

That indirection is why light mode is a palette swap with no component rule touched, and why the playground can retint a live page from radio inputs.

What is deliberately missing

No carousel, no toast queue, no combobox, no scroll-animation library. Anything that only works when a script runs would break the rule the optional layer is built on: every component functions without it.

A horizontally snapping .pf-scroller replaces the carousel. It has no autoplay and no hidden slides, and every card is reachable by scroll, keyboard and screen reader.

Baseline

What it needs from a browser

FeatureUsed forFromWithout it
Custom propertiesEvery token2016Nothing works — this is the floor
Grid and gapAll layout primitives2017Nothing works
clamp()Fluid type and rhythmChrome 79Type stops scaling; layout holds
:has()Option cards, playgroundChrome 105, Safari 15.4Selected-state styling is skipped
@containerContainer-query helpersChrome 105, Safari 16Components keep their stacked layout
animation-timelineScroll revealChrome 115Content is simply visible
color-mix()Frosted panels, brand tintsChrome 111, Safari 16.2Falls back to a solid colour

Roughly Chrome 105, Safari 15.4 and Firefox 103. Every feature past that floor is behind @supports or has a declared fallback, so an older engine does less rather than breaking.

Beyond the floor

Newer platform features, and the fallback for each

FeatureUsed forFromWithout it
<dialog>Modals, drawersBaseline 2022data-pf-dialog script or a server-rendered page
popover + invoker commandsMenus and dialog triggers in markupBaseline 2025Buttons do nothing — keep a scripted or server fallback
@starting-styleDialog and overlay transitionsBaseline 2024Overlays appear instantly
Anchor positioning.pf-popover placement next to its buttonChrome 125, Safari 26The popover opens in a fixed spot instead
field-sizing.pf-textarea--autoChrome 123, Safari 18.4The textarea keeps a fixed height
animation-timeline: scroll()Reading progress on blog postsChrome 115, Safari 26The progress bar hides itself
@view-transitionCross-fade between pagesChrome 126, Safari 18.2Navigation is instant
Service workerOffline reading of docs you have visitedEvery evergreen engineOffline hits a browser error page

The same honesty the blog posts argue for, applied to this site: every progressive feature here names the engine it needs and what happens without it. Firefox versions trail the Chromium/Safari edge by a release or two on most of these.