围绕 CSS 机制排查问题并编写组件样式表,而不是凭感觉试错。
设计与多媒体
Tailwind CSS
诊断并交付能在生产环境编译生效的 Tailwind v3/v4 标记、主题与构建配置
它能做什么
覆盖 Tailwind v3 与 v4 两套配置面,按用户选定的版本、构建工具、暗色策略、组件语法与合并工具输出对应代码。聚焦那些容易在生产环境隐形的故障——动态拼接的类名没被扫描、工具类输给作者样式、`dark:` 首屏闪烁、点击后焦点环被吃掉等。涉及层叠顺序、堆叠上下文、flex 溢出等 CSS 本身的问题交由 `css` 技能处理,本技能只承担 Tailwind 自身的规则与套路。
什么时候用它
- 定位类在生产包里不生成 CSS 的扫描根与动态字符串问题
- 把项目的 tailwind.config.js 与 @tailwind 指令迁移到 v4 的 @theme 与重命名后的工具类
- 将 Tailwind 接入 Vite、Next.js、Astro、SvelteKit、Rails、Laravel 等框架
- 在 @apply、cva 组件、tailwind-merge 之间为重复出现的类集合挑方案
技能文档
User preferences and memory live in ~/Clawic/data/tailwindcss/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/tailwindcss/ or ~/clawic/tailwindcss/), move it to ~/Clawic/data/tailwindcss/.
Configuration
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/tailwindcss/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| tailwind_version | 3 | 4 | 4 | Selects the config surface of every answer: 4 emits @import "tailwindcss", @theme, @source, @utility, suffix !; 3 emits tailwind.config.js, @tailwind directives, content, safelist, prefix ! |
| build_integration | vite | postcss | cli | browser | vite | Which install steps and config files installation.md emits, and where the scan root sits |
| dark_mode_strategy | media | class | data-attribute | media | The variant definition emitted in CSS, and whether toggle code plus the paint-blocking script ships with it (dark-mode.md) |
| component_syntax | jsx | vue-sfc | svelte | astro | html | jsx | Markup language of every emitted example, and which @apply/@reference rules apply (frameworks.md) |
| merge_helper | cn | clsx | tailwind-merge | none | cn | Helper wrapped around conditional or overridable class props in emitted components (components.md) |
| rem_base | number px (10-16) | 16 | Every px↔rem conversion for utilities in Utility Scale Math (p-4 = 4 × 0.25rem = 1rem = rem_base px). Breakpoints are exempt — see that section |
| token_threshold | number (2-5) | 3 | Uses of the same value before it must become a theme token: gates Core Rule 2, the promotion rules in arbitrary-values.md and variants.md, and the second Output Gate |
| text_direction | ltr | rtl | both | ltr | ltr emits physical utilities (ml-4, text-left, border-l); rtl and both emit logical ones (ms-4, text-start, border-s) everywhere and add dir to example markup (variants.md) |
| a11y_target | aa | aaa | aa | Which contrast and target-size rows gate output: AA = 4.5:1 body and size-6; AAA = 7:1 body and size-11 (accessibility.md) |
Preference areas to record as the user reveals them:
- tooling — formatter and lint stack (
prettier-plugin-tailwindcss, ESLint class rules), IntelliSense regex for custom helpers, how eagerly to adopt new majors - conventions — theme namespace naming, CSS entrypoint layout, where component classes are allowed to live
- design system — palette source (default palette, brand tokens, an imported token pipeline), spacing and type scale overrides, multi-brand or white-label needs
- integrations — UI kit in play (shadcn/ui, Headless UI, Radix, daisyUI, Flowbite): decides the variant idiom (
data-[state=…]) and the class-merging strategy - risk posture — appetite for arbitrary values and the
!modifier, whether Preflight may be dropped, tolerance for features above the v4 browser floor - constraints — banned techniques (
@apply, browser build, runtime class construction), legacy stylesheets that must keep winning, email or WebView targets - output — whether to show the generated CSS, and whether to name the mechanism before giving the fix
When To Use
- Writing or reviewing Tailwind markup,
@theme/tailwind.config.js, custom utilities, or plugins - Debugging: a class emits nothing, emits but loses, works in dev and not in production, or fires in the wrong state
- Setting up or migrating: first install, framework wiring, v3 → v4, adding Tailwind to a codebase that already has CSS
- Design-system work in Tailwind: tokens, dark mode, multi-brand theming, component variant APIs
- Build health: rebuild speed, CSS bundle size, monorepo and library scanning
- Not for CSS mechanics or taste — why a flex child overflows belongs to
css, palette and scale choices todesign-system
Quick Reference
| Situation | Play |
|---|---|
| Class is in the markup, no CSS in the output | The scanner never saw the string (→ Class Detection); dynamic name or unscanned file → missing-styles.md |
Works in dev, gone after build | Same scanner problem, plus config drift between dev and build → missing-styles.md |
| CSS is generated but the element ignores it | Cascade And Conflicts below, then debugging.md symptom chains |
| Your own CSS silently beats every utility | Unlayered author CSS outranks every cascade layer in v4 (→ Cascade And Conflicts) |
Two utilities of the same property fight (px-4 px-6) | Sheet order decides, not attribute order; runtime merging → components.md |
hover:, group-hover:, peer-*, has-[…], data-[…] won't fire | variants.md |
dark: does nothing, or the theme flashes on load | dark-mode.md |
| Custom color, spacing step, font, breakpoint, or keyframe | theming.md |
| A one-off value the theme has no token for | arbitrary-values.md |
space-x gaps wrong, truncate won't truncate, w-screen overflows | layout.md |
| A breakpoint fires at the wrong width, a range needs both bounds, or one component must respond to its own width | responsive.md |
| The same 14 classes repeated in 30 places | components.md — component boundary, cva, cn, and where @apply is still correct |
prose, form-control resets, or writing a custom utility/variant | plugins.md |
| Transitions, keyframes, enter/exit animation, reduced motion | animations.md |
Focus rings, sr-only, contrast of the default palette | accessibility.md |
| Next.js, Nuxt, Astro, SvelteKit, Rails, Laravel, Storybook, email, React Native | frameworks.md |
| First install, Vite plugin vs PostCSS vs CLI, editor IntelliSense | installation.md |
| Slow rebuilds, huge CSS file, monorepo or library scanning | performance.md |
Upgrading v3 → v4, or an error naming @tailwind, content, or corePlugins | v4-migration.md |
| Adding Tailwind to a codebase that already has CSS, Bootstrap, or a UI kit | adoption.md |
| Anything else | Put the single class on a bare `` in isolation: if it works there the fault is scanning or cascade, not the utility |
Core Rules
- A class only exists if its complete string exists in a scanned file. The scanner is a text matcher, not a JS evaluator:
bg-${tone}-500,'text-' + size, and`p-${n}`produce zero CSS and zero errors. Write a lookup of whole classes —const tone = { danger: 'bg-red-500', ok: 'bg-green-500' }— and index into it. - Theme first, arbitrary second,
@applylast. A value used ≥token_thresholdtimes (default 3) becomes a token (--color-brand-500: oklch(0.62 0.19 259)); below that,bg-[#1da1f2]. The legitimate uses of@applyare exactly three, and this list is closed: third-party HTML,::-webkit-*pseudo-elements, print sheets. Anything else you were about to@applyis a component you haven't written yet. - Conflicts resolve by generated-sheet order, never by attribute order. Tailwind sorts by property group then by scale ascending, so
px-6is emitted afterpx-4and wins —class="px-6 px-4"still renders 1.5rem. To resolve at runtime,twMerge('px-4','px-6')→px-6; string concatenation just ships both. - A manual dark toggle needs three things or it fails silently. Default
dark:followsprefers-color-scheme. Manual toggling requires (a) the strategy — v4@custom-variant dark (&:where(.dark, .dark *));, v3darkMode: 'class'; (b) the class on ``, not on a component; (c) a blocking inline script that sets it before first paint, or every reload flashes the wrong theme. - Preflight is take-it-or-leave-it: never fork or edit the reset itself. It removes heading sizes and list markers and makes
imgdisplay: block; max-width: 100%— which is why CMS HTML and third-party widgets go flat the day Tailwind lands. Three sanctioned exits, in order: wrap unowned content inprose(typography plugin); restore the handful of defaults you need in your own@layer base(an explicit short list, never a copy of the old reset); or drop Preflight whole by importing the layers individually (adoption.md). What is forbidden is a patched Preflight — a vendored copy with rules commented out drifts from the framework on every upgrade with no error. - Mobile-first: unprefixed applies everywhere,
md:means ≥768px and up. A range needs two utilities (md:flex lg:hidden= 768–1023px) or onemax-*variant (max-lg:flex). "Only on tablets" written asmd:blockis the classic responsive bug in Tailwind markup. - Repetition is a component problem, not an
@applyproblem. A component with a variant map (cva) plus a mergeableclassNameprop keeps variants, IntelliSense, and the scanner working.@applydoes support variants (@apply hover:bg-blue-500compiles fine) — what it costs you is discoverability and override order, and inside a Vue `` or CSS module it needs@referenceto see the theme at all. - Never remove a focus affordance without replacing it in the same rule.
focus:outline-nonealone is the classic Tailwind accessibility regression. Canonical replacement, used verbatim everywhere in this skill:focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2(add afocus-visible:ring-*color where the defaultcurrentColoris wrong). Two reasons for every token in it:outline-hiddenkeeps the outline under forced colors whereoutline-nonedeletes it, andfocus-visible:on both halves means a mouse click shows no ring while keyboard focus does —focus:outline-hiddenwould strip the native outline on click too. - Read the rename list before bumping the major. v4 renamed
shadow→shadow-sm,shadow-sm→shadow-xs,rounded→rounded-sm,outline-none→outline-hidden, droppedbg-opacity-*forbg-black/50, and changedringfrom 3px blue to 1pxcurrentColor. The build succeeds and the design shifts quietly (v4-migration.md).
Utility Scale Math
Canonical home for these numbers; other files point here.
- Utilities — px = rem ×
rem_base. Stepn= n × 0.25rem.p-4= 1rem = 16px at the defaultrem_base16, and 10px atrem_base10, because a utility's rem resolves againsthtml { font-size }. Fractional steps 0.5/1.5/2.5/3.5 exist;px= 1px. v4 derives every step from--spacing: 0.25rem, so any multiple works (p-13= 3.25rem); v3 only ships the listed steps. - Breakpoints —
rem_basedoes not apply. sm 640px/40rem · md 768/48 · lg 1024/64 · xl 1280/80 · 2xl 1536/96, identical in both majors. A media query resolvesremagainst the browser's initial font-size, always 16px, ignoringhtml { font-size }(CSS Media Queries Level 4, "Evaluating Media Features"). Sosm:is 640px even in a project withrem_base: 10— computing 40 × 10 = 400px there is wrong. This is the one exception to the row above. - Container scale is not the breakpoint scale.
max-w-*and container-query sizes share--container-*: xs 20rem · sm 24 · md 28 · lg 32 · xl 36 · 2xl 42 · 3xl 48 · 4xl 56 · 5xl 64 · 6xl 72 · 7xl 80. Somax-w-sm= 24rem whilesm:= 40rem — the pair most often misread in the framework. - Type (size/line-height, rem):
text-sm0.875/1.25 ·text-base1/1.5 ·text-lg1.125/1.75 ·text-xl1.25/1.75 ·text-2xl1.5/2. - Target sizes:
size-6= 1.5rem = 24px = WCAG 2.2 AA floor (2.5.8);size-11= 2.75rem = 44px = AAA and Apple HIG.a11y_targetpicks the row. The floor is specified in CSS px, so underrem_base10 those same classes render 15px and 27.5px and fail it — size hit areas in px or raise the step. - Opacity modifier:
bg-blue-500/50is the color at 50% alpha. v4 computes it withcolor-mix(), so it also works oncurrentColorand on a CSS variable; v3 required a color defined with the `` placeholder.
Class Detection
The mental model that explains most Tailwind bugs: the build reads your source files as plain text and extracts every substring shaped like a utility. It does not parse, does not resolve imports, and does not run your code.
- A class inside a comment or a dead branch is generated. A class assembled at runtime is not. A class that lives only in a database row, CMS field, or API response is never seen.
- v4 scans from the project root automatically, skipping
.gitignored paths, binaries, andnode_modules. Widen with@source "../packages/ui/src";, narrow with@source not "./legacy";, force with@source inline("bg-red-500 bg-green-500");. - v3 scans exactly the
contentglobs and nothing else. A new top-level directory, or anindex.htmlmissing from the array, yields silence — not an error. - Classes shipped inside a dependency's compiled files are invisible in both majors until you point a source at them (
missing-styles.md). - Verify, don't hope: after a production build,
grep -c 'bg-brand-500' dist/**/*.css. Either the class is in the artifact or the scan configuration is wrong.
Cascade And Conflicts
- v4 emits into cascade layers (
theme, base, components, utilities). Author CSS written outside any layer beats every layer regardless of specificity — one stray unlayered.card { padding: 0 }disablesp-4on every card, and DevTools shows the utility struck through with no specificity explanation. Move that rule into@layer baseand the utility wins again. - v3 output has no layers: plain specificity and source order decide, so
.card p { margin: 0 }(0,2,0) beatsmt-4(0,1,0). Same symptom, opposite mechanism — checktailwind_versionbefore diagnosing. - Same-property utilities never "override" each other in the attribute; both are generated and the sheet decides (→ Core Rule 3).
- Override across a component boundary with
twMerge, which knows the conflict groups. Custom utilities needextendTailwindMergeor the merge silently keeps both (components.md). - Escape hatch: the important modifier — v4 suffix
bg-red-500!, v3 prefix!bg-red-500. Reserve it for CSS you don't own;important: trueproject-wide trades one problem for a permanent one. - Custom CSS placement:
@layer componentsfor anything a utility should be able to override,@utility(v4) for anything that must sort with utilities and accept variants.
Output Gates
Before emitting Tailwind markup or config, verify:
- Every class a complete literal string in a scanned file — no interpolation, no concatenation?
- Any value repeated ≥
token_thresholdtimes (default 3) promoted to a theme token instead of repeated arbitrary syntax? - Interactive elements carry the canonical
focus-visiblering of rule 8 and meet the target-size row? - Directional utilities match
text_direction— logical (ms-*,text-start) whenever it is notltr? - Every color set has its
dark:counterpart, if the project themes? - Layout read at the smallest width first — unprefixed is mobile, not desktop?
- Text colors pass the contrast floor (
text-gray-400on white is ≈2.5:1 and fails)? - Syntax matches
tailwind_version— no@tailwinddirective in a v4 project, no@themeblock in a v3 one?
Traps
| Trap | Why it fails | Do instead |
|---|---|---|
bg-${color}-500, 'text-' + size | The scanner is a text matcher; that string never exists in the source | Lookup map of complete classes (→ Class Detection) |
class="px-4 px-6" to override | Both are generated; the stylesheet decides, not the attribute | twMerge, or don't emit both (→ Cascade And Conflicts) |
focus:outline-none with nothing after it | Deletes the only affordance keyboard users have | focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 (→ Core Rule 8) |
important: true in the config | Every utility becomes !important: third-party CSS dies and your own overrides need escalation | Per-class ! on the few that need it; layer your CSS instead (→ adoption.md) |
@apply in a Vue `` or a CSS module | That file compiles in its own context with no theme loaded | v4 @reference "../app.css"; first — better, move the classes to the markup (→ frameworks.md) |
h-screen for a full-height mobile section | 100vh ignores the browser chrome that collapses on scroll | h-dvh, or h-svh when live resizing would be jumpy |
w-screen for a full-bleed row | 100vw includes the scrollbar width → horizontal scroll on desktop | w-full, or w-dvw |
space-x-4 on a wrapping or reversed row | The sibling selector skips wrapped rows and inverts under flex-row-reverse | gap-4 (→ layout.md) |
truncate with no width constraint | text-overflow needs a resolvable width; a flex child defaults to min-width: min-content | min-w-0 on the flex child, or an explicit max-w-* |
safelist: [{ pattern: /bg-.*/ }] | Emits the whole palette across every variant — bundle explodes, tree-shaking gone | Enumerate the real classes (→ missing-styles.md) |
Adding dark: to background only | Half the component follows the theme; the bug is invisible in the mode you develop in | Set foreground and background as a pair, every time |
| Browser build (``) in production | Compiles on every page load, no scanning discipline, no plugin parity | A real build step (→ installation.md) |
| Pasting v3 snippets into a v4 project | @tailwind, content, corePlugins, and safelist are gone; the error names the directive, never the cause | Mapping table in v4-migration.md |
rounded-full on an element without overflow-hidden | Absolutely positioned children paint over the rounded corner | overflow-hidden on the rounded parent (→ layout.md) |
Where Experts Disagree
@apply. Tailwind's own maintainers argue against it; design-system teams shipping a class API to templates they don't control use it deliberately. Boundary:@applyis right when the markup isn't yours to edit — otherwise it's a component you haven't written yet.- Arbitrary values vs a closed theme. One camp treats
[...]as a design-system leak; the other as the reason Tailwind survives real designs. Boundary: escapes are fine at the leaf, harmful in shared components — those pull from tokens so a rebrand is one file. - CSS config (
@theme) vs JS config (@config). JS is programmable — loops, imports from a token package, generated scales — and v4 still loads it. Boundary: generated token pipelines stay in JS; hand-maintained themes move to@themeand get CSS variables for free. - Class sorting. Automated (
prettier-plugin-tailwindcss, canonical order, zero diff noise) vs hand-grouped by concern (layout, then color, then state) for readability. Boundary: any repo with more than one author takes the automated order; the argument only survives in solo codebases.
Related Skills
More Clawic skills, get them at https://clawic.com/skills/tailwindcss (install if the user confirms):
css— the mechanics underneath the utilities: stacking contexts, flex sizing, cascadereact— component architecture, props, and state around the class stringsnextjs— App Router, fonts, and build integration specificsdesign-system— tokens, scales, and multi-product theming above the config fileaccessibility-audit— full WCAG review beyond the floor enforced here
Feedback
- If useful, star it: https://clawic.com/skills/tailwindcss
- Latest version: https://clawic.com/skills/tailwindcss
Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/tailwindcss.
常见问题
- 为什么 `bg-${color}-500` 生成不出对应 CSS?
- 扫描器按完整字符串匹配,不做 JS 求值,所以动态拼接出来的类名永远不会被识别。把拼接换成查表——预先准备好 tone 到完整类名的映射,运行时取常量字符串即可。
- 同一属性的两个工具类写在同一个元素上,谁赢?
- 由生成样式表中的属性分组与步长升序决定,跟 DOM 上的书写顺序无关。例如 `class="px-6 px-4"` 仍渲染 1.5rem。需要运行时取舍就用 `twMerge('px-4','px-6')` 得到 `px-6`,直接字符串拼接只会两边都下发。
- v3 升 v4 主要变了什么?
- `@tailwind` 指令换成 `@import "tailwindcss"`,配置大量迁入 `@theme`;部分工具类改了名(`shadow`→`shadow-sm`、`shadow-sm`→`shadow-xs`、`rounded`→`rounded-sm`、`outline-none`→`outline-hidden`);透明度写法如 `bg-blue-500/50` 改用 `color-mix()`,因此能作用于 `currentColor` 与 CSS 变量。
相关技能
针对 Vue 3 响应式、组件、路由和性能问题,依据控制台报错与代码形态给出具体修复方案。
排查并修复 Svelte 与 SvelteKit 的响应式、SSR 与构建问题,并把 Svelte 4 代码迁移到 runes。
让 Markdown 在 GitHub、MDX、Pandoc、文档站、Slack、Notion 等解析器中正确渲染,并定位修复具体损坏位置。
Build polished, conversion-aware frontends with strong visual taste, clear hierarchy, and production-grade HTML/CSS/JS. Landing pages, dashboards, components...
按规则构建、调试和审查 React 应用,覆盖组件、状态、表单、性能与 React 19 特性。