Composição
A base para construir componentes de IU modernos.
Composição, ou componibilidade, é a base para construir componentes de IU modernos. É uma das técnicas mais poderosas para criar componentes flexíveis e reutilizáveis que podem lidar com requisitos complexos sem sacrificar a clareza da API.
Em vez de amontoar toda a funcionalidade em um único componente com dezenas de props, a composição distribui responsabilidades entre vários componentes que cooperam.
Fernando fez uma ótima apresentação sobre isso na React Universe Conf 2025, onde compartilhou sua abordagem para reconstruir o Message Composer do Slack como um componente componível.
Tornando um componente componível
Para tornar um componente componível, você precisa dividi-lo em componentes menores e mais focados. Por exemplo, vamos pegar este componente Accordion:
import { Accordion } from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return (
<Accordion data={data} />
);Embora este componente Accordion pareça simples, ele está assumindo responsabilidades demais. Ele é responsável por renderizar o container, o Trigger e o Content; além de gerenciar o estado e os dados do Accordion.
Personalizar o estilo deste componente é difícil porque está fortemente acoplado. Provavelmente exige overrides globais de CSS. Além disso, adicionar nova funcionalidade ou ajustar o comportamento requer modificar o código-fonte do componente.
Para resolver isso, podemos dividir isso em componentes menores e mais focados.
1. Componente Root
Primeiro, vamos focar no container — o componente que mantém tudo junto, ou seja, o trigger e o content. Este container não precisa conhecer os dados, mas precisa acompanhar o estado open.
No entanto, também queremos que esse estado seja acessível pelos componentes filhos. Então, vamos usar a Context API para criar um contexto para o estado open.
Por fim, para permitir a modificação do elemento div, nós estenderemos os atributos HTML padrão.
Chamaremos este componente de "Root".
type AccordionProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
const AccordionContext = createContext<AccordionProps>({
open: false,
setOpen: () => {},
});
export type AccordionRootProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
export const Root = ({
children,
open,
setOpen,
...props
}: AccordionRootProps) => (
<AccordionContext.Provider value={{ open, setOpen }}>
<div {...props}>{children}</div>
</AccordionContext.Provider>
);2. Componente Item
O componente Item é o elemento que contém o item do Accordion. É simplesmente um wrapper para cada item no Accordion.
export type AccordionItemProps = React.ComponentProps<'div'>;
export const Item = (props: AccordionItemProps) => <div {...props} />;3. Componente Trigger
O componente Trigger é o elemento que abre o accordion quando ativado. Ele é responsável por:
- Renderizar como um botão por padrão (pode ser personalizado com
asChild) - Lidar com eventos de clique para abrir o Accordion
- Gerenciar foco quando o Accordion fecha
- Fornecer atributos ARIA adequados
Vamos adicionar este componente ao nosso componente Accordion.
export type AccordionTriggerProps = React.ComponentProps<'button'> & {
asChild?: boolean;
};
export const Trigger = ({ asChild, ...props }: AccordionTriggerProps) => (
<AccordionContext.Consumer>
{({ open, setOpen }) => (
<button onClick={() => setOpen(!open)} {...props} />
)}
</AccordionContext.Consumer>
);4. Componente Content
O componente Content é o elemento que contém o conteúdo do accordion. Ele é responsável por:
- Renderizar o conteúdo quando o Accordion está aberto
- Fornecer atributos ARIA adequados
Vamos adicionar este componente ao nosso componente Accordion.
export type AccordionContentProps = React.ComponentProps<'div'> & {
asChild?: boolean;
};
export const Content = ({ asChild, ...props }: AccordionContentProps) => (
<AccordionContext.Consumer>
{({ open }) => <div {...props} />}
</AccordionContext.Consumer>
);5. Juntando tudo
Agora que temos todos os componentes, podemos juntá-los no nosso arquivo original.
import * as Accordion from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return (
<Accordion.Root open={false} setOpen={() => {}}>
{data.map((item) => (
<Accordion.Item key={item.title}>
<Accordion.Trigger>{item.title}</Accordion.Trigger>
<Accordion.Content>{item.content}</Accordion.Content>
</Accordion.Item>
))}
</Accordion.Root>
);Convenções de nomeação
Ao construir componentes componíveis, convenções de nomeação consistentes são cruciais para criar APIs intuitivas e previsíveis. Tanto o shadcn/ui quanto o Radix UI seguem padrões estabelecidos que se tornaram o padrão de fato no ecossistema React.
Componentes Root
O componente Root serve como o contêiner principal que envolve todos os outros subcomponentes. Ele normalmente gerencia o estado compartilhado e o contexto, fornecendo um contexto para todos os componentes filhos.
<AccordionRoot>{/* Child components */}</AccordionRoot>Elementos interativos
Componentes interativos que disparam ações ou alternam estados usam nomes descritivos:
Trigger- O elemento que inicia uma ação (abrir, fechar, alternar)Content- O elemento que contém o conteúdo principal que é mostrado/ocultado
<CollapsibleTrigger>Click to expand</CollapsibleTrigger>
<CollapsibleContent>
Hidden content revealed here
</CollapsibleContent>Estrutura de conteúdo
Para componentes com áreas de conteúdo estruturadas, use nomes semânticos que descrevam seu propósito:
Header- Seção superior contendo títulos ou controlesBody- Área de conteúdo principalFooter- Seção inferior para ações ou metadados
<DialogHeader>
{/* Dialog title */}
</DialogHeader>
<DialogBody>
{/* Dialog content */}
</DialogBody>
<DialogFooter>
{/* Dialog footer */}
</DialogFooter>Componentes informativos
Componentes que fornecem informação ou contexto usam sufixos descritivos:
Title- Cabeçalho primário ou rótuloDescription- Texto de apoio ou conteúdo explicativo
<CardTitle>Project Statistics</CardTitle>
<CardDescription>
View your project's performance over time
</CardDescription>