Criar interfaces modernas e responsivas é essencial no desenvolvimento front-end. Combinar shadcn/ui, Vite e React-TS oferece uma stack poderosa e produtiva para construir telas como login e registro de forma rápida, modular e com excelente suporte a TypeScript.
Neste guia, você aprenderá passo a passo como implementar uma tela de login e registro utilizando os componentes do shadcn/ui, integrados em um projeto Vite + React-TS, com uma estrutura organizada e reutilizável.
Estrutura do Projeto
Antes de começarmos com o código, vamos organizar o projeto com uma estrutura clara e escalável:
auth-app/
├── public/
├── src/
│ ├── components/ # Componentes reutilizáveis
│ │ ├── ui/ # Componentes estilizados (baseados no shadcn/ui)
│ │ ├── LoginForm.tsx
│ │ ├── RegisterForm.tsx
│ │ └── AuthContainer.tsx
│ ├── pages/ # Telas da aplicação
│ │ ├── Login.tsx
│ │ └── Register.tsx
│ ├── router.tsx # Configuração das rotas
│ ├── App.tsx
│ └── main.tsx
├── package.json
└── tailwind.config.js
Essa estrutura facilita a manutenção e escala futura do projeto, além de seguir boas práticas de organização de pastas no React.
Configuração Inicial do Projeto
Comece criando um novo projeto com Vite usando o template React-TS:
npm create vite@latest auth-app --template react-ts
cd auth-app
npm install
Instale as dependências necessárias para Tailwind CSS e Radix UI (base do shadcn/ui):
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Atualize o arquivo tailwind.config.js
para incluir as configurações de diretórios:
module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
Agora instale os pacotes base utilizados pelo shadcn/ui:
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label @radix-ui/react-slot
Com isso, seu projeto está pronto para usar os componentes base do shadcn/ui.
Criando Componentes Reutilizáveis
Para não repetir código e facilitar a manutenção, vamos criar componentes na pasta src/components
.
Button.tsx
// src/components/ui/Button.tsx
import * as React from "react"
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 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
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 hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary"
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button }
Input.tsx
// src/components/ui/Input.tsx
import { cn } from "../lib/utils"
export const Input = React.forwardRef<
HTMLInputElement,
React.InputHTMLAttributes<HTMLInputElement>
>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
})
Input.displayName = "Input"
Label.tsx
// src/components/ui/Label.tsx
import { cn } from "../lib/utils"
export const Label = React.forwardRef<
HTMLLabelElement,
React.LabelHTMLAttributes<HTMLLabelElement>
>(({ className, ...props }, ref) => {
return (
<label
className={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
ref={ref}
{...props}
/>
)
})
Label.displayName = "Label"
Criando Formulários de Login e Registro
LoginForm.tsx
// src/components/LoginForm.tsx
import { Button } from "@/components/ui/Button"
import { Input } from "@/components/ui/Input"
import { Label } from "@/components/ui/Label"
export default function LoginForm() {
return (
<form className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">E-mail</Label>
<Input id="email" type="email" placeholder="[email protected]" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha</Label>
<Input id="password" type="password" />
</div>
<Button className="w-full mt-4">Entrar</Button>
</form>
)
}
RegisterForm.tsx
// src/components/RegisterForm.tsx
import { Button } from "@/components/ui/Button"
import { Input } from "@/components/ui/Input"
import { Label } from "@/components/ui/Label"
export default function RegisterForm() {
return (
<form className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Nome</Label>
<Input id="name" type="text" placeholder="Seu nome" />
</div>
<div className="space-y-2">
<Label htmlFor="email">E-mail</Label>
<Input id="email" type="email" placeholder="[email protected]" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha</Label>
<Input id="password" type="password" />
</div>
<Button className="w-full mt-4">Registrar</Button>
</form>
)
}
Container de Autenticação
Crie um componente reutilizável que centraliza o formulário na tela:
// src/components/AuthContainer.tsx
import { Card } from "@/components/ui/Card"
export default function AuthContainer({
children,
title
}: {
children: React.ReactNode
title: string
}) {
return (
<div className="flex items-center justify-center min-h-screen bg-muted">
<Card className="w-full max-w-md p-6 space-y-4">
<h2 className="text-2xl font-bold text-center">{title}</h2>
{children}
</Card>
</div>
)
}
Integrando com Rotas
Instale o react-router-dom para alternar entre login e registro:
npm install react-router-dom
Crie o arquivo src/router.tsx
:
// src/router.tsx
import { createBrowserRouter } from "react-router-dom"
import Login from "./pages/Login"
import Register from "./pages/Register"
const router = createBrowserRouter([
{
path: "/login",
element: <Login />
},
{
path: "/register",
element: <Register />
},
{
path: "*",
element: <Login />
}
])
export default router
Atualize o main.tsx
:
import ReactDOM from "react-dom/client"
import { RouterProvider } from "react-router-dom"
import router from "./router"
ReactDOM.createRoot(document.getElementById("root")!).render(
<RouterProvider router={router} />
)
Crie os arquivos Login.tsx
e Register.tsx
dentro de src/pages/
:
// src/pages/Login.tsx
import AuthContainer from "../components/AuthContainer"
import LoginForm from "../components/LoginForm"
export default function Login() {
return (
<AuthContainer title="Entrar">
<LoginForm />
</AuthContainer>
)
}
// src/pages/Register.tsx
import AuthContainer from "../components/AuthContainer"
import RegisterForm from "../components/RegisterForm"
export default function Register() {
return (
<AuthContainer title="Criar Conta">
<RegisterForm />
</AuthContainer>
)
}
Estilização e Temas
O shadcn/ui permite personalizar totalmente os componentes. Para adicionar temas claros e escuros, você pode criar um contexto global com base no sistema do usuário ou preferência salva. Isso ajuda a manter consistência visual e melhora a experiência do usuário.
Além disso, aproveite o sistema de tokens do Tailwind CSS para garantir uniformidade nos estilos ao longo da aplicação.
Considerações Finais
Ao utilizar shadcn/ui com Vite e React-TS, você tem à disposição uma estrutura leve, altamente customizável e ideal para projetos modernos. A tela de login e registro apresentada neste artigo pode ser facilmente expandida com validações, integração com backends e navegação condicional.
Essa abordagem também facilita a manutenção e escalabilidade, já que cada parte do código é modular e reutilizável — características centrais do design system do shadcn/ui.