agentleFS
medy-gribkovmedy-gribkov/arcana/skills/tailwind-css/SKILL.md

tailwind-css

Skill1 starsChanged 8 months agoApache-2.0View on GitHub

Writes and reviews Tailwind CSS v4 code with the CSS-first setup (@import, @theme, @utility, @plugin, @source), the cn() class merge helper, mobile-first responsive design, class-based dark mode, built-in container queries, and variant-driven components. Also migrates v3 projects to v4. Use when styling with Tailwind, fixing classes that do not apply or conflict, setting up or upgrading Tailwind, adding design tokens or dark mode, or building reusable Tailwind components.

  • Installs packages

We split this skill into the rules it gives an agent

12 rules. 0 are common, 2 are distinctive, and 10 are very specific to this project.

  • 0

    Common

    Most of the 8 edited versions we read keep these.

  • 2

    Distinctive

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Add or change breakpoints with --breakpoint-* in @theme (see Core Configuration).

    line 110
  2. Very specific:

    Put or remove the dark class on <html> from a small script or a theme library, and add suppressHydrationWarning to <html> if the class is …

    line 130
  3. Very specific:

    Always pair motion with motion-reduce: so users who ask for less motion get none.

    line 200
  4. Very specific:

    Keep a visible focus indicator.

    line 272
  5. Very specific:

    Check the version: read package.json and look for tailwind.config.* and @tailwind directives.

    line 286
  6. Distinctive:

    Build: run the project build and check the CSS size.

    line 293
  7. Very specific:

    If the file lives in an ignored path (node_modules, .gitignore), add @source.

    line 300
  8. Distinctive:

    Check for specificity: a later or more specific rule can override the utility.

    line 302
  9. Very specific:

    Use complete class strings in a lookup object, never text-${color}-500.

    line 305
  10. Very specific:

    Default is prefers-color-scheme.

    line 309
  11. Very specific:

    Check the <html> element actually gets the dark class.

    line 310
  12. Very specific:

    Check the dark: variants sit on the affected elements.

    line 311

“Common” is measured: kept by at least 6 in 10 of the 8 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Tailwind CSS Skill

This skill targets Tailwind CSS v4. In v4 the configuration lives in CSS, not in tailwind.config.ts. If the project still has a tailwind.config.* file, it is on v3 or it loads the file with @config. Check package.json before you choose syntax.

Longer examples: references/components.md (variant components and form inputs) and references/v3-to-v4.md (migration and renamed utilities).

Core Configuration (v4)

BAD: v3 directives and a JS config for tokens

/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
// tailwind.config.ts
export default {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: { extend: { colors: { brand: { 500: '#3b82f6' } } } },
};

GOOD: one CSS file, tokens in @theme

/* styles/globals.css */
@import "tailwindcss";

@theme {
  --color-brand-50: oklch(0.97 0.02 240);
  --color-brand-500: oklch(0.62 0.19 260);
  --color-brand-900: oklch(0.3 0.1 265);
  --font-display: "Inter", sans-serif;
  --breakpoint-xs: 475px;
}

Every @theme variable in a utility namespace becomes a utility (bg-brand-500, font-display, xs:flex) and a CSS variable (var(--color-brand-500)). Spacing needs no tokens: p-18 works because spacing is a multiple of one base unit. Do not mix inline style={{}} with Tailwind for values the tokens already cover.

Install (build tool plugin, no content array needed):

npm install tailwindcss @tailwindcss/vite     # Vite
npm install tailwindcss @tailwindcss/postcss  # Next.js and other PostCSS setups

cn() Utility Pattern

BAD: string concatenation, conflicting classes are not resolved

export function Card({ className }: { className?: string }) {
  // p-4 and p-8 both apply, and CSS source order decides the winner
  return <div className={`p-4 rounded-lg bg-white ${className}`} />;
}
// <Card className="p-8 bg-blue-50" />

GOOD: clsx plus tailwind-merge, the last class wins

// lib/cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// components/Card.tsx
import { cn } from '@/lib/cn';

interface CardProps {
  className?: string;
  variant?: 'default' | 'outlined';
}

export function Card({ className, variant = 'default' }: CardProps) {
  return (
    <div
      className={cn(
        'p-4 rounded-lg',
        variant === 'default' && 'bg-white shadow-sm',
        variant === 'outlined' && 'border border-gray-300',
        className
      )}
    />
  );
}
// <Card className="p-8 bg-blue-50" /> => p-8 and bg-blue-50 override correctly

Responsive Design Patterns

Unprefixed classes are the mobile base. Each prefix (sm:, md:, lg:) means "from this width up".

BAD: desktop-first thinking, the base class is overridden by nothing

<div className="grid-cols-4 md:grid-cols-2 sm:grid-cols-1" />

GOOD: mobile-first progressive enhancement

<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" />

Add or change breakpoints with --breakpoint-* in @theme (see Core Configuration). Use max-md: only for a rule that must stop at a width.

Dark Mode Implementation

In v4 the dark: variant follows prefers-color-scheme by default. For a manual toggle, override the variant in CSS.

BAD: branching class strings in JS

<div className={isDark ? 'bg-gray-900 text-white' : 'bg-white text-gray-900'} />

GOOD: dark: variants, with a class-based switch when you need a toggle

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-white">Content</div>

Put or remove the dark class on <html> from a small script or a theme library, and add suppressHydrationWarning to <html> if the class is set before hydration. Do not hardcode className="dark" in the layout: that locks dark mode on.

Custom Utilities and Plugins

BAD: the same long class list copied across the code

