tailwind

shadcn/ui is the primary component framework. This skill covers the Tailwind CSS utility system that powers shadcn components — use it for layout, responsive design, spacing, theming, and custom styling beyond what shadcn provides out of the box.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tailwind" with this command: npx skills add michaelkeevildown/claude-agents-skills/michaelkeevildown-claude-agents-skills-tailwind

Tailwind CSS

When to Use

shadcn/ui is the primary component framework. This skill covers the Tailwind CSS utility system that powers shadcn components — use it for layout, responsive design, spacing, theming, and custom styling beyond what shadcn provides out of the box.

Use this skill for:

  • Page layout (flex, grid, positioning, spacing)

  • Responsive design (breakpoints, container queries)

  • Theming and design tokens (@theme directive, CSS variables)

  • Customizing shadcn/ui components with utility overrides

  • Animation and transitions

  • State-based styling (hover, focus, disabled)

Defer to the shadcn-ui skill for: component selection, composition patterns (Dialog, Sheet, Command), form integration (react-hook-form + zod), and accessibility.

Setup (v4)

Tailwind v4 uses CSS-first configuration. No tailwind.config.js needed.

Import

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

This single import replaces the v3 directives (@tailwind base , @tailwind components , @tailwind utilities ). Template files are auto-discovered — no content paths required.

Installation

Next.js / Vite (PostCSS)

npm install tailwindcss @tailwindcss/postcss

Vite plugin (alternative)

npm install tailwindcss @tailwindcss/vite

PostCSS config:

// postcss.config.mjs export default { plugins: { "@tailwindcss/postcss": {}, }, };

Vite plugin (if not using PostCSS):

// vite.config.ts import tailwindcss from "@tailwindcss/vite";

export default defineConfig({ plugins: [tailwindcss()], });

Important: Sass and Less are incompatible with Tailwind v4. Use plain CSS with @theme and @layer directives.

Theming with @theme

The @theme directive defines design tokens in CSS. Each token creates both a utility class and a CSS variable.

Defining Tokens

@import "tailwindcss";

@theme { --font-display: "Satoshi", sans-serif; --color-brand: oklch(0.72 0.18 50); --color-brand-light: oklch(0.92 0.05 50); --radius-lg: 0.75rem; --breakpoint-3xl: 120rem; }

This generates:

  • font-display utility class

  • bg-brand , text-brand color utilities

  • rounded-lg using the custom radius

  • 3xl: responsive breakpoint

  • CSS variables: var(--font-display) , var(--color-brand) , etc.

Overriding vs Extending Defaults

/* Extend — adds to existing colors */ @theme { --color-brand: oklch(0.72 0.18 50); }

/* Override — replaces ALL colors / @theme { --color-: initial; --color-brand: oklch(0.72 0.18 50); --color-white: #fff; --color-black: #000; }

Use --color-*: initial (namespace wildcard) to clear defaults before defining your own.

Aligning with shadcn/ui Tokens

shadcn/ui defines semantic tokens in :root and .dark (see shadcn-ui skill for full setup). Tailwind's @theme can reference these:

@theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-destructive: var(--destructive); --color-border: var(--border); --color-ring: var(--ring); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); }

The inline keyword tells Tailwind not to emit the variables (shadcn already defines them in :root ).

Utility-First Patterns

Core Principle

Style by composing utility classes directly in markup:

<div className="flex items-center gap-3 rounded-lg border p-4"> <Avatar className="h-10 w-10" /> <div> <p className="text-sm font-medium">Jane Doe</p> <p className="text-xs text-muted-foreground">jane@example.com</p> </div> </div>

Spacing

p-4 → padding: 1rem (all sides) px-6 → padding-left + padding-right: 1.5rem py-2 → padding-top + padding-bottom: 0.5rem m-auto → margin: auto mt-8 → margin-top: 2rem gap-4 → gap: 1rem (flex/grid) space-y-4 → vertical spacing between children (margin-based)

Typography

text-sm → 0.875rem / 1.25rem text-lg → 1.125rem / 1.75rem font-semibold → font-weight: 600 tracking-tight → letter-spacing: -0.025em leading-relaxed → line-height: 1.625 truncate → overflow hidden + text-overflow ellipsis + whitespace nowrap line-clamp-3 → clamp to 3 lines

Arbitrary Values

Use brackets for one-off values not in the theme:

<div className="w-[calc(100%-2rem)] top-[117px] grid-cols-[1fr_2fr_1fr]">

Use underscores for spaces in arbitrary values: grid-cols-[1fr_2fr_1fr] .

Prefer theme tokens (w-96 , gap-4 ) over arbitrary values. Only use brackets when the design truly requires a one-off value not in your design system.

State Variants

<button className="bg-primary hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none"> Save </button>

Common variants: hover: , focus: , focus-visible: , active: , disabled: , aria-selected: , data-[state=open]: .

Group and Peer Modifiers

{/* Parent hover affects children */} <div className="group rounded-lg border p-4 hover:border-primary"> <h3 className="font-medium group-hover:text-primary">Title</h3> <p className="text-muted-foreground group-hover:text-foreground">Description</p> </div>

{/* Peer state affects siblings */} <input className="peer" type="email" /> <p className="hidden text-sm text-destructive peer-invalid:block"> Invalid email </p>

@apply (Use Sparingly)

@apply extracts utilities into custom CSS classes. In v4, variant modifiers (hover: , md: , focus: ) cannot be used inside @apply — use native CSS pseudo-classes and media queries instead, or prefer React components for stateful patterns.

/* OK — simple atomic pattern */ @layer components { .prose-link { @apply text-primary underline underline-offset-4; } }

/* Prefer a React component instead of @apply for anything with variants */

Layout Patterns

Flexbox

{ /* Horizontal bar with items centered, space between / } <div className="flex items-center justify-between gap-4"> <Logo /> <nav className="flex items-center gap-6">{/ links */}</nav> <UserMenu /> </div>;

{ /* Vertical stack */ } <div className="flex flex-col gap-4"> <Card /> <Card /> </div>;

Grid

{ /* Equal-width columns */ } <div className="grid grid-cols-3 gap-6"> <Card /> <Card /> <Card /> </div>;

{ /* Spanning columns / } <div className="grid grid-cols-4 gap-6"> <div className="col-span-3">{/ Main /}</div> <div>{/ Sidebar */}</div> </div>;

{ /* Auto-fill responsive grid (no breakpoints needed) */ } <div className="grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-6"> {items.map((item) => ( <Card key={item.id} /> ))} </div>;

Common Recipes

Sticky header + scrollable content:

<div className="flex h-screen flex-col"> <header className="sticky top-0 z-10 border-b bg-background px-6 py-3"> {/* Header /} </header> <main className="flex-1 overflow-y-auto p-6">{/ Scrollable content */}</main> </div>

Sidebar layout:

<div className="flex h-screen"> <aside className="w-64 shrink-0 border-r bg-muted/40 p-4"> {/* Sidebar /} </aside> <main className="flex-1 overflow-y-auto p-6">{/ Content */}</main> </div>

Centered content (both axes):

<div className="flex min-h-screen items-center justify-center"> <Card className="w-full max-w-md">{/* Centered card */}</Card> </div>

Responsive Design

Mobile-First Breakpoints

Unprefixed utilities apply to all screen sizes. Prefixed utilities apply at that breakpoint and above.

Prefix Min-width Target

(none) 0px All sizes (mobile base)

sm:

640px Large phones

md:

768px Tablets

lg:

1024px Laptops

xl:

1280px Desktops

2xl:

1536px Large desktops

Responsive Stacking to Grid

{ /* Single column on mobile, 2 cols on tablet, 3 cols on desktop */ } <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3"> <Card /> <Card /> <Card /> </div>;

Responsive Typography

<h1 className="text-2xl font-bold md:text-3xl lg:text-4xl">Dashboard</h1>

Responsive Spacing

<section className="px-4 py-8 md:px-8 md:py-12 lg:px-16 lg:py-16"> {/* Content with increasing padding on larger screens */} </section>

Show/Hide by Breakpoint

