agentleFS
mikulgohilmikulgohil/claude-skills/skills/frontend/tailwind-v4/SKILL.md

tailwind-v4

Skill0 starsChanged 5 months agoMITView on GitHub

Tailwind CSS v4 — @theme configuration, utility patterns, responsive and dark-mode design, container queries, and v3-to-v4 migration. Use when styling with Tailwind or migrating a config to v4.

11 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use operational-enterprise-ai, product-proof-saas and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • boraoztuncSkill

    operational-enterprise-ai

    Create or redesign enterprise AI, automation, security, and operations product pages that explain system boundaries, approvals, auditability, exceptions, and rollback. Use for dark cinematic heroes, hairline grids, metric pauses, expandable solution rows, case-study evidence, security proof, and qualified demo or waitlist handoffs.

    Also in 3 projects that use this

  • boraoztuncSkill

    product-proof-saas

    Create or redesign SaaS and AI product landing pages where a real workflow, interface, or deterministic demo is the central proof. Use for pale atmospheric shells, product UI in the hero, prompt-to-output stories, audience tabs, compact feature modules, honest pricing comparisons, and FAQ handoffs.

    Also in 3 projects that use this

  • Dragoon0xSkill

    pricing-page

    Present pricing in ways that reduce friction and increase clarity. Handles tier naming, feature comparison, anchor pricing, and plan selection psychology. Use when designing pricing pages, plan selectors, or upgrade prompts.

    Also in 3 projects that use this

  • ParthJadhavSkill

    app-store-screenshots

    Use when building App Store or Google Play screenshot pages, generating exportable marketing screenshots for iOS, macOS, and/or Android apps, or scaffolding a screenshot editor with Next.js. Triggers on app store, mac app store, play store, screenshots, marketing assets, html-to-image, phone mockup, mac mockup, android screenshots, feature graphic.

    Also in 3 projects that use this

  • silvainfmSkill

    page-cro

    When the user wants to optimize, improve, or increase conversions on any marketing page — including homepage, landing pages, pricing pages, feature pages, or blog posts. Also use when the user says "CRO," "conversion rate optimization," "this page isn't converting," "improve conversions," or "why isn't this page working." For signup/registration flows, see signup-flow-cro. For post-signup activation, see onboarding-cro. For forms outside of signup, see form-cro. For popups/modals, see popup-cro.

    Also in 3 projects that use this

  • silvainfmSkill

    analytics-tracking

    When the user wants to set up, improve, or audit analytics tracking and measurement. Also use when the user mentions "set up tracking," "GA4," "Google Analytics," "conversion tracking," "event tracking," "UTM parameters," "tag manager," "GTM," "analytics implementation," or "tracking plan." For A/B test measurement, see ab-test-setup.

    Also in 3 projects that use this

  • +1 more from boraoztunc
  • +1 more from silvainfm

The file

Files
SKILL.md

Tailwind Operations

Comprehensive Tailwind CSS patterns covering layout, responsive design, components, dark mode, animations, and v4 migration.

Layout Decision Tree

Which layout approach?
│
├─ Items in a single row or column?
│  └─ Use Flexbox
│     ├─ Row:    class="flex items-center gap-4"
│     ├─ Column: class="flex flex-col gap-4"
│     ├─ Wrap:   class="flex flex-wrap gap-4"
│     └─ Push item to end: class="flex" + child class="ml-auto"
│
├─ Items in a 2D grid (rows AND columns)?
│  └─ Use CSS Grid
│     ├─ Equal columns:   class="grid grid-cols-3 gap-6"
│     ├─ Responsive grid:  class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
│     ├─ Sidebar layout:  class="grid grid-cols-[250px_1fr] gap-6"
│     ├─ Spanning:         child class="col-span-2" or "row-span-2"
│     └─ Auto-fill:        class="grid grid-cols-[repeat(auto-fill,minmax(250px,1fr))] gap-6"
│
├─ Component should adapt to its CONTAINER size (not viewport)?
│  └─ Use Container Queries (v3.2+ / v4 native)
│     ├─ Parent:  class="@container"
│     ├─ Child:   class="@sm:flex-row @lg:grid-cols-3"
│     └─ Named:   class="@container/sidebar" → child: "@sm/sidebar:flex-row"
│
├─ Centering something?
│  ├─ Horizontal text:  class="text-center"
│  ├─ Horizontal block: class="mx-auto" (needs width)
│  ├─ Flex center:      class="flex items-center justify-center"
│  ├─ Grid center:      class="grid place-items-center"
│  └─ Absolute center:  class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
│
└─ Full-page layout (header/sidebar/content/footer)?
   └─ Use Grid with named areas or template rows
      ├─ Sticky header:  class="grid grid-rows-[auto_1fr_auto] min-h-screen"
      └─ Sidebar + main: class="grid grid-cols-[250px_1fr] min-h-screen"

