Skill: iliaal/compound-eng-tailwind-css (version 3.0.5)

--- name: ia-tailwind-css class: language description: >- 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. paths: "**/*.css,**/tailwind.config.*" --- # Tailwind CSS v4 **Verify before implementing**: For v4-specific syntax (`@theme`, `@variant`, CSS-first config), search current docs via `search_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 | ```css @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](./references/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-*` over `w-* 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 `@theme` before using `[#hex]` - **Never construct classes dynamically** -- `text-${color}-500` won't be detected; use complete class names - **`@utility` over `@apply` with `@layer`** -- `@apply` on `@layer` classes fails in v4 - **Parent padding over last-child margin** -- use padding on containers instead of bottom margins on the last child ## ESLint Integration Use [REDACTED_SECRET] for automated class validation: - `no-conflicting-classes` -- catches `text-red-500 text-blue-500` - `no-unknown-classes` -- flags typos - `enforce-canonical-classes` -- normalizes shorthand - `no-duplicate-classes` -- removes redundant entries - `no-deprecated-classes` -- catches v3 classes removed in v4 - `useSortedClasses` -- enforces canonical class order; configure `attributes: ["classList"]` and `functions: ["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. ```typescript import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } ``` ```typescript // 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()`). ```typescript 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](./references/component-patterns.md) 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) ```css :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 build` or equivalent) - No v3 class names remain in changed files (check with `@tailwindcss/upgrade --dry-run` if available) - No conflicting classes on the same element ## References - [Component patterns](./references/component-patterns.md) -- tailwind-variants slots, CVA, compound components - [Layout patterns](./references/layout-patterns.md) -- grid areas, container queries, z-index management, fluid typography

path: SPEC.md
content: # 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.md` and routed via the should_trigger phrasings in `distillery/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-plugin` flags >70% description overlap as DUPLICATE_TRIGGER).
- <!-- to fill in: domain-specific exclusions when the skill drifts -->

## Trigger Context

- Class: `language`
- Hook regex: `plugins/compound-engineering/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/compound-engineering/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/compound-engineering/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):

```bash
python3 distillery/scripts/distiller.py validate-plugin --component ia-tailwind-css
python3 distillery/scripts/distiller.py test-triggers --skill ia-tailwind-css
```

Deeper (when behavior risk warrants):

```bash
python3 distillery/scripts/distiller.py dspy-eval ia-tailwind-css
python3 distillery/scripts/distiller.py diagnose-negatives ia-tailwind-css
```

Acceptance gates:
- `validate-plugin --component ia-tailwind-css` returns 0 HIGH findings.
- `test-triggers --skill ia-tailwind-css` returns 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.md` when the runtime workflow, branch conditions, or output contract changes.
- Update this `SPEC.md` when 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.sh` whenever fixture positives expose a missed phrasing; verify F1 = 1.0 with `eval-triggers` before committing.
- Run the full release pipeline via `/release` -- never bump versions or update CHANGELOG.md from a per-skill edit.


path: references/component-patterns.md
content: # Component Patterns

## tailwind-variants (tv)

Type-safe component variants with slots, composition, and responsive support.

### Slots API

```typescript
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

```typescript
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

```typescript
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.

```typescript
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.

```typescript
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 |


path: references/layout-patterns.md
content: # Layout Patterns

## Grid Template Areas

Define reusable grid areas with `@utility`:

```css
@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; }
```

```html
<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

```html
<!-- 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:

```css
@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.

```css
@plugin "@tailwindcss/container-queries";
```

```html
<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()`:

```css
@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

```css
@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:

```css
@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:

```css
[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)

```css
@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.


path: references/v3-to-v4-migration.md
content: # 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.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)