Accordion
Molecule
Accordion reduz densidade visual sem esconder decisões críticas.
Use Accordion para conteúdo complementar, grupos de configuração, perguntas frequentes, evidências secundárias ou detalhes que não precisam estar todos abertos ao mesmo tempo. Conteúdo crítico deve aparecer fora do accordion ou ser sinalizado antes da dobra.
Exemplos copiáveis
React
React
import * as React from "react"
type AccordionItem = {
id: string
title: string
description?: string
content: React.ReactNode
}
type PulsoAccordionProps = {
items: AccordionItem[]
defaultOpenId?: string
allowMultiple?: boolean
}
export function PulsoAccordion({
items,
defaultOpenId,
allowMultiple = false,
}: PulsoAccordionProps) {
const [openIds, setOpenIds] = React.useState<string[]>(
defaultOpenId ? [defaultOpenId] : [],
)
function toggleItem(id: string) {
setOpenIds((current) => {
const isOpen = current.includes(id)
if (allowMultiple) {
return isOpen ? current.filter((itemId) => itemId !== id) : [...current, id]
}
return isOpen ? [] : [id]
})
}
return (
<div className="divide-y divide-[var(--border)] rounded-[var(--radius-lg)] border border-[var(--border)] bg-[var(--card)]">
{items.map((item) => {
const isOpen = openIds.includes(item.id)
const triggerId = `${item.id}-trigger`
const panelId = `${item.id}-panel`
return (
<section key={item.id}>
<h3>
<button
id={triggerId}
type="button"
aria-expanded={isOpen}
aria-controls={panelId}
onClick={() => toggleItem(item.id)}
className="flex w-full items-start justify-between gap-4 px-4 py-3 text-left transition-colors duration-[var(--motion-duration-fast)] ease-[var(--motion-easing)] hover:bg-[var(--muted)] focus-visible:outline focus-visible:outline-[var(--ring-width)] focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--ring)]"
>
<span>
<span className="block text-sm font-semibold text-[var(--fg)]">
{item.title}
</span>
{item.description ? (
<span className="mt-1 block text-xs leading-5 text-[var(--fg-muted)]">
{item.description}
</span>
) : null}
</span>
<span aria-hidden="true" className="font-mono text-[var(--fg-muted)]">
{isOpen ? "−" : "+"}
</span>
</button>
</h3>
{isOpen ? (
<div
id={panelId}
role="region"
aria-labelledby={triggerId}
className="border-t border-[var(--border)] px-4 py-4 text-sm leading-6 text-[var(--fg-muted)]"
>
{item.content}
</div>
) : null}
</section>
)
})}
</div>
)
}Exemplo de uso
<PulsoAccordion
defaultOpenId="sources"
items={[
{
id: "sources",
title: "Fontes auditáveis",
description: "Relatórios e evidências usados na análise.",
content: <p>Clipping reputacional, relatório interno e menções sociais.</p>,
},
{
id: "cris",
title: "Sugestão da Cris",
description: "Conteúdo assistido exige revisão humana.",
content: <p>Recomendação gerada com base nas fontes validadas às 14:32.</p>,
},
]}
/>Quando usar
- FAQs e documentação.
- Configurações agrupadas por categoria.
- Evidências secundárias em detalhe de crise.
- Conteúdo complementar dentro de Sheet ou página longa.
Quando não usar
- Estado crítico que precisa estar sempre visível.
- Fluxos sequenciais obrigatórios.
- Menus de navegação global.
- Conteúdo tão curto que não justifica colapso.
Acessibilidade
- O trigger deve ser um botão ou
summarynativo. - Exponha
aria-expandedquando implementar manualmente. - Associe trigger e painel com
aria-controls/aria-labelledbyquando necessário. - Enter e Space precisam alternar abertura.
- Respeite
prefers-reduced-motionse animar altura/opacidade.
Regras
- Não esconda risco crítico dentro de painel fechado sem indicação externa.
- Títulos precisam descrever o conteúdo, não apenas “Detalhes”.
- Use abertura múltipla apenas quando comparar painéis for necessário.
- Conteúdo assistido por IA precisa manter origem e revisão humana visíveis.
- Não use Accordion para economizar espaço quando a informação é essencial para decisão.
Last updated on