Tooling
Built for Chromium 103
The game’s UI runs in FiveM’s embedded Chromium 103 and stays on Tailwind 3.4, because Tailwind 4 needs Chrome 111. Everything here — tokens, utilities, components — has to work there.
Where it runs
Game NUI
FiveM CEF · Chromium 103
Vite 8 · React 19 · Tailwind 3.4.1
backdrop-filter off, ui-scale PostCSS pass
Hub
Evergreen browsers
Next 16 · React 19 · Tailwind 3.4.17
light and dark themes
Web site
Evergreen browsers
React Router · Tailwind
tokens only
Why a lint
A Tailwind 4 class in a Tailwind 3 app generates nothing. Nothing fails, nothing warns; the style just never shows. Both apps already ship a few dozen of them, copied from newer shadcn code.
Silent by design
outline-hidden in Tailwind 3 is not an error — it is an unknown class, and an unknown class is ignored. The only way to catch it is to look for it.Tailwind 4 class names
| Pattern | Why, and what to write instead |
|---|---|
| ^(?:[\w-]+:)*outline-hidden$ | Tailwind 4 only; use outline-none |
| ^(?:[\w-]+:)*shadow-xs$ | Tailwind 4 only; use shadow-sm |
| ^(?:[\w-]+:)*rounded-xs$ | Tailwind 4 only; use rounded-sm |
| ^(?:[\w-]+:)*bg-linear-to-[trbl]{1,2}$ | Tailwind 4 only; use bg-gradient-to-* |
| ^(?:[\w-]+:)*field-sizing- | Tailwind 4 only (field-sizing) |
| ^(?:[\w-]+:)*[\w-]+-\(--[\w-]+\)$ | Tailwind 4 variable shorthand; use [var(--x)] |
| ^(?:[\w-]+:)*data-(?:disabled|open|closed|active|selected|highlighted|placeholder): | Tailwind 4 bare data variant; use data-[disabled]: |
| ^(?:[\w-]+:)*not-[\w[\]-]+: | Tailwind 4 not-* variant |
| ^(?:[\w-]+:)*starting: | Tailwind 4 starting: variant |
| ^(?:[\w-]+:)*inset-shadow- | Tailwind 4 only (inset-shadow) |
| ^(?:[\w-]+:)*text-shadow- | Tailwind 4 only (text-shadow) |
Tailwind 3 classes Chromium 103 cannot run
| Pattern | Why, and what to write instead |
|---|---|
| (?:^|:)(?:group-|peer-)?has-\[ | has-* compiles to :has(), which needs Chromium 105 |
| ^(?:[\w-]+:)*text-(?:balance|pretty)$ | text-wrap: balance/pretty needs Chromium 114 |
| ^(?:[\w-]+:)*@container | container queries need Chromium 105 |
Dynamic viewport units (h-dvh, min-h-svh) are allowed only behind a supports-[height:100dvh]: guard, with a vh fallback beside them.
CSS Chromium 103 cannot parse
| Pattern | Why, and what to write instead |
|---|---|
| color-mix\( | color-mix() needs Chromium 111 |
| \bokl(?:ch|ab)\( | oklch()/oklab() need Chromium 111 |
| :has\( | :has() needs Chromium 105 |
| @container\b | container queries need Chromium 105 |
| \b\d*\.?\d+(?:s|d|l)v[hwib]\b | svh/dvh/lvh need Chromium 108 |
| text-wrap:\s*(?:balance|pretty) | text-wrap: balance needs Chromium 114 |
| @starting-style\b | @starting-style needs Chromium 117 |
Inside an @supports block these are fine. A mask-image also needs its -webkit-mask-image twin.
Run it
The tokens package ships the lint as
fast-tw-compat. It scans .ts/.tsx/.js/.jsx/.css and exits 1 on any finding.bunx fast-tw-compat src
# src/components/ui/select.tsx:42 outline-hidden Tailwind 4 only; use outline-none