{ /* Mobile navigation toggle — hidden on desktop */ } <Button className="md:hidden" variant="ghost" size="icon"> <Menu className="h-5 w-5" /> </Button>;

{ /* Desktop sidebar — hidden on mobile / } <aside className="hidden md:block w-64">{/ Sidebar */}</aside>;

Container Queries

Container queries respond to a parent container's size instead of the viewport:

{ /* Define container / } <div className="@container"> {/ Respond to container size */} <div className="flex flex-col @md:flex-row @md:items-center gap-4"> <Avatar /> <div> <p className="text-sm @lg:text-base">Name</p> </div> </div> </div>;

Container breakpoints: @sm (320px), @md (448px), @lg (512px), @xl (576px), etc.

Custom Breakpoints

@theme { --breakpoint-3xl: 120rem; --breakpoint-xs: 480px; }

Dark Mode

With shadcn/ui (Preferred Approach)

When using shadcn/ui, dark mode is handled by CSS variables. The :root and .dark selectors define token values, and utilities like bg-background , text-foreground , border-border automatically switch. No dark: prefix needed for themed elements.

{ /* These auto-switch between light/dark — no dark: prefix */ } <div className="bg-background text-foreground border-border"> <p className="text-muted-foreground">Auto-themed content</p> </div>;

See the shadcn-ui skill for next-themes setup and theme toggle implementation.

dark: Variant (For Non-Themed Values)

Use dark: only when you need behavior outside the shadcn token system:

{ /* Custom illustration that needs different treatment in dark mode */ } <div className="bg-blue-50 dark:bg-blue-950"> <img className="opacity-100 dark:opacity-80" src="/illustration.svg" /> </div>;

Media Query Strategy

For system-only dark mode (no toggle), Tailwind uses prefers-color-scheme by default. The class-based strategy (required for next-themes toggle) is configured by shadcn's setup.

Customizing shadcn Components with Tailwind

Using cn() to Override Styles

shadcn components accept className . Use cn() (from @/lib/utils ) to merge your utilities with the component's defaults:

import { Button } from "@/components/ui/button";

{ /* Full-width button with left-aligned text */ } <Button className="w-full justify-start text-left font-normal"> Select a date </Button>;

{ /* Card with custom max-width and shadow / } <Card className="max-w-lg shadow-lg"> <CardContent>{/ ... */}</CardContent> </Card>;

