Back

Como Integrar ShadCN com Next.js

Como Integrar ShadCN com Next.js

Configurar uma biblioteca moderna de componentes de UI não deveria parecer como montar móveis sem instruções. Se você está construindo com Next.js 15 e quer componentes bonitos e personalizáveis sem a frustração de caixa-preta das bibliotecas tradicionais, este guia mostra exatamente como integrar Shadcn UI ao seu projeto.

Este artigo cobre o processo completo de configuração do Shadcn UI com Next.js, desde a criação inicial do projeto até a instalação de componentes, configuração do Tailwind CSS e implementação adequada do modo escuro. Você aprenderá como evitar problemas comuns de compatibilidade com React 19 e configurar um sistema de componentes pronto para produção que você realmente possui.

Principais Pontos

  • Shadcn UI copia o código-fonte dos componentes diretamente para seu projeto, dando-lhe controle total de propriedade e personalização
  • A CLI cuida da instalação e gerenciamento de dependências, tornando a configuração direta mesmo com considerações de compatibilidade do React 19
  • A integração com Tailwind CSS é perfeita, com suporte integrado a temas e implementação de modo escuro
  • Os componentes são agnósticos ao framework e funcionam tanto com App Router quanto com Pages Router no Next.js

Por que Escolher Shadcn UI para Seu Projeto Next.js

Shadcn UI adota uma abordagem fundamentalmente diferente para bibliotecas de componentes. Em vez de instalar uma dependência que você não pode modificar, Shadcn copia o código-fonte dos componentes diretamente para seu projeto. Isso significa que você obtém:

  • Propriedade completa dos seus componentes - modifique qualquer coisa, a qualquer momento
  • Design Tailwind-first que se integra perfeitamente com seus estilos existentes
  • Zero overhead de runtime - componentes são apenas código em seu projeto
  • Type safety integrada desde o início
  • Acessibilidade tratada corretamente por padrão através dos primitivos Radix UI

Esta abordagem brilha particularmente com o App Router do Next.js 15, onde componentes de servidor e limites de cliente requerem design cuidadoso de componentes.

Pré-requisitos e Configuração Inicial

Antes de iniciar sua configuração Shadcn UI Next.js, certifique-se de ter:

  • Node.js 18.17 ou superior
  • Um gerenciador de pacotes (npm, pnpm, yarn, ou bun)
  • Familiaridade básica com React e Tailwind CSS

Criando Seu Projeto Next.js 15

Comece criando um novo projeto Next.js com TypeScript e Tailwind CSS:

npx create-next-app@latest my-app --typescript --tailwind --app

Quando solicitado, selecione estas opções:

  • ✓ Would you like to use ESLint? Yes
  • ✓ Would you like to use src/ directory? Yes (recomendado)
  • ✓ Would you like to customize the default import alias? No

Navegue para seu projeto:

cd my-app

Instalando e Configurando Shadcn UI

A CLI do Shadcn cuida da maior parte do trabalho pesado para instalação de componentes. Execute o comando de inicialização:

npx shadcn@latest init

Você verá várias solicitações de configuração:

  1. Style: Escolha seu estilo preferido (Default/New York)
  2. Base color: Selecione entre os esquemas de cores disponíveis
  3. CSS variables: Use Yes para melhor suporte a temas

Lidando com Dependências Peer do React 19

Se você estiver usando npm com React 19, encontrará avisos de dependências peer:

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree

A CLI solicitará que você escolha uma estratégia de resolução:

? How would you like to proceed?
 Use --force
  Use --legacy-peer-deps

Selecione --legacy-peer-deps para a abordagem mais segura. Usuários de pnpm, yarn ou bun não verão estes avisos e podem prosseguir normalmente.

Adicionando Seus Primeiros Componentes

Com o Shadcn inicializado, adicione componentes usando a CLI:

npx shadcn@latest add button card

Este comando:

  1. Baixa o código-fonte do componente
  2. Coloca-o em src/components/ui/
  3. Instala quaisquer dependências necessárias (como primitivos Radix UI)
  4. Atualiza suas importações de componentes

Aqui está como um componente button típico do Shadcn fica após a instalação:

// src/components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

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",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button"
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

export { Button, buttonVariants }

Observe como o componente é totalmente personalizável - você pode modificar variantes, adicionar novas ou alterar o estilo completamente.

Configurando Tailwind CSS para Shadcn

Shadcn UI funciona tanto com Tailwind CSS v3 quanto v4. A CLI configura automaticamente seu tailwind.config.js durante a inicialização. Aqui está o que é adicionado:

// tailwind.config.js
module.exports = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}

Atualize Seu globals.css

A CLI também atualiza seu globals.css com variáveis CSS para temas:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 221.2 83.2% 53.3%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96%;
    --secondary-foreground: 222.2 84% 4.9%;
    --muted: 210 40% 96%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96%;
    --accent-foreground: 222.2 84% 4.9%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 221.2 83.2% 53.3%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 217.2 91.2% 59.8%;
    --primary-foreground: 222.2 84% 4.9%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 224.3 76.3% 94.1%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Implementando Modo Escuro com next-themes

