Introdução ao Radix UI para componentes acessíveis sem estilo imposto
1. O que é Radix UI e por que ele é revolucionário para temas
Radix UI é uma biblioteca de componentes headless de código aberto, projetada para oferecer funcionalidade e acessibilidade sem impor qualquer estilo visual. Diferente de bibliotecas tradicionais como Material UI ou Chakra UI, que entregam componentes com aparência pré-definida, o Radix fornece apenas a lógica de comportamento e marcação semântica, deixando o design totalmente sob controle do desenvolvedor.
Essa abordagem é revolucionária para sistemas de design porque permite que times criem temas consistentes sem precisar lutar contra estilos embutidos ou sobrescrever regras CSS complexas. Com Radix, você obtém componentes que funcionam corretamente em termos de acessibilidade e interação, enquanto a aparência visual é definida separadamente, seja com Tailwind CSS, styled-components, CSS Modules ou qualquer outra solução.
2. Acessibilidade como pilar central dos componentes Radix
A acessibilidade não é um complemento no Radix — é um requisito fundamental. Cada componente é construído seguindo as especificações WAI-ARIA, garantindo que atributos como role, aria-expanded, aria-controls e aria-hidden sejam gerenciados automaticamente.
O suporte a navegação por teclado é nativo: componentes como Menu, Dialog e Tooltip respondem a teclas como Tab, Enter, Escape e setas direcionais sem necessidade de configuração extra. O gerenciamento de foco também é tratado internamente, evitando que o foco escape para fora de modais ou menus.
Por exemplo, ao abrir um Dialog, o Radix automaticamente move o foco para o primeiro elemento interativo dentro do conteúdo e, ao fechar, retorna o foco ao elemento que acionou a abertura.
3. Primeiros passos: instalação e componentes fundamentais
Para começar, instale o pacote principal do Radix:
npm install @radix-ui/react-dialog
Cada componente do Radix segue uma estrutura de composição com partes específicas. Vamos criar um Dialog (modal) acessível:
import * as Dialog from '@radix-ui/react-dialog';
function ModalAcessivel() {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button className="botao">Abrir modal</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="conteudo">
<Dialog.Title>Título do modal</Dialog.Title>
<Dialog.Description>Descrição para leitores de tela</Dialog.Description>
<p>Conteúdo do modal aqui.</p>
<Dialog.Close asChild>
<button className="fechar">Fechar</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Note que não há estilos aplicados — as classes CSS são definidas por você. O Radix gerencia toda a semântica ARIA, o gerenciamento de foco e a navegação por teclado.
4. Personalização total: como aplicar seu próprio tema sem conflitos
A verdadeira força do Radix está na personalização. Como não há estilos embutidos, você pode aplicar qualquer sistema de design sem conflitos.
Com Tailwind CSS:
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg shadow-lg">
Com styled-components:
const StyledContent = styled(Dialog.Content)`
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
A propriedade asChild permite que você use seus próprios elementos como triggers ou closes, mantendo os comportamentos acessíveis do Radix. Isso é essencial para integrar com botões ou links já estilizados no seu tema.
Para definir tokens de design, crie variáveis CSS ou objetos de tema:
:root {
--color-primary: #3b82f6;
--spacing-md: 1rem;
--font-size-base: 16px;
}
5. Gerenciamento de estado e comportamento complexo
O Radix oferece componentes controlados e não controlados. Para estado simples, use o modo não controlado:
<Dialog.Root defaultOpen={false}>
Para controle externo, utilize props como open e onOpenChange:
const [open, setOpen] = useState(false);
<Dialog.Root open={open} onOpenChange={setOpen}>
Exemplo com Accordion e estado customizado:
import * as Accordion from '@radix-ui/react-accordion';
function AcordeaoPersonalizado() {
return (
<Accordion.Root type="single" collapsible>
<Accordion.Item value="item-1">
<Accordion.Header>
<Accordion.Trigger>Seção 1</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Conteúdo da seção 1</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
}
Callbacks como onValueChange permitem reagir a mudanças de estado, ideal para formulários e validações.
6. Composição avançada: criando componentes híbridos com Radix
Você pode combinar primitivos do Radix para criar componentes complexos. Um exemplo é um seletor de data usando Popover e componentes de calendário:
import * as Popover from '@radix-ui/react-popover';
import { Calendar } from 'react-date-range';
function SeletorData() {
return (
<Popover.Root>
<Popover.Trigger asChild>
<button>Selecionar data</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content>
<Calendar date={new Date()} onChange={() => {}} />
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}
Para integração com formulários, combine Radix com React Hook Form:
<Controller
name="checkbox"
control={control}
render={({ field }) => (
<Checkbox.Root checked={field.value} onCheckedChange={field.onChange}>
<Checkbox.Indicator />
</Checkbox.Root>
)}
/>
7. Performance e boas práticas em projetos reais
O Radix suporta tree shaking nativamente, pois cada componente é um pacote separado. Importe apenas o que usar:
import { Dialog } from '@radix-ui/react-dialog'; // Errado: importa tudo
import * as Dialog from '@radix-ui/react-dialog'; // Correto: importa módulo específico
Para evitar renderizações desnecessárias, use React.memo em componentes que recebem props que mudam com frequência. Em listas grandes de Accordion ou Tabs, considere virtualização com bibliotecas como react-window.
Testes de acessibilidade podem ser automatizados com React Testing Library e axe-core:
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';
test('modal é acessível', async () => {
const { container } = render(<ModalAcessivel />);
const resultados = await axe(container);
expect(resultados).toHaveNoViolations();
});
8. Comparação com alternativas e roadmap futuro
Comparado ao Headless UI (Tailwind Labs), o Radix oferece mais componentes prontos (Dropdown Menu, Context Menu, Toolbar) e melhor suporte a animações. Já o Reach UI, embora focado em acessibilidade, tem desenvolvimento menos ativo.
Limitações atuais do Radix incluem a ausência de componentes como Date Picker completo e Data Table, exigindo composição manual. A curva de aprendizado é moderada devido à arquitetura de composição.
O ecossistema Radix inclui Radix Themes para estilização rápida e Radix Colors para paletas acessíveis. Futuros lançamentos prometem mais componentes e melhor integração com frameworks.
Referências
- Documentação oficial do Radix UI — Guia completo de todos os componentes primitivos, exemplos e API
- WAI-ARIA Authoring Practices Guide — Padrões oficiais de acessibilidade seguidos pelo Radix UI
- Radix UI + Tailwind CSS: Guia prático — Tutorial oficial de integração com Tailwind e temas customizados
- React Testing Library + axe-core — Documentação para testes de acessibilidade em componentes React
- Comparativo: Radix UI vs Headless UI — Análise detalhada das diferenças entre as duas bibliotecas headless
- Radix Colors: paletas acessíveis — Conjunto de cores com contraste garantido para temas inclusivos