Skip to content
PolyFish CSS

Every component, grouped by file

If a component appears under Content, it needs PolyFishCSS-Content.css. Core is assumed everywhere.

PolyFishCSS-Core.css

Type, buttons, layout

Interactive: states you cannot see by looking

state

The resting state. Everything below is the same markup with one attribute changed.

The ring comes from :focus-visible, so it appears for keyboard focus and not for a mouse click. Shown here without holding the tab key.

disabled and aria-disabled="true" both dim and block pointers. The second stays focusable, which is usually what you want — a control nobody can reach cannot explain why it is off.

Driven by aria-busy, so the spinner and the announced state come from one attribute. The spinner is a pseudo-element — no markup changes when the state flips.

That does not look like an email address.

Styling keys off aria-invalid, and the message is joined with aria-describedby — without it a screen reader says "invalid" and never says why.

Radio inputs and :has(). Each row is real markup with a real attribute, not a screenshot of a state.

Type scale

Heading one, display face

Heading two

Heading three

Body copy sits at one rem with a 1.65 line height and a measure capped at 68 characters, because a line you can't track back to is a line nobody reads. Inline code, a an inline link, bold and a ⌘K key all live at this size.

Small print, muted role colour.

Buttons

Pills and markers

Default Green Orange Facet marker

Grid and split

.pf-grid--4
column
column
column
.pf-grid--auto
fits to --pf-min
no media query needed

PolyFishCSS-Headers.css

Announcement bars, navigation, heroes

The sticky bar at the top of this page is the navigation component. Below are the pieces that sit around it.

Announcement bar

Green bar, for the announcement that actually matters. Read it

Centred hero

Centred variant

One column, mesh behind it

The scale mesh is a masked background on the hero itself, so it never needs an image request or a wrapper element.

Interior page header

Shorter, for pages that aren't the front door

Breadcrumbs, title, one line of context.

PolyFishCSS-Content.css

The middle of the page

Cards

Default card

Surface tint, hairline border, large radius. The icon slot is optional.

Facet card

One clipped corner, sized by --pf-cut. Use it for the cards you want read first, not for all of them.

Accent link card

The whole card is the anchor. Hover and focus states are handled.

Feature grid and checklists

Tokens first

Components read roles, never raw hex values.

Prefixed classes

Everything starts with pf-, so nothing collides.

Readable source

Hand-written CSS with section headers, meant to be edited.

  • Checklist item with a facet bullet
  • Second item, wrapping onto a second line to show the alignment
  • Third item

Plan cards

Core only

Tokens, type and buttons.

11 KB one file

  • Reset and type scale
  • Layout primitives
  • Buttons and pills

Docs page

Core and Content.

23 KB two files

  • Sections and prose
  • Code blocks and tables
  • Accordions and alerts

Stats

  • 5Files
  • 0Dependencies
  • 1Required file
  • MITLicence

Table

FileHoldsNeeds CoreSize
CoreTokens, reset, type, layout, buttonsIs Core11 KB
HeadersTop bar, nav, heroes, page headersYes7 KB
ContentSections, cards, tables, formsYes12 KB
FootersFooter layouts and link columnsYes4 KB
UtilitiesSpacing, colour, visibility helpersYes4 KB

Alerts and quotes

Default

Left rule in the accent colour.

Success

Deployed. The file is live at the path you set.

Careful

Utilities loads last. Move it above Content and the helpers stop winning ties.

Broken

Core is missing. Every other file reads its custom properties and renders unstyled without it.

A framework you can read end to end in an afternoon is a framework you can fix at 2am.
Design note, PolyFish 0.1.0

Forms

Shown in the page title.

You can change this by deleting a link tag.

Code block

card.htmlhtml
<article class="pf-card pf-card--facet">
  <h3 class="pf-card__title">Facet card</h3>
  <p class="pf-card__body">Two classes, no wrapper.</p>
</article>

Accordion, steps and gallery

Native details element

Open and close state is handled by the browser. No script, and it prints expanded.

Second panel

Wrap them in .pf-accordion for the spacing and the marker.

  1. Copy the files

    Numbered markers are only used where the content is a real sequence.

  2. Link them in order

    Core first.

CTA band

Sits above the footer

Mesh background, one clipped edge of contrast, two actions.

PolyFishCSS-Grid.css

