# Step 4 — TailwindCSS v4 + shadcn/ui Components

iKanban styles every component with [TailwindCSS v4](https://tailwindcss.com) utility classes, and builds its UI primitives on [shadcn/ui](https://ui.shadcn.com) — which, unlike a normal npm package, **generates component source code directly into your repo** rather than shipping a compiled library. That's why `Badge`, `Dialog`, `Tooltip`, and friends live at `src/components/ui/` as plain, editable `.tsx` files instead of coming from `node_modules`.

## `components.json` — how new components land

```json
{
  "tailwind": { "config": "tailwind.config.js", "css": "src/index.css", "baseColor": "slate" },
  "aliases": { "components": "@/components", "utils": "@/lib/utils" }
}
```

Running `npx shadcn-ui@latest add <component>` reads this file and writes a new file under `@/components/ui/`, already wired to the `@/` alias from Step 3. This is why CLAUDE.md's core rule #4 says **shadcn/ui only** for new UI primitives — hand-rolling a modal or dropdown means losing this generated, consistent styling contract.

## Composing primitives: back to `AgentModeBadge`

Step 2 showed the TypeScript side of `AgentModeBadge.tsx`. Here's the composition of shadcn primitives underneath it:

```tsx
import { Badge } from '@/components/ui/badge';
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from '@/components/ui/tooltip';

// ...
return (
  <TooltipProvider>
    <Tooltip>
      <TooltipTrigger asChild>{badge}</TooltipTrigger>
      <TooltipContent>
        <p>{t(config.descriptionKey)}</p>
      </TooltipContent>
    </Tooltip>
  </TooltipProvider>
);
```

Three things worth noticing:

1. **`asChild`** — a Radix UI pattern (which shadcn/ui wraps) that tells `TooltipTrigger` to merge its behavior onto its child (`badge`) instead of rendering an extra wrapper `<div>`. You'll see `asChild` throughout iKanban wherever a trigger needs to wrap an existing styled element.
2. **Composition over configuration** — instead of one `<Badge tooltip="..." tooltipPosition="top">` component with a dozen props, iKanban composes small pieces (`Tooltip` + `TooltipTrigger` + `TooltipContent`) the way you'd nest HTML tags. This scales better as requirements grow.
3. **Conditional Tailwind classes inline**:
   ```tsx
   className={`gap-1 ${mode === 'both' ? 'border-green-500 text-green-600 dark:text-green-400' : ''} ${className || ''}`}
   ```
   Tailwind classes are just strings, so plain template literals and ternaries are enough to make styling conditional on props or state — no separate CSS file or `styled-components` needed. `dark:` prefixes apply only in dark mode, handled automatically by Tailwind's dark-mode variant.

## Checkpoint

Find one other shadcn/ui component under `vibe-frontend/src/components/ui/` (e.g. `dialog.tsx` or `alert.tsx`) and identify:
1. Which Radix primitive it wraps (check the imports at the top).
2. One `dark:`-prefixed Tailwind class controlling its dark-mode appearance.

Continue to **Step 5 — Local & Global State with Zustand**, where we move from stateless display components to components that hold and update their own state.
