Skip to content
PolyFish CSS

A framework you can read in an afternoon

PolyFish exists because most pages do not need a build step, and most CSS frameworks make you take one anyway.

What it is

PolyFish is five hand-written CSS files covering the parts a page is made of: a required core of tokens and primitives, then headers, content, footers and utilities. You link the ones your page has and skip the ones it doesn't. There is no compiler and no configuration file, and nothing needs JavaScript to render.

What it is not

Behaviour is optional and it is separate. Every component renders and functions from CSS alone; an optional 8 KB script upgrades a handful of them — closing a menu on an outside click, giving the tabs a real ARIA tablist, counting characters in a textarea. Nothing depends on it, and nothing is hidden by it.

It is also not a utility-first framework. Utilities exist in the fifth file and they are deliberately small — enough to stop you writing one-off rules, not enough to build a page out of class strings.

Principles

  • Readable source. What you download is what you read. Every file has section headers and comments explaining why a rule exists.
  • Roles, not colours. Components reference --pf-accent, never a hex value. Retheming is eleven custom properties, applied at runtime.
  • Ordinary HTML. One or two classes per element, no required wrapper divs beyond a container.
  • Prefixed everything. Every class starts with pf-, so dropping it into an existing project collides with nothing.

Versioning

PolyFish follows semantic versioning. Within a major version, class names and token names do not change meaning — a value may be tuned, a component may gain a variant, but existing markup keeps rendering the way it did. Removals wait for a major.

Browser support

The framework uses custom properties, clamp(), grid, gap, clip-path and color-mix(). Everything degrades: where color-mix() is unsupported the sticky navigation falls back to a solid background, and where backdrop-filter is unsupported it simply doesn't blur. Anything from roughly 2023 onward renders as designed.

Licence

MIT. Use it commercially, fork it, rename it, ship it inside a product. Attribution is appreciated and not required.

The name

The mark is a betta built out of a hexagon and three fans of triangles, all radiating from a single point. It is the framework's argument in one image: a small number of regular pieces, arranged deliberately, is enough.

Where the constraints came from

Each rule below exists because of a specific failure, not a preference. They are in the order they were learned.

RuleThe failure behind it
Components read roles, never palette valuesRetinting a framework by editing component rules means every update re-opens the same merge conflict. Eleven role properties make a theme a stylesheet instead of a fork.
No element is hidden by JavaScript on loadA script that hides content and then fails to load has deleted that content. The optional layer only upgrades what already works.
Nothing injects CSS from scriptA script that writes styles forces unsafe-inline into your Content-Security-Policy. The two elements the script creates are styled in the Content module instead.
Data values get utility classes, not inline stylesA rating bar needing style="width: 69%" is a framework quietly forcing you to weaken your policy. Twenty-one width classes are cheaper than that.
Every animation sits inside a reduced-motion guardSlowing an animation that starts at opacity: 0 to nothing leaves the element invisible forever. Not applying it is the only correct behaviour.
Grid children carry min-width: 0A wide table stretches its track past 1fr and scrolls the whole page sideways. One line, and it was latent here for four versions.
Form fields are floored at 16px on touchiOS Safari zooms on focus below that and never zooms back out. The fields were at 14px.

Versioning

Inside a major version, class names and token names do not change meaning. A value may be tuned and a component may gain a variant, but existing markup keeps rendering the way it did. Removals wait for a major and are deprecated in a minor first. The changelog records every release, including the ones that only fixed something embarrassing — the redirect loop, the class collision between two modules, the heading levels that broke outline navigation.

How the site verifies itself

Every claim on this site is checked by a script rather than remembered, because the numbers move every release. The build fails on any of these:

  • Structure and SEO — one h1 per page, no duplicate ids, every internal link and fragment resolving, canonical uniqueness, sitemap parity, no orphan pages.
  • Documentation coverage — every public class must appear somewhere on the site. It was 41% before the check existed.
  • Module waste — a page may not link a module it never uses. Seventeen pages were shipping one.
  • Consistency — every site page uses the shared header, the shared footer, the theme toggle and one page head. Eighteen pages once carried thirteen different navigations.
  • Mobile — nineteen checks covering viewport, fixed widths, table wrappers, viewport units, touch targets, iOS zoom, safe areas and menu reachability.
  • Accessibility — heading order, landmarks, an accessible name on every control, link text, skip-link targets.
  • Correctness — W3C validation on every page, source-versus-minified rule parity on every module, and 69 jsdom assertions on the optional script, twelve of which check that the page survives the script's absence.

The build is also deterministic: it runs four times in a row producing byte-identical output, because a non-deterministic build makes a real change indistinguishable from noise.

Who it is built for

PolyFish is the framework behind SudoSecurity, a technical publication covering security tooling and developer releases. That is not a footnote — it is the reason the framework looks and behaves the way it does. Every constraint here came from shipping a real publication on it, and the priorities are the publication's priorities, in its order: security first, performance second, design that works for a first-time reader and a fifteen-year sysadmin third.

Security decided the architecture

A publication is a static site with an editorial workflow behind it, so the framework had to work without a runtime. No script is required to render a page, nothing is injected into the DOM, and no asset comes from a third-party origin — which is what lets the served policy refuse remote scripts, remote styles and remote fonts outright rather than allow-listing a CDN.

Performance had to hold at both ends

An article page loads four files, about 11 KB over the wire. The documentation you are reading loads the bundle at 19 KB. Both are cached for a year with a versioned URL, so a returning reader downloads nothing. The modular split exists because a long-form article has no pricing table and should never pay for one.

Readability is the design brief

Large headers, a body face that holds up at length, a measure capped at 66 characters and vertical rhythm from a token rather than habit. A publication lives or dies on whether a 3,000-word piece is comfortable to read on a phone at midnight, which is a harder test than any landing page.

What a publication needs that a landing page does not

Most CSS frameworks are optimised for the marketing page and treat long-form content as an afterthought — a .prose class bolted on at the end. Publishing on this one surfaced a different list:

NeedWhat it became
Article body that stays readable at lengthA measure token applied to p and li globally, with per-element opt-outs, plus prose rhythm driven by the space scale rather than browser defaults
Code samples in running textA code block with a filename bar, four highlight classes and no tokeniser — highlighting is whatever your pipeline already does
Tables of versions, CVEs and benchmarks on a phoneA required scroll wrapper, a stacking variant that reads cell labels from data-pf-label, sticky headers and captions
Callouts that do not shoutAlerts coloured by a left rule and a wash rather than a filled block, so they can sit mid-article
Hub pages and category indexesCard grids, stat rows and the section rhythm variants, so an index page and an article share one visual language
A theme that survives a redesignEleven role tokens. The publication's palette is a stylesheet, not a fork of the framework

Where it runs

This site and SudoSecurity are both served from SudoSecurity Cloud on nginx, with the configuration published in nginx/ so it can be read rather than described. Static files, immutable caching on versioned URLs, a strict Content-Security-Policy and no interpreter in the request path. The whole framework is eleven CSS files and one optional script; there is nothing to patch on a CVE cadence, which for a security publication is the entire point.

Start with one file.

Core is 11 KB and does most of the work. Add the rest as the page needs them.