Tradeoffs
chaiTailwind is small on purpose. That smallness has costs you should know about up front.
Costs
Styles arrive after JS
Because everything is applied at runtime via element.style, your initial paint shows unstyled markup until the engine runs. For most pages this is invisible — the script lives in <head> and runs at DOMContentLoaded, so painting happens with styles already in place. But:
- If JavaScript is disabled, no styles apply.
- If the script is loaded
async, you’ll see a flash of unstyled content. - If the page is huge (10k+ elements), the initial scan can take meaningful time.
For documents where the first paint must be styled — marketing pages, content-heavy SSR sites — use a real CSS framework that emits a stylesheet.
Every element pays per-property cost
Inline styles are the lowest-friction way to apply rules, but they’re not free at scale. Each property becomes a style.setProperty(...) call. With a few hundred elements this is imperceptible; with tens of thousands it becomes noticeable.
No pseudo-classes
There’s no inline equivalent of :hover, :focus, :checked, or media queries. If you need them, write a small bespoke <style> block alongside chaiTailwind:
<style> .my-button:hover { transform: translateY(-1px); } @media (prefers-color-scheme: dark) { body { background: #111; } }</style>This is the most common gap people hit. We may eventually add a companion stylesheet approach, but it’s not v1.
No responsive prefixes
There’s no chai-md:p-4 syntax in v1. Containers can be styled with clamp() and min()/max() in arbitrary values, which covers a surprising amount:
<div class="chai-p-[clamp(1rem,4vw,3rem)] chai-text-[clamp(1rem,2.5vw,1.5rem)]">…</div>But true breakpoint-driven layout still wants real media queries.
No SSR / static HTML output
chaiTailwind only runs in a browser. There is no Node-side renderer that takes your HTML, parses chai-* classes, and inlines the styles statically. (This is technically possible — the parser is pure — and may land in a future version.)
Wins
One file, no toolchain
If you’ve ever hit “Tailwind requires PostCSS” in a context where PostCSS isn’t on the menu — a CMS, a Webflow embed, an internal tool, an iframe widget — chaiTailwind fits. Drop in a <script>, write classes.
Trivially extensible at runtime
Adding a custom rule is a one-liner. There’s no config file to edit, no build to re-run.
chai.extend((token) => token === 'spin' ? { animation: 'spin 1s linear infinite' } : null);Plays well with anything that touches the DOM
htmx, Alpine, vanilla event handlers, dynamic frameworks — anything that mounts new nodes gets styled by the MutationObserver automatically.
Decision flow
If you can answer “yes” to any of these, chaiTailwind is probably wrong for you:
- The page must be readable with JavaScript disabled.
- You need responsive prefixes (
md:,lg:) baked into class names. - You need pseudo-class variants (
hover:,focus:). - The page renders thousands of elements at first paint.
- The design system has hundreds of opinionated tokens.
Otherwise, you’re in the comfortable middle of the curve where chaiTailwind is exactly the right amount of tool.
Made by Saad · @developedbysaad on X