Modais e Diálogos
Regras
- Use Radix Dialog ou shadcn
<Dialog> — acessível, com foco automático e bloqueio de rolagem por padrão
- Diálogos de confirmação para ações destrutivas: "Excluir este projeto?" com botões Cancelar (secundário) e Excluir (destrutivo)
- Folhas/gavetas para dispositivos móveis:
<Sheet> do shadcn — desliza de baixo ou lateralmente, com melhor alvo de toque do que modais centralizados
- Foco automático: Radix gerencia automaticamente — o primeiro elemento focalizável recebe o foco ao abrir, retorna ao fechar
- Bloqueio de rolagem: impede a rolagem do corpo quando o modal está aberto — Radix gerencia isso com
<Dialog.Overlay>
- Gatilhos de fechamento: tecla Escape, clique fora do overlay, botão de fechar explícito — todos fornecidos pelo Radix
- Estado controlado: props
open + onOpenChange — não dependa do estado interno para modais acionados por eventos externos
- Formulários em diálogos: submeter com Enter, fechar ao sucesso, mostrar erros inline — não feche em caso de falha de validação
- Dispositivos móveis: use
<Sheet> (gaveta inferior) em vez de <Dialog> centralizado para ações em telas pequenas
Padrões
import {
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader,
AlertDialogTitle, AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
function DeleteButton({ onDelete }: { onDelete: () => Promise<void> }) {
const [loading, setLoading] = useState(false);
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Excluir projeto</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Excluir este projeto?</AlertDialogTitle>
<AlertDialogDescription>
Isso excluirá permanentemente o projeto e todos os seus dados. Esta ação não pode ser desfeita.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={async () => { setLoading(true); await onDelete(); setLoading(false); }}
disabled={loading}
>
{loading ? "Excluindo..." : "Excluir"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
function MobileMenu() {
return (
<Sheet>
<SheetTrigger asChild><Button variant="ghost" size="icon"><MenuIcon /></Button></SheetTrigger>
<SheetContent side="bottom">
<SheetHeader><SheetTitle>Ações</SheetTitle></SheetHeader>
<nav className="flex flex-col gap-2 py-4">
<Button variant="ghost">Editar</Button>
<Button variant="ghost">Compartilhar</Button>
<Button variant="destructive">Excluir</Button>
</nav>
</SheetContent>
</Sheet>
);
}
Evitar
- Construir modais personalizados com
position: fixed e gerenciamento manual de foco — use Radix/shadcn Dialog
- Faltar com a tecla Escape e fechamento ao clicar fora — Radix lida com ambos, não os sobrescreva
- Modais centralizados em dispositivos móveis para menus de ação — use Sheet/Drawer inferior para melhor alcance do polegar
- Fechar o diálogo ao falhar na validação do formulário — mantenha aberto, mostre erros inline, deixe o usuário corrigir e reenviar
- Diálogos aninhados sem contexto de empilhamento adequado — se necessário, use o suporte a diálogos aninhados do Radix com
z-index apropriado