Responsive Design Quick Reference

Breakpoints (Mobile-First)

PrefixMin WidthTypical Target
(none)0pxMobile (default)
sm:640pxLarge phones, landscape
md:768pxTablets
lg:1024pxSmall laptops
xl:1280pxDesktops
2xl:1536pxLarge screens

Mobile-first means: base styles apply to mobile, add breakpoint prefixes to override upward.

<!-- Stack on mobile, 2 columns on tablet, 3 on desktop -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  <div>Card 1</div>
  <div>Card 2</div>
  <div>Card 3</div>
</div>

<!-- Hide on mobile, show on desktop -->
<nav class="hidden lg:flex items-center gap-6">...</nav>

<!-- Full width on mobile, constrained on desktop -->
<div class="w-full max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">...</div>

Container Queries

<!-- Parent declares itself as a container -->
<div class="@container">
  <!-- Children respond to PARENT width, not viewport -->
  <div class="flex flex-col @sm:flex-row @lg:grid @lg:grid-cols-3 gap-4">
    <div>Adapts to container</div>
  </div>
</div>

<!-- Named container (useful when nesting) -->
<div class="@container/card">
  <h2 class="text-sm @md/card:text-lg">Responds to card container</h2>
</div>

Fluid Typography with clamp()

<!-- Fluid heading: 1.5rem at small, 3rem at large, scales between -->
<h1 class="text-[clamp(1.5rem,4vw,3rem)]">Fluid Heading</h1>

<!-- Fluid body text -->
<p class="text-[clamp(0.875rem,1.5vw,1.125rem)] leading-relaxed">
  Body text that scales smoothly.
</p>

Dark Mode Decision Tree

Which dark mode strategy?
│
├─ Manual toggle (user preference stored)?
│  └─ class strategy (v3) / selector strategy (v4)
│
│     v3: tailwind.config.js
│     module.exports = { darkMode: 'class' }
│     → Add class="dark" to <html> element
│
│     v4: CSS @custom-variant or default behavior
│     @custom-variant dark (&:where(.dark, .dark *));
│     → Same toggle, add class="dark" to <html>
│
├─ Follow system preference only?
│  └─ media strategy
│
│     v3: tailwind.config.js
│     module.exports = { darkMode: 'media' }
│     → Uses prefers-color-scheme automatically
│
│     v4: Default behavior (no config needed)
│     → Uses prefers-color-scheme out of the box
│
└─ Custom selector (data attribute, etc.)?
   └─ selector strategy (v4 only)

      v4: @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
      → Add data-theme="dark" to <html>

Dark Mode Patterns

<!-- Background and text -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">

  <!-- Card with dark variant -->
  <div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700">
    <h3 class="text-gray-900 dark:text-white font-semibold">Card Title</h3>
    <p class="text-gray-600 dark:text-gray-400">Card content adapts to dark mode.</p>
  </div>

  <!-- Input with dark variant -->
  <input type="text"
    class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600
           text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500
           focus:ring-2 focus:ring-blue-500 rounded-lg px-4 py-2"
    placeholder="Type here...">
</div>

Component Patterns Quick Reference

<!-- Card -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
  <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Title</h3>
  <p class="text-gray-600 dark:text-gray-400">Content here.</p>
</div>

<!-- Button variants -->
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 transition-colors">Primary</button>
<button class="bg-gray-200 text-gray-800 px-4 py-2 rounded-lg hover:bg-gray-300 transition-colors">Secondary</button>
<button class="border border-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-50 transition-colors">Outline</button>
<button class="text-blue-600 px-4 py-2 rounded-lg hover:bg-blue-50 transition-colors">Ghost</button>

<!-- Form input -->
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Email</label>
<input type="email"
  class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
         bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
         focus:ring-2 focus:ring-blue-500 focus:border-transparent"
  placeholder="you@example.com">

<!-- Navbar -->
<nav class="bg-white dark:bg-gray-900 shadow">
  <div class="max-w-7xl mx-auto px-4 flex items-center justify-between h-16">
    <a href="/" class="text-xl font-bold text-gray-900 dark:text-white">Logo</a>
    <div class="hidden md:flex items-center gap-6">
      <a href="#" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white">Home</a>
      <a href="#" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">CTA</a>
    </div>
  </div>
</nav>

<!-- Modal overlay -->
<div class="fixed inset-0 z-50 flex items-center justify-center">
  <div class="fixed inset-0 bg-black/50" aria-hidden="true"></div>
  <div class="relative bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-md mx-4" role="dialog" aria-modal="true">
    <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Modal Title</h2>
    <p class="text-gray-600 dark:text-gray-400 mb-6">Modal content goes here.</p>
    <div class="flex justify-end gap-3">
      <button class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg">Cancel</button>
      <button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Confirm</button>
    </div>
  </div>
</div>

<!-- Badge -->
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">Active</span>

<!-- Alert -->
<div class="flex items-start gap-3 p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800" role="alert">
  <span class="text-red-600 dark:text-red-400 mt-0.5" aria-hidden="true">&#10007;</span>
  <div>
    <h4 class="text-sm font-medium text-red-800 dark:text-red-300">Error</h4>
    <p class="text-sm text-red-700 dark:text-red-400 mt-1">Something went wrong. Please try again.</p>
  </div>
</div>

Tailwind v4 Quick Reference

Major Changes from v3

Areav3v4
Configurationtailwind.config.jsCSS-first: @theme in CSS
Theme valuesJS theme.extend.colors@theme { --color-brand: #3b82f6; }
PluginsJS plugin() function@plugin "my-plugin" in CSS
Config filemodule.exports = {...}@config "./legacy.config.js" (compat)
PostCSStailwindcss package@tailwindcss/postcss
VitePostCSS plugin@tailwindcss/vite (faster)
ColorsNamed scales (gray-50..950)Same + OKLCH support
Container queriesPlugin requiredNative @container, @sm:, @md:
Entry animationsJS needed@starting-style (CSS native)

v4 CSS-First Config

/* v4: Define theme in CSS */
@import "tailwindcss";

@theme {
  --color-brand: #3b82f6;
  --color-brand-dark: #1d4ed8;
  --font-display: "Inter", sans-serif;
  --breakpoint-3xl: 1920px;
  --spacing-18: 4.5rem;
}

/* v4: Import a plugin */
@plugin "@tailwindcss/typography";

/* v4: Use legacy JS config as fallback */
@config "./tailwind.config.js";

v4 New Utilities

<!-- Container queries (native in v4) -->
<div class="@container">
  <div class="@sm:flex @md:grid @md:grid-cols-2">Adapts to container</div>
</div>

<!-- @starting-style: entry animations without JS -->
<!-- Applied via CSS - Tailwind v4 supports it natively -->

<!-- Anchor positioning (experimental) -->
<!-- Position elements relative to an anchor element via CSS -->

<!-- New shadow and ring defaults -->
<div class="shadow-sm ring ring-blue-500/20">Improved defaults</div>

Animation Patterns

Transition Utilities

<!-- Color transition (most common) -->
<button class="bg-blue-600 hover:bg-blue-700 transition-colors duration-150">
  Hover me
</button>

<!-- Multiple properties -->
<div class="transform hover:scale-105 hover:shadow-lg transition-all duration-200 ease-in-out">
  Scale and shadow on hover
</div>

<!-- Specific properties -->
<div class="transition-[transform,opacity] duration-300 ease-out">
  Only transform and opacity animate
</div>

Built-in Animations

<!-- Spin (loading spinners) -->
<svg class="animate-spin h-5 w-5 text-blue-600" viewBox="0 0 24 24">
  <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
  <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
</svg>

<!-- Pulse (skeleton loaders) -->
<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-4 rounded w-3/4"></div>

<!-- Ping (notification indicator) -->
<span class="relative flex h-3 w-3">
  <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
  <span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>

<!-- Bounce -->
<div class="animate-bounce">&#8595;</div>

Custom Keyframes (v3 Config)

// tailwind.config.js (v3)
module.exports = {
  theme: {
    extend: {
      keyframes: {
        'fade-in': {
          '0%': { opacity: '0', transform: 'translateY(10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        'slide-in-right': {
          '0%': { transform: 'translateX(100%)' },
          '100%': { transform: 'translateX(0)' },
        },
      },
      animation: {
        'fade-in': 'fade-in 0.3s ease-out',
        'slide-in-right': 'slide-in-right 0.3s ease-out',
      },
    },
  },
}

Custom Keyframes (v4 CSS)

/* v4: Define in CSS with @theme */
@theme {
  --animate-fade-in: fade-in 0.3s ease-out;
  --animate-slide-in-right: slide-in-right 0.3s ease-out;
}

@keyframes fade-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes slide-in-right {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}

Entry Animations with @starting-style (v4)

/* Dialog that animates in from transparent/translated */
dialog[open] {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.3s, transform 0.3s;

  @starting-style {
    opacity: 0;
    transform: translateY(10px);
  }
}

State Modifiers Quick Reference

ModifierTriggers OnExample
hover:Mouse hoverhover:bg-blue-700
focus:Element focused (all focus)focus:ring-2
focus-visible:Keyboard focus onlyfocus-visible:outline-2
focus-within:Child is focusedfocus-within:ring-2
active:Being clicked/pressedactive:scale-95
disabled:disabled attributedisabled:opacity-50 disabled:cursor-not-allowed
group-hover:Parent .group hoveredgroup-hover:text-blue-600
group-focus:Parent .group focusedgroup-focus:ring-2
peer-checked:Sibling .peer checkedpeer-checked:bg-blue-600
peer-invalid:Sibling .peer invalidpeer-invalid:text-red-500
data-[state=open]:Custom data attributedata-[state=open]:rotate-180
aria-expanded:aria-expanded="true"aria-expanded:bg-gray-100
aria-selected:aria-selected="true"aria-selected:font-bold
open:<details> or <dialog> openopen:bg-gray-50
first:First childfirst:rounded-t-lg
last:Last childlast:rounded-b-lg
odd:Odd childrenodd:bg-gray-50
even:Even childreneven:bg-white
placeholder:Placeholder textplaceholder:text-gray-400
motion-reduce:Prefers reduced motionmotion-reduce:transition-none
motion-safe:No motion preferencemotion-safe:animate-bounce
print:Print mediaprint:hidden

Group and Peer Patterns

<!-- Group: parent state affects children -->
<a href="#" class="group flex items-center gap-3 p-3 rounded-lg hover:bg-gray-100">
  <div class="w-10 h-10 bg-gray-200 group-hover:bg-blue-100 rounded-lg"></div>
  <span class="text-gray-700 group-hover:text-blue-600">Hover the whole card</span>
</a>

<!-- Named groups (nested groups) -->
<div class="group/card p-4">
  <div class="group/button">
    <span class="group-hover/card:text-blue-600 group-hover/button:underline">
      Responds to specific parent
    </span>
  </div>
</div>

<!-- Peer: sibling state affects next sibling -->
<input type="checkbox" class="peer sr-only" id="toggle">
<label for="toggle" class="peer-checked:bg-blue-600 peer-checked:text-white px-4 py-2 rounded-lg cursor-pointer">
  Toggle me
</label>

<!-- Form validation with peer -->
<input type="email" class="peer" required>
<p class="hidden peer-invalid:block text-sm text-red-500 mt-1">
  Please enter a valid email.
</p>

Common Gotchas

GotchaWhyFix
Dynamic class names don't work: `bg-${color}-500`Tailwind scans source for complete class strings at build time. String interpolation produces classes it never sees.Use complete classes: const colors = { red: 'bg-red-500', blue: 'bg-blue-500' } and select by key.
Styles not applying (specificity)Another CSS rule or @apply has higher specificity.Use !important modifier: !text-red-500. Or restructure to avoid conflicts.
@apply breaks with component libraries@apply resolves at build time and can't access runtime theme values or conflict with scoped styles.Prefer inline utility classes. Reserve @apply for base styles or markdown content.
Prose plugin styles leak@tailwindcss/typography prose applies broad element selectors (h1, p, a, etc.).Scope with prose only on content wrappers. Use not-prose class to exclude sections.
Classes missing in productionJIT content detection didn't scan the file containing the class.Ensure content paths in config cover all template files including component libraries.
Dark mode flash (FOUC)Class-based dark mode renders light first until JS adds dark class.Add inline <script> in <head> that reads localStorage and sets dark class before paint.
Container queries not scopedChild @sm: responds to nearest @container ancestor, which may not be the intended one.Use named containers: @container/card and @sm/card:flex.
Arbitrary values vs configw-[137px] works but creates one-off values. Repeated arbitrary values signal missing design tokens.Add recurring values to theme config: spacing: { '137': '137px' }.
group / peer naming collisionsNested groups without names cause children to respond to wrong ancestor.Use named groups: group/card, group/button.
Responsive order mattersAdding lg:flex without base block or hidden can cause unexpected behavior on smaller screens.Always define the mobile-first base, then override upward: hidden lg:flex.
Transition on display: nonehidden to block can't be transitioned because display isn't animatable.Use opacity-0/opacity-100 with invisible/visible, or use @starting-style (v4).
Purge removes dynamic classesTailwind purges classes not found as complete strings in scanned files.Add classes to safelist in config, or use a safelist comment in the source file.

Reference Files

FileContentLines
references/component-patterns.mdCards, buttons, forms, navigation, modals, tables, alerts, badges, avatars, dropdowns, tooltips, skeleton loaders, accessibility~700
references/v4-migration.mdCSS-first config, @theme, @plugin, removed utilities, container queries, @starting-style, migration steps, breaking changes~500
references/configuration.mdTheme config (v3+v4), colors, spacing, typography, plugins, @layer, @apply, custom variants, dark mode, container queries~500

See Also

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.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

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.