Capítulo 94 de 108

Tailwind v4

Core Idea

shadcn/ui fully supports Tailwind v4 + React 19: new @theme/@theme inline directives, data-slot on every primitive, no more forwardRef, OKLCH colors. New projects default to this; existing v3/React 18 projects keep working until manually upgraded.

Key Concepts

  • @theme / @theme inline: Tailwind v4 directive mapping CSS custom properties to design tokens; inline lets you reference var(--background) directly without re-wrapping in hsl().
  • data-slot: Every primitive now carries a data-slot="..." attribute for styling hooks, replacing reliance on component structure alone.
  • forwardRef removal: Components now take React.ComponentProps<...> directly (no React.forwardRef, no ref={ref}, no displayName).
  • size-* utility: Replaces w-* h-* pairs (e.g. size-4 instead of w-4 h-4), supported via tailwind-merge.
  • tw-animate-css: Replaces deprecated tailwindcss-animate plugin; imported via @import "tw-animate-css" instead of @plugin 'tailwindcss-animate'.
  • Non-breaking: Existing Tailwind v3/React 18 apps are unaffected; only new projects/new component adds default to v4.

Code Examples

:root {
  --background: hsl(0 0% 100%);
  --foreground: hsl(0 0% 3.9%);
}
.dark {
  --background: hsl(0 0% 3.9%);
  --foreground: hsl(0 0% 98%);
}
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
}
  • O que demonstra: padrão correto de migração — hsl() fica na definição da variável (:root/.dark), @theme inline só referencia via var(), sem re-wrap.
function AccordionItem({
  className,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
  return (
    <AccordionPrimitive.Item
      data-slot="accordion-item"
      className={cn("border-b last:border-b-0", className)}
      {...props}
    />
  )
}
  • O que demonstra: forma pós-migração de um primitive — sem forwardRef, com data-slot, tipagem via ComponentProps.

Reference Tables

ChangeBeforeAfter
Sizing utilityw-4 h-4size-4
Animation plugin@plugin 'tailwindcss-animate'@import "tw-animate-css"
Chart colorcolor: "hsl(var(--chart-1))"color: "var(--chart-1)"
Ref patternReact.forwardRef<...>React.ComponentProps<...> (no ref)

Anti-patterns

  • Wrapping theme colors in hsl() inside @theme inline: wrap once at the :root/.dark variable definition, not again inside @theme.
  • Upgrading Tailwind without reading the compatibility docs: Tailwind v4 uses bleeding-edge browser features; check https://tailwindcss.com/docs/compatibility first.
  • Re-adding components with --overwrite without committing first: npx shadcn@latest add --all --overwrite overwrites existing components; always git commit before running it.

Key Takeaways

  1. Migration path: run @tailwindcss/upgrade@next codemod → move CSS vars out of @layer base → wrap in hsl() → use @theme inline → strip hsl() from the @theme block itself.
  2. Use remove-forward-ref codemod (or manual steps) to drop forwardRef across your local copied components.
  3. pnpm up "@radix-ui/*" cmdk lucide-react recharts tailwind-merge clsx --latest bumps the core dependency set for v4.
  4. To pick up the new (March 2025) dark-mode OKLCH colors on a v4-native (not upgraded) project, commit changes then run npx shadcn@latest add --all --overwrite.

Connects To

  • react-19 (ch093): superseded guide for the npm peer-dependency workaround.
  • theming (main docs): base color / OKLCH reference used when updating dark mode colors.