Twelve columns, five breakpoints

For layouts that genuinely change proportion per breakpoint. Most pages should use the Core primitives instead — one class, own breakpoints.

Explicit columns

.pf-col-12 .pf-col-md-8
.pf-col-12 .pf-col-md-4
6 / lg-3
6 / lg-3
6 / lg-3
6 / lg-3

Offsets, order and auto columns

.pf-col-4 .pf-offset-4
.pf-row-cols-3
no child classes
tight gutters

Interactive: columns and gutters

--pf-row-cols
gutters
1
2
3
4
5
6
7
8
9
10
11
12

Both controls set a custom property on the row. The column classes on the cells never change.

Grid or primitive?

A row of three equal cards is .pf-grid--3 — one class, and it collapses on its own. Reach for .pf-row when the proportions themselves change: an 8/4 split that becomes 7/5 before it stacks.

PolyFishCSS-Components.css

Application pieces

Eighteen components for the screens behind the login: badges, groups, pagination, progress, lists, switches, tabs, menus, dialogs. All of them work with no script.

Badges and tags

Default Accent Secondary Failing Outline Muted 12 css no-build

Button groups, pagination and toolbar

24 results filtered

Progress, spinner and skeleton

Progress

Bar and segmented meter.

Spinner

Stops rotating under reduced motion, which is why the label is required.

List group, avatars and switches

api-gateway
10 second interval
Healthy
billing-worker
30 second interval
Degraded
Add a check +
https://
or

Tabs, menu and dialog

Radio inputs and sibling selectors. Arrow keys move between tabs natively, which is the behaviour a scripted tablist has to reimplement.

Fourteen regions, two must agree before an alert is raised.

Thirty days of raw check results, exportable as CSV.

Actions
Open dialog (CSS only)

Native dialog

Opened with showModal(), so focus is trapped, Escape closes it and focus returns to the button that opened it. The CSS-only version beside it does none of that — which is exactly the gap the optional script fills.

Built for a phone

Drawer

Opens on :target with no script. With the optional script it closes on Escape, returns focus and locks the page behind it.

Stacking table

Below 620px each row becomes a card and each cell shows its column name. Narrow this window to see it.

CheckRegionStatus
api-gatewayeu-westHealthy
billing-workerus-eastDegraded

Scroller

Snap points, no autoplay, no hidden slides.

Second

Swipe or scroll. Keyboard reaches every card.

Third

This is what PolyFish has instead of a carousel.

Fourth

Nothing is hidden from a screen reader.

Empty state and ratio box

No checks yet

Point PolyFish at a URL and it will start watching. Nothing here is a dead end.

Add a check

PolyFishCSS-Forms.css

Forms that tell you what went wrong

Sizes, floating labels, validation feedback, option cards, range and file inputs, and the layout rows to arrange them.

That does not look like an email address.

Resolves from all fourteen regions.

42 / 280

Every 30 seconds.

Alert routing
Digest

Collapses to stacked below 760px.

Why aria-invalid, not a class

The red border and the announced error come from one attribute, so they cannot drift apart. The message is joined to the field with aria-describedby — without it a screen reader says "invalid" and never says why.

PolyFishCSS-Footers.css

Three footers

The full column footer is at the bottom of this page. These are the other two.

PolyFishCSS-Utilities.css

Helpers, if you want them

Every demo box on this page is built from these classes, so the file documents itself.

GroupClassesNotes
Spacingpf-mt-0…8, pf-mb-0…8, pf-p-4…6Maps to the 4px space scale
Gappf-gap-1…6Works on any flex or grid parent
Flexpf-flex, pf-col, pf-items-center, pf-justify-betweenEnough to stop writing one-off rules
Textpf-text-sm, pf-display, pf-mono, pf-balanceSizes come from the type tokens
Colourpf-fg-accent, pf-bg-deep, pf-bg-meshRole-based, so themes follow
Visibilitypf-hide-sm, pf-hide-lgOne breakpoint at 860px, plus print rules

Other things you can poke

Every documentation page on this site has something interactive in it: swap page regions, retint a live page, change the type scale, see what each module unlocks, assemble your link block, frame a real page at 320px, weigh a page type, build an attribute list, turn the invisible guards on, flip a component through its states and filter the changelog by tag. All of it is radio inputs, checkboxes and :has() — no script runs any of it.