The common reading of PolyFish is "a framework that is not Tailwind", which is true but unhelpful. The better question is what a page looks like when the thing you reach for is a component rather than a string of utilities — and what to do with the muscle memory the utilities built.
The reflex map
Most of the Tailwind vocabulary has a direct counterpart, and the counterpart is usually shorter because the decision it encodes has already been made once, inside the class.
| Tailwind reflex | PolyFish | Why it is different |
|---|---|---|
flex gap-4 | .pf-cluster | Row with wrapping and alignment decided once — the primitives own their overflow behaviour |
flex flex-col gap-6 | .pf-stack | Vertical rhythm as a flow rule, not a flexbox |
grid grid-cols-3 gap-4 | .pf-grid / .pf-switcher | Columns come from content width, not a count: auto-fit means no empty columns at the wrap |
p-4 m-2 mt-8 | --pf-space-* tokens + gap utilities | Spacing is rhythm between siblings, not padding on everything — the gap does the margin's job |
md:flex-row | .pf-md-* variants, .pf-container-q | Component breakpoints answer "is there room here?", not "is the window wide?" |
dark: | A theme file or .pf-invert | Dark is a swapped stylesheet of eleven roles, not a prefix on every class |
hover:bg-blue-700 | Baked into the component | Hover is a state the button already owns; you only name it when you deviate |
@apply | The component class itself | .pf-btn is the composed utility string, pre-composed and named |
tailwind.config | :root { --pf-* } | Config is a runtime stylesheet, not a build input — retinting a deployed page needs no rebuild |
prose plugin | .pf-prose | Same idea, different spelling: typographic rhythm for article bodies |
The bigger swap: where decisions live
Tailwind puts the decision in the markup — every element carries its layout, spacing and
state. PolyFish puts the same decisions in the class once: .pf-btn knows its own
padding, radius, hover and focus ring, and a page of buttons stays a page of
pf-btn rather than a page of identical twenty-class strings that drift apart the
first time someone edits one.
That is also the limit of the translation. Utilities compose to anything; components compose to what the framework decided. When the component is wrong for you, the answer is not a longer class string — it is your own class reading the same tokens, which inherits every theme for free.
What the variants actually are
The reflex worth unlearning is the breakpoint prefix. md: answers a question about
the window; PolyFish's .pf-container-q answers a question about the space the
component is actually in — a card in a sidebar adapts even when the window is wide. Where a
window-level switch really is the intent (hide on mobile, swap a row), the .pf-md-*
helpers exist, two tiers rather than five.
Arbitrary values, honestly
w-[347px] has no PolyFish spelling and never will. The escape hatch is a custom
property or three lines in your own stylesheet, which is what arbitrary values compile to
anyway — minus the compiler. The whole framework works the same way: the tokens are public,
and the cheapest way to deviate is to write CSS that reads them.
Figures are measured from the build they describe.
What this doesn’t cover
A map is not the territory. The table translates the reflexes most pages actually use; niche utilities — aspect ratios, scroll snapping, masks, the plugin ecosystem — either have no counterpart or deserve their own entry. And the mapping runs one way: patterns in the other direction, like anchor-positioned overlays or invoker commands, are what PolyFish offers that no utility string can write.
Sources: the class reference for the PolyFish side, and Tailwind’s own documentation for the utility spellings. The mapping table was written for this post; the claims underneath it — two breakpoint tiers, eleven roles, the bundle’s gzip weight — are measured from the current build.