Para uma implementação de modo escuro pronta para produção, use next-themes para evitar problemas de hidratação.

Instalar next-themes

npm install next-themes

Criar um Theme Provider

Crie src/components/theme-provider.tsx:

"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

Atualize Seu Layout Raiz

Modifique src/app/layout.tsx:

import { ThemeProvider } from "@/components/theme-provider"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

Criar um Componente Theme Toggle

"use client"

import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { setTheme, theme } = useTheme()

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  )
}

Trabalhando com Mudanças do React 19

React 19 introduz várias mudanças que afetam como você trabalha com componentes Shadcn:

Manuseio Simplificado de ref

React 19 permite passar ref como uma prop regular, eliminando a necessidade de forwardRef em muitos casos:

// Abordagem antiga (React 18)
const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, ...props }, ref) => {
    return <input ref={ref} className={className} {...props} />
  }
)

// Nova abordagem (React 19)
function Input({ className, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} className={className} {...props} />
}

Novos Hooks de Formulário

Os hooks useActionState e useFormStatus do React 19 funcionam perfeitamente com componentes de formulário Shadcn:

import { useActionState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

function ContactForm() {
  const [state, formAction] = useActionState(async (prevState, formData) => {
    // Lógica da server action aqui
    const email = formData.get("email")
    // Processar formulário...
    return { success: true }
  }, { success: false })

  return (
    <form action={formAction}>
      <Input name="email" type="email" required />
      <Button type="submit">Submit</Button>
      {state.success && <p>Obrigado por se inscrever!</p>}
    </form>
  )
}

Problemas Comuns e Soluções

Erros de Build com Tailwind CSS

Se você encontrar erros de build após a configuração, certifique-se de que sua configuração PostCSS está correta:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Estilização de Componente Não Aplicada

Verifique se seu globals.css está importado em seu layout raiz:

import "./globals.css"

Erros TypeScript com Props de Componente

Certifique-se de que seu tsconfig.json inclui os caminhos corretos:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Melhores Práticas para Produção

  1. Personalize o utilitário cn: A função cn em lib/utils.ts mescla classes inteligentemente. Estenda-a para suas necessidades específicas.

  2. Crie variantes de componente: Use CVA (class-variance-authority) para criar variantes consistentes de componentes em sua aplicação.

  3. Otimize o tamanho do bundle: Instale apenas os componentes que você realmente usa. Cada componente é independente.

  4. Teste acessibilidade: Componentes Shadcn usam primitivos Radix UI que lidam com acessibilidade, mas sempre teste com leitores de tela.

  5. Controle de versão: Como os componentes são copiados para seu projeto, faça commit deles no controle de versão e rastreie mudanças.

Conclusão

Configurar Shadcn UI com Next.js oferece um sistema de componentes moderno que equilibra flexibilidade com velocidade de desenvolvimento. Ao contrário das bibliotecas de componentes tradicionais, você possui cada linha de código, pode personalizar qualquer coisa e não fica preso às decisões de design de outra pessoa.

A combinação do App Router do Next.js 15, melhorias do React 19 e Tailwind CSS cria uma base poderosa para construir aplicações web modernas. Com a abordagem do Shadcn de copiar componentes em vez de instalá-los como dependências, você obtém o melhor dos dois mundos: desenvolvimento rápido e controle completo.

FAQs

Ao usar npm com React 19, você precisará usar a flag --legacy-peer-deps durante a instalação de componentes devido a conflitos de dependências peer. Gerenciadores de pacotes como pnpm, yarn e bun lidam com essas dependências de forma mais elegante e não requerem flags especiais. O resultado final é o mesmo independentemente do gerenciador de pacotes que você escolher.

Sim, Shadcn UI funciona tanto com App Router quanto com Pages Router. O código do componente em si é agnóstico ao framework. A principal diferença está em como você implementa providers como o theme provider - no Pages Router, você envolveria seu app em _app.tsx em vez do layout raiz.

Como o Shadcn copia componentes diretamente para seu projeto, você pode modificá-los como qualquer outro código. Abra o arquivo do componente em src/components/ui/, ajuste as classes Tailwind ou variantes CVA e salve. Você também pode modificar as variáveis de tema globais em seu CSS para alterar cores e espaçamento em todos os componentes de uma vez.

Não, você deve instalar apenas os componentes conforme precisar deles. Cada componente é independente e inclui suas próprias dependências. Isso mantém o tamanho do seu bundle mínimo e seu projeto organizado. Use npx shadcn@latest add [nome-do-componente] para adicionar componentes individuais quando necessário.

Como os componentes são copiados para seu projeto, eles não serão atualizados automaticamente quando Shadcn lançar novas versões. Você pode verificar manualmente a documentação do Shadcn UI para atualizações e executar novamente o comando add para componentes específicos ou aplicar mudanças manualmente. Isso lhe dá controle sobre quando e como os componentes são atualizados em seu projeto.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers