Engenharia de UI Front-end
Visão Geral
Construa interfaces de usuário de qualidade de produção que sejam acessíveis, com bom desempenho e visualmente polidas. O objetivo é uma UI que pareça ter sido construída por um engenheiro com noção de design em uma empresa de ponta — e não gerada por uma IA. Isso significa aderência real a sistemas de design, acessibilidade adequada, padrões de interação bem pensados e nenhum "estética AI" genérica.
Quando Usar
- Construindo novos componentes ou páginas de UI
- Modificando interfaces de usuário existentes
- Implementando layouts responsivos
- Adicionando interatividade ou gerenciamento de estado
- Corrigindo problemas visuais ou de UX
Arquitetura de Componentes
Estrutura de Arquivos
Coloque tudo relacionado a um componente junto:
src/components/
TaskList/
TaskList.tsx # Implementação do componente
TaskList.test.tsx # Testes
TaskList.stories.tsx # Histórias do Storybook (se estiver usando)
use-task-list.ts # Hook personalizado (se o estado for complexo)
types.ts # Tipos específicos do componente (se necessário)
Padrões de Componentes
Prefira composição em vez de configuração:
// Bom: Composável
<Card>
<CardHeader>
<CardTitle>Tarefas</CardTitle>
</CardHeader>
<CardBody>
<TaskList tasks={tasks} />
</CardBody>
</Card>
// Evite: Superconfigurado
<Card
title="Tarefas"
headerVariant="large"
bodyPadding="md"
content={<TaskList tasks={tasks} />}
/>
Mantenha os componentes focados:
// Bom: Faz uma coisa
function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {
return (
<li className="flex items-center gap-3 p-3">
<Checkbox checked={task.done} onChange={() => onToggle(task.id)} />
<span className={task.done ? 'line-through text-muted' : ''}>{task.title}</span>
<Button variant="ghost" size="sm" onClick={() => onDelete(task.id)}>
<TrashIcon />
</Button>
</li>
);
}
Separe a busca de dados da apresentação:
// Container: lida com dados
export function TaskListContainer() {
const { tasks, isLoading, error } = useTasks();
if (isLoading) return <TaskListSkeleton />;
if (error) return <ErrorState message="Falha ao carregar tarefas" retry={refetch} />;
if (tasks.length === 0) return <EmptyState message="Nenhuma tarefa ainda" />;
return <TaskList tasks={tasks} />;
}
// Apresentação: lida com renderização
export function TaskList({ tasks }: { tasks: Task[] }) {
return (
<ul role="list" className="divide-y">
{tasks.map(task => <TaskItem key={task.id} task={task} />)}
</ul>
);
}
Gerenciamento de Estado
Escolha a abordagem mais simples que funciona:
Estado local (useState) → Estado de UI específico do componente
Estado elevado → Compartilhado entre 2-3 componentes irmãos
Context → Tema, autenticação, localidade (leitura pesada, escrita rara)
Estado de URL (searchParams) → Filtros, paginação, estado de UI compartilhável
Estado de servidor (React Query, SWR) → Dados remotos com cache
Armazenamento global (Zustand, Redux) → Estado de cliente complexo compartilhado em todo o app
Evite prop drilling com mais de 3 níveis. Se você estiver passando props por componentes que não as usam, introduza um context ou reestruture a árvore de componentes.
Aderência ao Sistema de Design
Qualidade de UI Baseada em Referências
Quando o produto precisar de uma direção visual distinta, colete evidências antes de escolher um layout:
- Pesquise em um catálogo de referências confiável ou use referências fornecidas pela equipe do produto.
- Estude duas ou três telas relevantes. Registre decisões sobre hierarquia, densidade, navegação, controles, comportamento responsivo e estados de interação.
- Transforme essas decisões em um breve contrato de design antes da implementação. Nomeie a função da tela, ação principal, estados necessários, regras responsivas e padrões a rejeitar.
- Reconstrua a estrutura útil usando os próprios componentes, tokens, conteúdo e linguagem visual do produto. Nunca copie a marca, texto proprietário, imagens ou layout exato de outro produto.
Use referências como evidência, não como modelos. Se as referências não estiverem disponíveis, documente as suposições e verifique o resultado em relação ao sistema de design existente do produto.
Evite a Estética AI
UI gerada por IA tem padrões reconhecíveis. Evite todos eles:
| Padrão AI | Por que é um Problema | Qualidade de Produção |
|---|---|---|
| Tudo roxo/índigo | Modelos padrão usam paletas visualmente "seguras", fazendo todo app parecer idêntico | Use a paleta de cores real do projeto |
| Gradientes excessivos | Gradientes adicionam ruído visual e conflitam com a maioria dos sistemas de design | Gradientes planos ou sutis que correspondem ao sistema de design |
| Tudo arredondado (rounded-2xl) | Arredondamento máximo sinaliza "amigável" mas ignora a hierarquia de raios de borda em designs reais | border-radius consistente do sistema de design |
| Seções hero genéricas | Layout baseado em modelo sem conexão com o conteúdo real ou necessidade do usuário | Layouts centrados no conteúdo |
| Texto lorem ipsum genérico | Texto placeholder esconde problemas de layout que o conteúdo real revela (comprimento, quebra de linha, estouro) | Conteúdo placeholder realista |
| Padding enorme em todos os lugares | Espaçamento generoso igual destrói a hierarquia visual e desperdiça espaço na tela | Escala de espaçamento consistente |
| Grades de cards genéricas | Grades uniformes são um atalho de layout que ignora a prioridade da informação e padrões de leitura | Layouts orientados por propósito |
| Design pesado em sombras | Sombras em camadas adicionam profundidade que compete com o conteúdo e diminui a renderização em dispositivos de baixo custo | Sombras sutis ou nenhuma, a menos que o sistema de design especifique |
Espaçamento e Layout
Use uma escala de espaçamento consistente. Não invente valores:
/* Use a escala: incrementos de 0.25rem (ou o que o projeto usar) */
/* Bom */ padding: 1rem; /* 16px */
/* Bom */ gap: 0.75rem; /* 12px */
/* Ruim */ padding: 13px; /* Não está em nenhuma escala */
/* Ruim */ margin-top: 2.3rem; /* Não está em nenhuma escala */
Tipografia
Respeite a hierarquia tipográfica:
h1 → Título da página (um por página)
h2 → Título da seção
h3 → Título da subseção
body → Texto padrão
small → Texto secundário/auxiliar
Não pule níveis de heading. Não use estilos de heading para conteúdo que não é heading.
Cor
- Use tokens de cores semânticas:
text-primary,bg-surface,border-default— não valores hexadecimais brutos - Garanta contraste suficiente (4.5:1 para texto normal, 3:1 para texto grande)
- Não dependa apenas da cor para transmitir informações (use também ícones, texto ou padrões)
Acessibilidade (WCAG 2.1 AA)
Todo componente deve atender a estes padrões:
Navegação por Teclado
// Todo elemento interativo deve ser acessível por teclado
<button onClick={handleClick}>Clique aqui</button> // ✓ Focável por padrão
<div onClick={handleClick}>Clique aqui</div> // ✗ Não focável
<div role="button" tabIndex={0} onClick={handleClick} // ✓ Mas prefira <button>
onKeyDown={e => {
if (e.key === 'Enter') handleClick();
if (e.key === ' ') e.preventDefault();
}}
onKeyUp={e => {
if (e.key === ' ') handleClick();
}}>
Clique aqui
</div>
Rótulos ARIA
// Rotule elementos interativos que não têm texto visível
<button aria-label="Fechar diálogo"><XIcon /></button>
// Rotule inputs de formulário
<label htmlFor="email">Email</label>
<input id="email" type="email" />
// Ou use aria-label quando não houver rótulo visível
<input aria-label="Pesquisar tarefas" type="search" />
Gerenciamento de Foco
// Mova o foco quando o conteúdo mudar
function Dialog({ isOpen, onClose }: DialogProps) {
const closeRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen) closeRef.current?.focus();
}, [isOpen]);
// Prenda o foco dentro do diálogo quando aberto
return (
<dialog open={isOpen}>
<button ref={closeRef} onClick={onClose}>Fechar</button>
{/* conteúdo do diálogo */}
</dialog>
);
}
Estados de Vazio e Erro Significativos
// Não mostre telas em branco
function TaskList({ tasks }: { tasks: Task[] }) {
if (tasks.length === 0) {
return (
<div role="status" className="text-center py-12">
<TasksEmptyIcon className="mx-auto h-12 w-12 text-muted" />
<h3 className="mt-2 text-sm font-medium">Nenhuma tarefa</h3>
<p className="mt-1 text-sm text-muted">Comece criando uma nova tarefa.</p>
<Button className="mt-4" onClick={onCreateTask}>Criar Tarefa</Button>
</div>
);
}
return <ul role="list">...</ul>;
}
Design Responsivo
Projete primeiro para mobile, depois expanda:
// Tailwind: responsivo mobile-first
<div className="
grid grid-cols-1 /* Mobile: coluna única */
sm:grid-cols-2 /* Pequeno: 2 colunas */
lg:grid-cols-3 /* Grande: 3 colunas */
gap-4
">
Teste nestes breakpoints: 320px, 768px, 1024px, 1440px.
Carregamento e Transições
// Esqueleto de carregamento (não spinners para conteúdo)
function TaskListSkeleton() {
return (
<div className="space-y-3" aria-busy="true" aria-label="Carregando tarefas">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-12 bg-muted animate-pulse rounded" />
))}
</div>
);
}
// Atualizações otimistas para velocidade percebida
function useToggleTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: toggleTask,
onMutate: async (taskId) => {
await queryClient.cancelQueries({ queryKey: ['tasks'] });
const previous = queryClient.getQueryData(['tasks']);
queryClient.setQueryData(['tasks'], (old: Task[]) =>
old.map(t => t.id === taskId ? { ...t, done: !t.done } : t)
);
return { previous };
},
onError: (_err, _taskId, context) => {
queryClient.setQueryData(['tasks'], context?.previous);
},
});
}
Veja Também
Para requisitos detalhados de acessibilidade e ferramentas de teste, veja ../../references/accessibility-checklist.md.
Racionalizações Comuns
| Racionalização | Realidade |
|---|---|
| "Acessibilidade é algo bom de ter" | É um requisito legal em muitas jurisdições e um padrão de qualidade de engenharia. |
| "Vamos deixar responsivo depois" | Adaptar o design responsivo depois é 3x mais difícil do que construí-lo desde o início. |
| "O design não está finalizado, então vou pular o estilo" | Use os padrões do sistema de design. UI sem estilo cria uma primeira impressão ruim para os revisores. |
| "Isso é só um protótipo" | Protótipos viram código de produção. Construa a base corretamente. |
| "A estética AI está boa por enquanto" | Ela sinaliza baixa qualidade. Use o sistema de design real do projeto desde o início. |
Bandeiras Vermelhas
- Componentes com mais de 200 linhas (divida-os)
- Estilos inline ou valores de pixel arbitrários
- Estados de erro, carregamento ou vazio ausentes
- Nenhum teste de navegação por teclado
- Cor como único indicador de estado (vermelho/verde sem texto ou ícones)
- Visual "AI" genérico (gradientes roxos, cards superdimensionados, layouts padrão)
Verificação
Depois de construir a UI:
- O componente renderiza sem erros no console
- Todos os elementos interativos são acessíveis por teclado (Tab pela página)
- O leitor de tela consegue transmitir o conteúdo e a estrutura da página
- Responsivo: funciona em 320px, 768px, 1024px, 1440px
- Estados de carregamento, vazio, erro, sucesso e permissão tratados quando aplicável
- Segue o sistema de design do projeto (espaçamento, cores, tipografia)
- O resultado renderizado passa por uma revisão final de qualidade específica de UI
- Nenhum aviso de acessibilidade nas ferramentas de desenvolvimento ou axe-core