<div className="backdrop-blur-md bg-white/10 border border-white/20 rounded-lg" />
// repeated in 20 places

GOOD: @utility for a reusable rule, @plugin for a package plugin

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

@utility glass {
  backdrop-filter: blur(12px);
  background-color: rgb(255 255 255 / 0.1);
  border: 1px solid rgb(255 255 255 / 0.2);
}
<div className="glass rounded-lg shadow-xl">Content</div>

A custom @utility works with every variant (hover:glass, md:glass). To keep an existing JS plugin or config, load it with @config "../tailwind.config.ts"; or @plugin "./my-plugin.js";.

Anti-Pattern: @apply Overuse

BAD: a CSS file full of @apply

.button { @apply px-4 py-2 bg-blue-500 text-white rounded-sm hover:bg-blue-600; }
.card   { @apply p-6 bg-white rounded-lg shadow-md; }
.input  { @apply w-full px-3 py-2 border border-gray-300 rounded-sm; }
/* Loses composability and duplicates what a component does better */

GOOD: a component that owns its classes

Wrap the classes in a component and use cva for variants. The full Button and Input examples are in references/components.md. Use @apply only for styles you cannot put in markup, such as third-party HTML you do not control.

Animation Utilities

BAD: a loose keyframe and class that bypass the theme

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.fade-in { animation: fadeIn 0.5s ease-in; }

GOOD: keyframes and the animation token inside @theme

@theme {
  --animate-fade-in: fade-in 0.5s ease-out;
  --animate-shimmer: shimmer 2s infinite linear;

  @keyframes fade-in {
    0% { opacity: 0; transform: translateY(10px); }
    100% { opacity: 1; transform: translateY(0); }
  }
  @keyframes shimmer {
    0% { background-position: -200% 0; }
    100% { background-position: 200% 0; }
  }
}
<div className="animate-fade-in motion-reduce:animate-none">Content</div>

Always pair motion with motion-reduce: so users who ask for less motion get none.

Performance: Content Detection and Dynamic Classes

v4 finds source files on its own. It respects .gitignore and skips node_modules and binary files. You do not write a content array.

BAD: building class names from fragments

// Tailwind scans text, so it never sees "bg-red-500" here
<div className={`bg-${color}-500`} />

GOOD: complete class names in a lookup, and @source for extra paths

const colorMap = {
  error: 'bg-red-500',
  success: 'bg-green-500',
  info: 'bg-blue-500',
} as const;

export function Alert({ type }: { type: keyof typeof colorMap }) {
  return <div className={colorMap[type]}>Alert</div>;
}
@import "tailwindcss";
@source "../node_modules/@my-company/ui";            /* a package that is ignored by default */
@source inline("bg-{red,green,blue}-{400,500,600}"); /* classes that only exist at runtime (v4.1+) */

Container Queries

Container queries are built in to v4. No plugin is needed.

BAD: viewport breakpoints for a component that lives in a narrow sidebar

<div className="p-4 md:p-6 lg:p-8">
  <h2 className="text-lg md:text-xl lg:text-2xl">Title</h2>
</div>

GOOD: the component responds to its own container

<div className="@container">
  <div className="p-4 @md:p-6 @lg:p-8">
    <h2 className="text-lg @md:text-xl @lg:text-2xl">Title</h2>
  </div>
</div>

Arbitrary Values and CSS Variables

BAD: magic numbers in class names

<section className="h-[calc(100vh-64px)] pt-[72px]">Content</section>

GOOD: named variables, referenced with the shorthand

@theme {
  --header-height: 4rem;
  --content-padding: 4.5rem;
}
<section className="h-[calc(100vh-var(--header-height))] pt-(--content-padding)">Content</section>

pt-(--content-padding) is the v4 shorthand for pt-[var(--content-padding)].

Focus States

Keep a visible focus indicator. In v4, outline-none removes the outline completely. Use outline-hidden when you replace it with a ring, and prefer focus-visible: so the ring shows for keyboard users.

BAD: focus removed and nothing replaces it

<input className="focus:outline-none" />

GOOD: the ring replaces the outline

<input className="rounded-lg border px-3 py-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500" />

Workflow: Apply This Skill

  1. Check the version: read package.json and look for tailwind.config.* and @tailwind directives. Use v3 syntax only when the project is on v3.
  2. Audit className usage: find string concatenation that should use cn().
  3. Check @apply usage: replace it with components or @utility.
  4. Verify tokens: colors, spacing and breakpoints live in @theme, not in magic values.
  5. Test dark mode: confirm dark: variants work and the toggle (if any) sets the dark class.
  6. Review responsive design: mobile-first, container queries for reusable components.
  7. Check dynamic classes: complete class names only, or @source inline(...).
  8. Build: run the project build and check the CSS size.

Common Fixes

Fix: Tailwind classes not applying

  • Confirm the CSS file has @import "tailwindcss"; and is imported once by the app.
  • Confirm the build plugin (@tailwindcss/vite or @tailwindcss/postcss) is installed and registered.
  • If the file lives in an ignored path (node_modules, .gitignore), add @source.
  • Clear the framework cache, for example delete the .next folder.
  • Check for specificity: a later or more specific rule can override the utility.

Fix: Dynamic classes not working

  • Use complete class strings in a lookup object, never text-${color}-500.
  • For classes that only exist at runtime, use @source inline(...).

Fix: Dark mode not switching

  • Default is prefers-color-scheme. For a toggle, add the @custom-variant dark line.
  • Check the <html> element actually gets the dark class.
  • Check the dark: variants sit on the affected elements.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.