cn() handles class conflicts — your overrides win over component defaults (e.g., adding justify-start replaces the button's default justify-center ).

Adding Responsive Behavior

{ /* Dialog content that's full-width on mobile, constrained on desktop / } <DialogContent className="w-full max-w-full sm:max-w-lg"> {/ ... */} </DialogContent>;

{ /* Sidebar that collapses on mobile / } <SheetContent side="left" className="w-[280px] sm:w-[350px]"> {/ ... */} </SheetContent>;

When to Customize via Utilities vs Component Source

Scenario Approach

One-off sizing/spacing tweak className override

Consistent variant across the app Edit component source in components/ui/

New variant (e.g., variant="warning" ) Add to component's cva() variants

Layout around component Wrapper div with utilities

Animation and Transitions

Transitions

{ /* Smooth hover effect / } <div className="transition-colors duration-200 hover:bg-muted"> {/ content */} </div>;

{ /* Transform on hover / } <div className="transition-transform duration-300 hover:scale-105"> {/ content */} </div>;

{ /* Multiple properties / } <div className="transition-all duration-200 ease-in-out">{/ content */}</div>;

Common duration values: duration-75 , duration-100 , duration-150 , duration-200 , duration-300 , duration-500 .

Built-in Animations

<Loader2 className="h-4 w-4 animate-spin" /> {/* Spinning loader /} <div className="animate-pulse">Loading...</div> {/ Pulsing skeleton /} <div className="animate-bounce">↓</div> {/ Bouncing arrow */}

tw-animate-css

The tw-animate-css package replaces the deprecated tailwindcss-animate plugin. It provides enter/exit animations used by shadcn/ui components (Dialog, Sheet, Popover, etc.).

npm install tw-animate-css

/* globals.css */ @import "tailwindcss"; @import "tw-animate-css";

Custom Keyframes

/* globals.css */ @theme { --animate-fade-in: fade-in 0.3s ease-out; --animate-slide-up: slide-up 0.4s ease-out; }

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

@keyframes slide-up { from { transform: translateY(1rem); opacity: 0; } to { transform: translateY(0); opacity: 1; } }

Usage: <div className="animate-fade-in"> .

Anti-Patterns

  1. Dynamic Class Construction

// BAD — Tailwind can't scan dynamically built class names function Badge({ color }: { color: string }) { return <span className={bg-${color}-500 text-${color}-50}>...</span>; }

// GOOD — use a static lookup map const colorStyles = { red: "bg-red-500 text-red-50", blue: "bg-blue-500 text-blue-50", green: "bg-green-500 text-green-50", } as const;

function Badge({ color }: { color: keyof typeof colorStyles }) { return <span className={colorStyles[color]}>...</span>; }

Tailwind scans source files for complete class names at build time. String interpolation produces class names that don't exist in the source, so they won't be generated.

  1. @apply Overuse

/* BAD — reimplements what a React component does better */ .card { @apply flex flex-col gap-4 rounded-lg border bg-background p-6 shadow-sm; } .card-title { @apply text-lg font-semibold leading-none tracking-tight; }

/* GOOD — use shadcn's <Card> component, or a custom React component */

@apply hides styles from the markup, makes them harder to override, and in v4, modifiers like hover: and responsive prefixes don't work on custom classes. Prefer React components for anything with variants or state.

  1. Hardcoded Colors Instead of Theme Tokens

// BAD — breaks dark mode, ignores theme <div className="bg-white text-gray-900 border-gray-200">

// GOOD — uses semantic tokens (auto-switches in dark mode) <div className="bg-background text-foreground border-border">

Always use semantic token classes (bg-primary , text-muted-foreground , border-border ) instead of raw color values. The tokens are defined by shadcn's CSS variables and switch automatically between light/dark.

  1. Redundant dark: With CSS Variables

// BAD — unnecessary when using shadcn tokens <div className="bg-background dark:bg-background text-foreground dark:text-foreground">

// ALSO BAD — fighting the token system <div className="bg-white dark:bg-gray-900 text-black dark:text-white">

// GOOD — tokens handle light/dark automatically <div className="bg-background text-foreground">

If you're using shadcn/ui's CSS variable system, dark: prefixes on themed properties are redundant. Only use dark: for values outside the token system.

  1. Desktop-First Design

// BAD — styles for large screens, then overrides for small <div className="grid grid-cols-3 gap-8 sm:grid-cols-1 sm:gap-4">

// GOOD — mobile base, enhance for larger screens <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">

Tailwind breakpoints are min-width (mobile-first). Start with mobile styles (no prefix), then add complexity at larger breakpoints.

  1. Mixing Sass/Less with v4

// BAD — Sass is incompatible with Tailwind v4's Oxide engine $primary: #3b82f6; .button { background: $primary; @apply rounded-lg px-4 py-2; }

/* GOOD — use native CSS with @theme */ @theme { --color-primary: oklch(0.62 0.21 255); }

Tailwind v4 processes CSS natively through its Rust-based engine. Sass/Less syntax causes build failures.

  1. Overly Long Class Strings Without Extraction

// BAD — same pattern repeated in 5 places <div className="flex items-center gap-3 rounded-lg border bg-card p-4 shadow-sm transition-colors hover:bg-accent"> ... </div>; { /* ...repeated 4 more times */ }

// GOOD — extract to a component function ListItem({ children, ...props }: React.ComponentProps<"div">) { return ( <div className={cn( "flex items-center gap-3 rounded-lg border bg-card p-4 shadow-sm transition-colors hover:bg-accent", props.className, )} {...props} > {children} </div> ); }

If a utility combination repeats more than twice, extract it to a React component. Always forward className via cn() so consumers can override styles.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

neo4j-data-models

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

neo4j-cypher

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

git-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review