Como Criar uma Tela de Login e Registro com Shadcn-ui, Vite e React-TS

Posted by

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.

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *