
Tailwind Css
- 85 installs
- 31 repo stars
- Updated August 4, 2026
- iliaal/ai-skills
Applies Tailwind CSS v4 CSS-first config, utility patterns, tailwind-variants, and v3-to-v4 migration for styling components and dark mode.
About
Tailwind-css covers Tailwind v4 CSS-first configuration with @theme tokens, utility rules, tailwind-variants, and dark-mode patterns. A developer uses it when styling with Tailwind, migrating v3 to v4, or fixing broken Tailwind styles.
- CSS-first @theme config replacing tailwind.config.ts
- Common-errors table and v4 dark-mode variable pattern
Tailwind Css by the numbers
- 85 all-time installs (skills.sh)
- +7 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,094 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iliaal/ai-skills --skill tailwind-cssAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 85 |
|---|---|
| repo stars | ★ 31 |
| Last updated | August 4, 2026 |
| Repository | iliaal/ai-skills ↗ |
What it does
Applies Tailwind CSS v4 CSS-first config, utility patterns, tailwind-variants, and v3-to-v4 migration for styling components and dark mode.
Files
Tailwind CSS v4
Verify before implementing: For v4-specific syntax (@theme, @variant, CSS-first config), look up current docs via Context7 (query-docs) before writing code. Tailwind v4 changed significantly from v3 and training data may be stale.
CSS-First Configuration
v4 eliminates tailwind.config.ts. All configuration lives in CSS.
| Directive | Purpose |
|---|---|
@import "tailwindcss" | Entry point (replaces @tailwind base/components/utilities) |
@theme { } | Define/extend design tokens -- auto-generates utility classes |
@theme inline { } | Map CSS variables to Tailwind utilities without generating new vars |
@theme static { } | Define tokens that don't generate utilities |
@utility name { } | Create custom utilities (replaces @layer components + @apply) |
@custom-variant name (selector) | Define custom variants |
@import "tailwindcss";
@theme {
--color-brand: oklch(0.72 0.11 178);
--font-display: "Inter", sans-serif;
--animate-fade-in: fade-in 0.2s ease-out;
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
}
@custom-variant dark (&:where(.dark, .dark *));Tokens defined with @theme become utilities automatically: --color-brand produces bg-brand, text-brand, border-brand. Define z-index as tokens (--z-modal: 50) and reference via z-(--z-modal) instead of arbitrary z-50.
CSS Modules: when using .module.css with Tailwind v4, add @reference "#tailwind"; at the top of the module file to enable theme token access inside the module.
Animations (tw-animate-css): use animate-in/animate-out base classes combined with effect classes (fade-in, slide-in-from-top). Decimal spacing gotcha: use bracket notation [0.625rem] instead of fractional values like 2.5.
v3 to v4 Migration
For projects upgrading from v3 to v4, see v3-to-v4-migration.md for the full breaking-change table and codemod guidance. For greenfield v4 work, current patterns are above.
Coding Rules
- `gap` over `space-x`/`space-y` -- gap handles wrapping; space-* breaks on wrap
- *`size-
overw- h-`** -- for equal dimensions - `min-h-dvh` over `min-h-screen` -- dvh accounts for mobile browser chrome
- Opacity modifier (
bg-black/50) --*-opacity-*utilities are removed in v4 - Design tokens over arbitrary values -- check
@themebefore using[#hex] - Never construct classes dynamically --
text-${color}-500won't be detected; use complete class names - `@utility` over `@apply` with `@layer` --
@applyon@layerclasses fails in v4 - Parent padding over last-child margin -- use padding on containers instead of bottom margins on the last child
ESLint Integration
Use eslint-plugin-better-tailwindcss for automated class validation:
no-conflicting-classes-- catchestext-red-500 text-blue-500no-unknown-classes-- flags typosenforce-canonical-classes-- normalizes shorthandno-duplicate-classes-- removes redundant entriesno-deprecated-classes-- catches v3 classes removed in v4useSortedClasses-- enforces canonical class order; configureattributes: ["classList"]andfunctions: ["clsx", "cva", "cn", "tv", "tw"]to cover JSX utility functions
Class Merging
Use cn() combining clsx + tailwind-merge for conditional/dynamic classes. Use plain strings for static className attributes.
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }// Static: plain string
<button className="rounded-lg px-4 py-2 font-medium bg-blue-600">
// Conditional: use cn()
<button className={cn("rounded-lg px-4 py-2", isActive ? "bg-blue-600" : "bg-gray-700")} />Component Variants
Use tailwind-variants (tv()) for type-safe variant components. Alternative: class-variance-authority (cva()).
import { tv } from "tailwind-variants";
const button = tv({
base: "rounded-lg px-4 py-2 font-medium transition-colors",
variants: {
color: { primary: "bg-blue-600 text-white", secondary: "bg-gray-200 text-gray-800" },
size: { sm: "text-sm px-3 py-1", md: "text-base", lg: "text-lg px-6 py-3" },
},
defaultVariants: { color: "primary", size: "md" },
});See tailwind-variants patterns for slots, composition, and responsive variants.
Common Errors
| Symptom | Fix |
|---|---|
bg-primary doesn't work | Add @theme inline { --color-primary: var(--primary); } |
| Colors all black/white | Double hsl() wrapping -- use var(--color) not hsl(var(--color)) |
@apply fails on custom class | Use @utility instead of @layer components |
| Build fails after migration | Delete tailwind.config.ts |
| Animations broken | Replace tailwindcss-animate with tw-animate-css |
.dark { @theme { } } fails | v4 does not support nested @theme -- use :root/.dark CSS vars mapped via @theme inline |
Dark Mode (v4 Pattern)
:root { --background: hsl(0 0% 100%); --foreground: hsl(222 84% 4.9%); }
.dark { --background: hsl(222 84% 4.9%); --foreground: hsl(210 40% 98%); }
@theme inline { --color-background: var(--background); --color-foreground: var(--foreground); }Semantic classes (bg-background, text-foreground) auto-switch -- no dark: variants needed for themed colors.
Verify
- Build passes with zero errors (
npm run buildor equivalent) - No v3 class names remain in changed files (check with
@tailwindcss/upgrade --dry-runif available) - No conflicting classes on the same element
References
- Component patterns -- tailwind-variants slots, CVA, compound components
- Layout patterns -- grid areas, container queries, z-index management, fluid typography
Component Patterns
tailwind-variants (tv)
Type-safe component variants with slots, composition, and responsive support.
Slots API
import { tv } from "tailwind-variants";
const card = tv({
slots: {
base: "rounded-lg border shadow-sm",
header: "flex flex-col space-y-1.5 p-6",
title: "text-2xl font-semibold leading-none tracking-tight",
content: "p-6 pt-0",
footer: "flex items-center p-6 pt-0",
},
variants: {
elevated: { true: { base: "shadow-lg border-0" } },
},
});
const { base, header, title, content, footer } = card({ elevated: true });Composition
const baseButton = tv({ base: "rounded-lg font-medium transition-colors" });
const iconButton = tv({
extend: baseButton,
base: "inline-flex items-center justify-center",
variants: {
size: { sm: "size-8", md: "size-10", lg: "size-12" },
},
});Responsive Variants
const grid = tv({
base: "grid gap-4",
variants: {
cols: { 1: "grid-cols-1", 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4" },
},
responsiveVariants: ["sm", "md", "lg"],
});
// Usage: <div className={grid({ cols: { initial: 1, sm: 2, lg: 4 } })} />CVA (class-variance-authority)
Alternative to tailwind-variants -- simpler API, no slots.
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: "border border-border bg-background hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
},
size: {
sm: "h-9 rounded-md px-3",
default: "h-10 px-4 py-2",
lg: "h-11 rounded-md px-8",
icon: "size-10",
},
},
defaultVariants: { variant: "default", size: "default" },
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export function Button({ className, variant, size, ...props }: ButtonProps) {
return <button className={cn(buttonVariants({ variant, size, className }))} {...props} />;
}Compound Components (React 19)
React 19 passes ref as a regular prop -- no forwardRef needed.
export function Card({ className, ref, ...props }: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {
return <div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />;
}
export function CardHeader({ className, ref, ...props }: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {
return <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />;
}ESLint Integration
Use eslint-plugin-better-tailwindcss for v4 class validation:
| Rule | Purpose |
|---|---|
no-conflicting-classes | Detect classes that override each other |
no-unknown-classes | Flag classes not registered with Tailwind |
enforce-shorthand-classes | size-6 not w-6 h-6; p-6 not px-6 py-6 |
no-deprecated-classes | Catch v3 class names used in v4 projects |
Layout Patterns
Grid Template Areas
Define reusable grid areas with @utility:
@utility grid-areas-dashboard {
grid-template-areas: "header header header" "nav main aside" "nav footer footer";
}
@utility area-header { grid-area: header; }
@utility area-nav { grid-area: nav; }
@utility area-main { grid-area: main; }<div class="grid grid-areas-dashboard grid-cols-[200px_1fr_250px] grid-rows-[60px_1fr_40px]">
<header class="area-header">Header</header>
<nav class="area-nav">Nav</nav>
<main class="area-main">Content</main>
</div>Auto-Responsive Grids
<!-- Auto-fit: cards stretch to fill -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-6">
<!-- Auto-fill: maintains track size, leaves empty space -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
<!-- Safe minimum (handles container < minmax min) -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(min(100%,300px),1fr))] gap-4">Z-Index Management
Define a z-index scale in @theme tokens instead of arbitrary numbers:
@theme {
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
}Reference with z-(--z-modal) syntax -- never use z-[9999].
Container Queries
Component-level responsiveness independent of viewport.
@plugin "@tailwindcss/container-queries";<article class="@container">
<div class="flex flex-col @sm:flex-row gap-4">
<img class="w-full @sm:w-32 @lg:w-48 aspect-video @sm:aspect-square object-cover" />
<div class="flex-1 min-w-0">
<h3 class="text-base @md:text-lg @lg:text-xl font-semibold truncate">Title</h3>
<p class="text-sm @md:text-base line-clamp-2 @lg:line-clamp-3">Description</p>
</div>
</div>
</article>| Use Container Queries | Use Viewport Queries |
|---|---|
| Reusable components | Page-level layouts |
| Sidebar widgets | Navigation bars |
| Card grids | Hero sections |
| Embedded/CMS content | Full-width sections |
Named containers scope queries: @container/sidebar with @lg/sidebar:flex-row.
Fluid Typography
Eliminate breakpoint jumps with clamp():
@theme {
--text-fluid-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-fluid-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem);
--text-fluid-3xl: clamp(1.875rem, 1.2rem + 3.375vw, 2.5rem);
}Always combine vw with rem -- pure vw breaks when users zoom (WCAG violation).
Custom Utilities
@utility scrollbar-none {
scrollbar-width: none;
-ms-overflow-style: none;
}
@utility text-gradient {
@apply bg-linear-to-r from-primary to-accent bg-clip-text text-transparent;
}Native CSS Animations (v4)
Define keyframes inside @theme and reference with --animate-* tokens:
@theme {
--animate-slide-up: slide-up 0.3s ease-out;
@keyframes slide-up {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
}Use @starting-style for entry animations on native popovers/dialogs:
[popover]:popover-open { opacity: 1; transform: scale(1); }
@starting-style { [popover]:popover-open { opacity: 0; transform: scale(0.95); } }Respect motion preferences: motion-safe:animate-bounce motion-reduce:animate-none.
Safe Area Handling (Notched Devices)
@utility safe-area-pt { padding-top: env(safe-area-inset-top); }
@utility safe-area-pb { padding-bottom: env(safe-area-inset-bottom); }Apply to fixed headers/footers on mobile.
v3 to v4 Breaking Changes
Reference for migrating an existing Tailwind v3 project to v4. For greenfield v4 work, the SKILL body covers current patterns directly.
| v3 | v4 | Notes |
|---|---|---|
tailwind.config.ts | @theme in CSS | Delete config file |
@tailwind base/components/utilities | @import "tailwindcss" | Single import |
darkMode: "class" | @custom-variant dark (...) | CSS-only |
bg-gradient-to-r | bg-linear-to-r | Also: bg-radial, bg-conic |
bg-opacity-60 | bg-red-500/60 | All *-opacity-* removed |
rounded-sm | rounded-xs | Radius scale shifted down one step |
rounded | rounded-sm | (run @tailwindcss/upgrade codemod) |
rounded-md (6px) | rounded (6px) | |
rounded-lg | rounded-md | |
rounded-xl | rounded-lg | |
min-h-screen | min-h-dvh | dvh handles mobile browser chrome |
w-6 h-6 | size-6 | Size shorthand for equal w/h |
space-x-4 | gap-4 | Gap handles flex/grid wrapping correctly |
text-base leading-7 | text-base/7 | Inline line-height modifier |
require("tailwindcss-animate") | tw-animate-css | CSS-only animations |
Run @tailwindcss/upgrade codemod before hand-editing -- it handles the mechanical class renames. Hand-fix @theme migration, custom variants, and any project-specific config patterns the codemod cannot infer.
ia-tailwind-css Specification
Intent
ia-tailwind-css is a language-class skill (stack-specific patterns and idioms). Tailwind CSS v4 patterns: CSS-first config, utility classes, component variants, v3 migration. Use when styling with Tailwind, configuring @theme tokens, using tailwind-variants/CVA, migrating v3 to v4, or fixing Tailwind styles and dark mode.
Scope
In scope:
- Behaviors described in
SKILL.mdand routed via the should_trigger phrasings indistillery/tests/fixtures/triggers/ia-tailwind-css.jsonl. - Updates to runtime behavior, structure, trigger precision, references, and validation.
Out of scope:
- Acting as the runtime instructions themselves (those live in
SKILL.md). - Trigger phrasings already covered by adjacent
ia-*skills (validate-pluginflags >70% description overlap as DUPLICATE_TRIGGER). - <!-- to fill in: domain-specific exclusions when the skill drifts -->
Trigger Context
- Class:
language - Hook regex:
plugins/whetstone/hooks/skill-patterns.sh->SKILL_PATTERNS[ia-tailwind-css] - Common requests (from fixture should_trigger):
- "add tailwind dark mode support to the settings page"
- "create a reusable button component using cva variants"
- "set up Tailwind v4 in this Next.js project"
- Should not trigger for (from fixture should_not_trigger):
- "write a bash script to rotate log files"
- "add a new GraphQL resolver for products"
- "write CSS modules for the layout"
Source And Evidence Model
Authoritative sources:
SKILL.md-- runtime instructions and reference routing.references/*.md-- bundled supplementary content (2 file(s)).distillery/tests/fixtures/triggers/ia-tailwind-css.jsonl-- positive and negative trigger phrasings under regression test.plugins/whetstone/hooks/skill-patterns.sh-- regex pattern that fires this skill.distillery/.eval-data/ia-tailwind-css/-- harvested session examples (when present).
Data that must not be stored in this skill or its references:
- Secrets, credentials, tokens.
- Machine-specific filesystem paths (
/home/...,/Users/...,~/ai/...). The validator (MACHINE_PATH_LEAK) flags these as HIGH. - Private URLs, customer data, or unredacted personal information.
Coverage matrix
| Dimension | Status | Evidence |
|---|---|---|
| Trigger fixtures | complete | distillery/tests/fixtures/triggers/ia-tailwind-css.jsonl (>=5 should_trigger, >=5 should_not_trigger) |
| Hook regex pattern | complete | plugins/whetstone/hooks/skill-patterns.sh (SKILL_PATTERNS[ia-tailwind-css]) |
| Reference architecture | complete | 2 file(s) under references/ |
| Real-usage signal | <!-- populated by harvest-sessions when sessions exist --> | distillery/.eval-data/ia-tailwind-css/ (created by harvest-sessions) |
Evaluation
Lightweight (run on every change):
python3 distillery/scripts/distiller.py validate-plugin --component ia-tailwind-css
python3 distillery/scripts/distiller.py test-triggers --skill ia-tailwind-cssDeeper (when behavior risk warrants):
python3 distillery/scripts/distiller.py dspy-eval ia-tailwind-css
python3 distillery/scripts/distiller.py diagnose-negatives ia-tailwind-cssAcceptance gates:
validate-plugin --component ia-tailwind-cssreturns 0 HIGH findings.test-triggers --skill ia-tailwind-cssreturns F1 = 1.0 with floors of 5 should_trigger and 5 should_not_trigger.- For dspy-eval, the composite score does not regress against the most recent saved baseline (see
distillery/.eval-data/ia-tailwind-css/history.json).
Known Limitations
<!-- to fill in over time as drift surfaces. Default rule: any time diagnose-negatives surfaces a recurring failure pattern, document it here so future maintainers understand the trade-off the current implementation accepts. -->
Maintenance Notes
- Update
SKILL.mdwhen the runtime workflow, branch conditions, or output contract changes. - Update this
SPEC.mdwhen intent, scope, evidence model, evaluation gates, or maintenance expectations change. - Update the trigger fixture when adding new positive phrasings, removing stale ones, or expanding scope (the 5/5 floor is a hard validator gate).
- Update the hook regex in
skill-patterns.shwhenever fixture positives expose a missed phrasing; verify F1 = 1.0 witheval-triggersbefore committing. - Run the full release pipeline via
/release-- never bump versions or update CHANGELOG.md from a per-skill edit.