Configuração completa do Tailwind CSS e shadcn/ui para criar componentes modernos, acessíveis e reutilizáveis.
O Tailwind CSS oferece utilitários de baixo nível para construir designs personalizados, enquanto o shadcn/ui fornece componentes pré-construídos e acessíveis.
# Inicializar shadcn/ui npx shadcn@latest init # Responder as perguntas: # ✔ Which color would you like to use as base color? › Slate
# Instalar componentes básicos bunx --bun shadcn@latest add button bunx --bun shadcn@latest add card bunx --bun shadcn@latest add input bunx --bun shadcn@latest add label bunx --bun shadcn@latest add badge bunx --bun shadcn@latest add avatar bunx --bun shadcn@latest add dropdown-menu bunx --bun shadcn@latest add dialog bunx --bun shadcn@latest add sonner bunx --bun shadcn@latest add table bunx --bun shadcn@latest add form bunx --bun shadcn@latest add select
@import "tailwindcss"; @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar: var(--sidebar); --color-chart-5: var(--chart-5); --color-chart-4: var(--chart-4); --color-chart-3: var(--chart-3); --color-chart-2: var(--chart-2); --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); --color-secondary-foreground: var(--secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); } :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.129 0.042 264.695); --card: oklch(1 0 0); --card-foreground: oklch(0.129 0.042 264.695); --popover: oklch(1 0 0); --popover-foreground: oklch(0.129 0.042 264.695); --primary: oklch(0.208 0.042 265.755); --primary-foreground: oklch(0.984 0.003 247.858); --secondary: oklch(0.968 0.007 247.896); --secondary-foreground: oklch(0.208 0.042 265.755); --muted: oklch(0.968 0.007 247.896); --muted-foreground: oklch(0.554 0.046 257.417); --accent: oklch(0.968 0.007 247.896); --accent-foreground: oklch(0.208 0.042 265.755); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.929 0.013 255.508); --input: oklch(0.929 0.013 255.508); --ring: oklch(0.704 0.04 256.788); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.984 0.003 247.858); --sidebar-foreground: oklch(0.129 0.042 264.695); --sidebar-primary: oklch(0.208 0.042 265.755); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.968 0.007 247.896); --sidebar-accent-foreground: oklch(0.208 0.042 265.755); --sidebar-border: oklch(0.929 0.013 255.508); --sidebar-ring: oklch(0.704 0.04 256.788); } .dark { --background: oklch(0.129 0.042 264.695); --foreground: oklch(0.984 0.003 247.858); --card: oklch(0.208 0.042 265.755); --card-foreground: oklch(0.984 0.003 247.858); --popover: oklch(0.208 0.042 265.755); --popover-foreground: oklch(0.984 0.003 247.858); --primary: oklch(0.929 0.013 255.508); --primary-foreground: oklch(0.208 0.042 265.755); --secondary: oklch(0.279 0.041 260.031); --secondary-foreground: oklch(0.984 0.003 247.858); --muted: oklch(0.279 0.041 260.031); --muted-foreground: oklch(0.704 0.04 256.788); --accent: oklch(0.279 0.041 260.031); --accent-foreground: oklch(0.984 0.003 247.858); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.551 0.027 264.364); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.208 0.042 265.755); --sidebar-foreground: oklch(0.984 0.003 247.858); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.279 0.041 260.031); --sidebar-accent-foreground: oklch(0.984 0.003 247.858); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.551 0.027 264.364); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } }
{ "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.ts", "css": "src/app/globals.css", "baseColor": "slate", "cssVariables": true, "prefix": "" }, "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" }, "iconLibrary": "lucide" }
import { cn } from "@/lib/utils" interface LoadingProps { className?: string size?: "sm" | "md" | "lg" } export function Loading({ className, size = "md" }: LoadingProps) { const sizeClasses = { sm: "h-4 w-4", md: "h-6 w-6", lg: "h-8 w-8" } return ( <div className={cn( "animate-spin rounded-full border-2 border-gray-300 border-t-blue-600", sizeClasses[size], className )} /> ) } export function LoadingPage() { return ( <div className="flex items-center justify-center min-h-screen"> <div className="text-center"> <Loading size="lg" className="mx-auto mb-4" /> <p className="text-gray-600">Carregando...</p> </div> </div> ) }
import { LucideIcon } from "lucide-react" import { Button } from "./button" interface EmptyStateProps { icon: LucideIcon title: string description: string action?: { label: string onClick: () => void } } export function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) { return ( <div className="flex flex-col items-center justify-center py-12 text-center"> <div className="rounded-full bg-gray-100 p-6 mb-4"> <Icon className="h-12 w-12 text-gray-400" /> </div> <h3 className="text-lg font-semibold text-gray-900 mb-2"> {title} </h3> <p className="text-gray-600 mb-6 max-w-sm"> {description} </p> {action && ( <Button onClick={action.onClick}> {action.label} </Button> )} </div> ) }
import Link from "next/link" import { Button } from "@/components/ui/button" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Bell, Settings, LogOut } from "lucide-react" export function Header() { return ( <header className="border-b bg-white"> <div className="container mx-auto px-4 py-4"> <div className="flex items-center justify-between"> <Link href="/" className="text-2xl font-bold text-gray-900"> Restaurantix </Link> <div className="flex items-center space-x-4"> <Button variant="ghost" size="icon"> <Bell className="h-5 w-5" /> </Button> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="ghost" className="relative h-8 w-8 rounded-full"> <Avatar className="h-8 w-8"> <AvatarImage src="/avatar.png" alt="Avatar" /> <AvatarFallback>GM</AvatarFallback> </Avatar> </Button> </DropdownMenuTrigger> <DropdownMenuContent className="w-56" align="end" forceMount> <DropdownMenuLabel className="font-normal"> <div className="flex flex-col space-y-1"> <p className="text-sm font-medium leading-none"> Gustavo Miranda </p> <p className="text-xs leading-none text-muted-foreground"> gustavo@restaurantix.com </p> </div> </DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem> <Settings className="mr-2 h-4 w-4" /> <span>Configurações</span> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <LogOut className="mr-2 h-4 w-4" /> <span>Sair</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div> </div> </div> </header> ) }
import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { Toaster } from "@/components/ui/sonner"; import { Header } from "@/components/layout/header"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Restaurantix - Dashboard", description: "Sistema de gestão para restaurantes" export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="pt-BR" suppressHydrationWarning> <body className={inter.className}> <div className="min-h-screen bg-background"> <Header /> {children} </div> <Toaster /> </body> </html> ); }
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { CheckCircle, Palette, Code, Layers } from "lucide-react"; export default function HomePage() { return ( <div> <main className="container mx-auto px-4 py-8"> <div className="text-center mb-12"> <Badge className="mb-4">Projeto Configurado</Badge> <h1 className="text-4xl font-bold mb-4">Restaurantix Dashboard</h1> <p className="text-xl text-muted-foreground mb-8"> Next.js 15 + Tailwind CSS + shadcn/ui </p> </div> <div className="grid md:grid-cols-3 gap-6 mb-8"> <Card> <CardHeader> <CardTitle className="flex items-center"> <Code className="h-5 w-5 mr-2 text-blue-600" /> Next.js 15 </CardTitle> </CardHeader> <CardContent> <p className="text-muted-foreground"> App Router configurado com TypeScript e otimizações automáticas. </p> </CardContent> </Card> <Card> <CardHeader> <CardTitle className="flex items-center"> <Palette className="h-5 w-5 mr-2 text-cyan-600" /> Tailwind CSS </CardTitle> </CardHeader> <CardContent> <p className="text-muted-foreground"> Utility-first CSS framework com design system customizável. </p> </CardContent> </Card> <Card> <CardHeader> <CardTitle className="flex items-center"> <Layers className="h-5 w-5 mr-2 text-purple-600" /> shadcn/ui </CardTitle> </CardHeader> <CardContent> <p className="text-muted-foreground"> Componentes acessíveis e customizáveis baseados em Radix UI. </p> </CardContent> </Card> </div> <Card className="max-w-2xl mx-auto"> <CardHeader> <CardTitle className="flex items-center"> <CheckCircle className="h-5 w-5 mr-2 text-green-600" /> Tudo Funcionando! </CardTitle> </CardHeader> <CardContent className="space-y-4"> <p className="text-muted-foreground"> Seu ambiente de desenvolvimento está pronto para construir o frontend do Restaurantix. </p> <div className="flex gap-4"> <Button>Começar Dashboard</Button> <Button variant="outline">Ver Documentação</Button> </div> </CardContent> </Card> </main> </div> ); }
# Adicionar novos componentes shadcn/ui bunx --bun shadcn@latest add [component-name] # Ver todos os componentes disponíveis bunx --bun shadcn@latest add # Atualizar componentes existentes bunx --bun shadcn@latest update # Verificar configuração bunx --bun shadcn@latest diff
Pratique criando componentes com shadcn/ui:
Use os componentes para criar uma página de login:
Adicione um toggle para alternar entre temas:
Implemente toast notifications: