Treeview
Árvore de navegação e seleção inline, com profundidade ilimitada e roving tabindex. (`mk-treeview`)
O mk-treeview é uma árvore de navegação e seleção inline — hierarquia de dados de profundidade arbitrária, como uma estrutura de pastas, categorias aninhadas ou um organograma. Diferente do modo tree do mk-select, ele não vive dentro de um dropdown flutuante: é um bloco de conteúdo comum, sempre visível na página.
Assim como mk-sidebar, não é uma única tag: você monta a árvore combinando mk-treeview (raiz), mk-treeview-node (nó expansível) e mk-treeview-item (item folha), aninhando mk-treeview-node/mk-treeview-item livremente dentro de outros nós, sem limite de profundidade.
Quando usar
Use quando:- Precisar exibir uma hierarquia navegável de profundidade arbitrária — pastas, categorias, organograma
- A árvore precisar viver inline, ocupando uma coluna ou painel — nunca sobreposta a outro conteúdo
- Precisar de seleção única ou múltipla sobre os nós da hierarquia, ou apenas navegação sem seleção
- A hierarquia for um campo de formulário dentro de um dropdown flutuante — use
mk-selectcomtree - For navegação lateral de aplicação com no máximo 3 níveis — use
mk-sidebar
Estrutura
mk-treeview ← raiz; role="tree", roving tabindex, fonte da verdade da seleção
├── mk-treeview-node "Documentos" ← nó, com filhos
│ ├── mk-treeview-item "Contrato.pdf" ← folha
│ └── mk-treeview-node "Relatórios" ← nó aninhado, sem limite de profundidade
│ ├── mk-treeview-item "Q1.xlsx"
│ └── mk-treeview-item "Q2.xlsx"
│
└── mk-treeview-item "Notas.txt" ← folha de primeiro nívelPor que o indicador de marcação não é um mk-checkbox
mk-checkbox renderiza um <input type="checkbox"> real, sem prop que o tire do ciclo de
tabulação, e é formAssociated. Instanciá-lo em cada linha criaria um segundo tab stop por
linha — o oposto do tab stop único que role="tree" exige — e um role="checkbox" aninhado
dentro de um treeitem, que não é uma composição válida no APG. O indicador é desenhado no
shadow root da própria linha, aria-hidden="true", reusando os mesmos tokens visuais do
mk-checkbox. O estado real de seleção é comunicado por aria-selected no host.
Padrão
Em selection-mode="none" (o padrão), a árvore é puramente navegável — nenhuma linha é selecionável, e o consumidor ouve mkClick em qualquer linha para rotear sem depender de seleção. O chevron de um nó sempre alterna a expansão, em qualquer modo.
import { MkTreeview, MkTreeviewItem, MkTreeviewNode } from '@db1/makuco-ui-react';
<MkTreeview label="Estrutura de pastas">
<MkTreeviewNode label="Documentos" value="docs" leadingIcon="folder" expanded>
<MkTreeviewItem label="Contrato.pdf" value="contract" leadingIcon="file" />
<MkTreeviewNode label="Relatórios" value="reports" leadingIcon="folder">
<MkTreeviewItem label="Q1.xlsx" value="q1" leadingIcon="file" />
<MkTreeviewItem label="Q2.xlsx" value="q2" leadingIcon="file" />
</MkTreeviewNode>
</MkTreeviewNode>
<MkTreeviewItem label="Notas.txt" value="notes" leadingIcon="file" />
</MkTreeview><mk-treeview label="Estrutura de pastas">
<mk-treeview-node label="Documentos" value="docs" leading-icon="folder" expanded>
<mk-treeview-item label="Contrato.pdf" value="contract" leading-icon="file"></mk-treeview-item>
<mk-treeview-node label="Relatórios" value="reports" leading-icon="folder">
<mk-treeview-item label="Q1.xlsx" value="q1" leading-icon="file"></mk-treeview-item>
<mk-treeview-item label="Q2.xlsx" value="q2" leading-icon="file"></mk-treeview-item>
</mk-treeview-node>
</mk-treeview-node>
<mk-treeview-item label="Notas.txt" value="notes" leading-icon="file"></mk-treeview-item>
</mk-treeview>Largura
size="md" (o padrão) trava o container em 320px. size="full" remove esse teto e ocupa 100% do espaço disponível — útil quando a árvore vive dentro de um grid ou flex container que já controla a largura da coluna.
<MkTreeview label="Estrutura de pastas" size="full">
<MkTreeviewNode label="Documentos" value="docs" leadingIcon="folder" expanded>
<MkTreeviewItem label="Contrato.pdf" value="contract" leadingIcon="file" />
</MkTreeviewNode>
<MkTreeviewItem label="Notas.txt" value="notes" leadingIcon="file" />
</MkTreeview><mk-treeview label="Estrutura de pastas" size="full">
<mk-treeview-node label="Documentos" value="docs" leading-icon="folder" expanded>
<mk-treeview-item label="Contrato.pdf" value="contract" leading-icon="file"></mk-treeview-item>
</mk-treeview-node>
<mk-treeview-item label="Notas.txt" value="notes" leading-icon="file"></mk-treeview-item>
</mk-treeview>Modo de seleção
selection-mode="single" permite uma linha selecionada por vez. selection-mode="multiple" exibe um indicador de marcação e permite várias linhas simultâneas, sem propagação para ancestrais ou descendentes — marcar um nó não marca seus filhos, e um filho marcado não afeta o pai.
O clique na linha (fora do chevron) só expande em none. Em single e multiple ele seleciona, e a expansão fica só no chevron — um único clique não carrega dois efeitos ao mesmo tempo. O padrão tree do WAI-ARIA não prescreve o alvo de clique; a divisão adotada aqui segue a mesma linha de árvores como Carbon Design System e Adobe Spectrum, que também restringem a expansão ao chevron quando a seleção está ativa.
<MkTreeview selectionMode="multiple" onMkChange={(e) => console.log(e.detail.value)}>
<MkTreeviewNode label="Categorias" value="categories" expanded>
<MkTreeviewItem label="Eletrônicos" value="electronics" />
<MkTreeviewItem label="Livros" value="books" />
<MkTreeviewItem label="Roupas" value="clothes" />
</MkTreeviewNode>
</MkTreeview><mk-treeview selection-mode="multiple" (mkChange)="onChange($event.detail.value)">
<mk-treeview-node label="Categorias" value="categories" expanded>
<mk-treeview-item label="Eletrônicos" value="electronics"></mk-treeview-item>
<mk-treeview-item label="Livros" value="books"></mk-treeview-item>
<mk-treeview-item label="Roupas" value="clothes"></mk-treeview-item>
</mk-treeview-node>
</mk-treeview>Ícone de trailing
trailingIcon renderiza um mk-icon ao final da linha, depois do label — um cadeado para conteúdo restrito, uma estrela para destaque. Sem a prop, o slot trailingIcon assume como fallback, para conteúdo que não é um ícone simples, como um mk-badge.
Os exemplos abaixo controlam value no app e escutam mkChange para refletir a seleção atual abaixo da árvore.
Seleção única
import { MkTreeview, MkTreeviewItem, MkTreeviewNode } from '@db1/makuco-ui-react';
import { useState } from 'react';
const [value, setValue] = useState<string>();
<MkTreeview
label="Documentos"
selectionMode="single"
value={value}
onMkChange={(e) => setValue(e.detail.value)}
>
<MkTreeviewNode label="Financeiro" value="fin" leadingIcon="folder" expanded>
<MkTreeviewItem label="Orçamento 2026" value="budget" leadingIcon="file" />
<MkTreeviewItem
label="Contratos"
value="contracts"
leadingIcon="file"
trailingIcon="lock"
/>
</MkTreeviewNode>
<MkTreeviewItem
label="Manual do colaborador"
value="handbook"
leadingIcon="file"
trailingIcon="star"
/>
</MkTreeview>
<p>Selecionado: {value ?? 'nenhum'}</p><mk-treeview
label="Documentos"
selection-mode="single"
[value]="value"
(mkChange)="value = $event.detail.value"
>
<mk-treeview-node label="Financeiro" value="fin" leading-icon="folder" expanded>
<mk-treeview-item label="Orçamento 2026" value="budget" leading-icon="file"></mk-treeview-item>
<mk-treeview-item
label="Contratos"
value="contracts"
leading-icon="file"
trailing-icon="lock"
></mk-treeview-item>
</mk-treeview-node>
<mk-treeview-item
label="Manual do colaborador"
value="handbook"
leading-icon="file"
trailing-icon="star"
></mk-treeview-item>
</mk-treeview>
<p>Selecionado: {{ value ?? 'nenhum' }}</p>Seleção múltipla
const [value, setValue] = useState<string[]>([]);
<MkTreeview
label="Recursos"
selectionMode="multiple"
value={value}
onMkChange={(e) => setValue(e.detail.value)}
>
<MkTreeviewNode label="Notificações" value="notifications" expanded>
<MkTreeviewItem label="E-mail" value="email" />
<MkTreeviewItem label="SMS" value="sms" trailingIcon="lock" />
<MkTreeviewItem label="Push" value="push" />
</MkTreeviewNode>
<MkTreeviewItem label="Relatórios avançados" value="reports" trailingIcon="lock" />
</MkTreeview>
<p>Selecionados: {value.length ? value.join(', ') : 'nenhum'}</p><mk-treeview
label="Recursos"
selection-mode="multiple"
[value]="value"
(mkChange)="value = $event.detail.value"
>
<mk-treeview-node label="Notificações" value="notifications" expanded>
<mk-treeview-item label="E-mail" value="email"></mk-treeview-item>
<mk-treeview-item label="SMS" value="sms" trailing-icon="lock"></mk-treeview-item>
<mk-treeview-item label="Push" value="push"></mk-treeview-item>
</mk-treeview-node>
<mk-treeview-item label="Relatórios avançados" value="reports" trailing-icon="lock"></mk-treeview-item>
</mk-treeview>
<p>Selecionados: {{ value.length ? value.join(', ') : 'nenhum' }}</p>Disabled em cascata
disabled em um mk-treeview-node desabilita toda a sua subárvore — nenhum descendente é selecionável ou alcançável pelo teclado. A subárvore continua expansível e legível: só a seleção e o foco são bloqueados.
<MkTreeview selectionMode="single">
<MkTreeviewNode label="Financeiro" value="fin" disabled expanded>
<MkTreeviewItem label="Faturas" value="invoices" />
<MkTreeviewItem label="Relatórios" value="fin-reports" />
</MkTreeviewNode>
<MkTreeviewItem label="Notas" value="notes" />
</MkTreeview><mk-treeview selection-mode="single">
<mk-treeview-node label="Financeiro" value="fin" disabled expanded>
<mk-treeview-item label="Faturas" value="invoices"></mk-treeview-item>
<mk-treeview-item label="Relatórios" value="fin-reports"></mk-treeview-item>
</mk-treeview-node>
<mk-treeview-item label="Notas" value="notes"></mk-treeview-item>
</mk-treeview>Carregamento assíncrono (lazy)
Marque o nó com lazy para declarar filhos ainda não carregados — o chevron aparece mesmo com o slot vazio. Na primeira expansão, o nó emite mkExpand; o consumidor liga loading (exibe um mk-skeleton no lugar dos filhos), injeta os filhos, e desliga loading. Reexpansões posteriores não reemitem mkExpand.
function LazyBranch() {
const [children, setChildren] = useState<string[] | null>(null);
const [loading, setLoading] = useState(false);
const handleExpand = () => {
setLoading(true);
setTimeout(() => {
setChildren(['Relatório Q1', 'Relatório Q2', 'Relatório Q3']);
setLoading(false);
}, 1200);
};
return (
<MkTreeview label="Estrutura de pastas">
<MkTreeviewNode label="Carregado sob demanda" value="lazy" leadingIcon="folder" lazy loading={loading} onMkExpand={handleExpand}>
{children?.map((label) => <MkTreeviewItem key={label} label={label} value={label} leadingIcon="file" />)}
</MkTreeviewNode>
</MkTreeview>
);
}<mk-treeview label="Estrutura de pastas">
<mk-treeview-node
label="Carregado sob demanda"
value="lazy"
leading-icon="folder"
lazy
[loading]="loading"
(mkExpand)="onExpand()"
>
<mk-treeview-item *ngFor="let item of children" [label]="item" [value]="item" leading-icon="file"></mk-treeview-item>
</mk-treeview-node>
</mk-treeview>Props — mk-treeview
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
selectionMode | 'none' | 'single' | 'multiple' | 'none' | Modo de seleção da árvore. none torna a árvore puramente navegável; single permite uma linha selecionada; multiple exibe o indicador de marcação e permite várias. |
size | 'md' | 'full' | 'md' | Largura do container. md trava em 320px; full ocupa 100% do espaço disponível, sem teto. |
value | string | string[] | — | Seleção atual. String em single, array em multiple. Mutável — o componente reatribui ao selecionar, e o consumidor pode sobrescrever a qualquer momento. |
disabled | boolean | false | Desabilita a árvore inteira: nenhuma linha é focável, selecionável ou expansível. |
label | string | — | Nome acessível da árvore, aplicado como aria-label no host. |
Props — mk-treeview-node
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
label | string | — | Texto da linha. Obrigatório. |
value | string | — | Identificador do nó na seleção. Sem value, o nó continua expansível e navegável, mas nunca é selecionado. |
leadingIcon | IconName | — | Ícone antes do indicador de marcação. Sem ele, o slot leadingIcon assume como fallback. |
trailingIcon | IconName | — | Ícone ao final da linha. Sem ele, o slot trailingIcon assume como fallback. |
expanded | boolean | false | Estado de expansão. Mutável — o nó alterna sozinho ao ser acionado, e o consumidor pode expandir ou colapsar programaticamente. |
disabled | boolean | false | Desabilita este nó e toda a sua subárvore. |
lazy | boolean | false | Declara que o nó tem filhos ainda não carregados. Faz o chevron aparecer mesmo com o slot vazio e libera mkExpand. |
loading | boolean | false | Exibe um mk-skeleton no lugar dos filhos e marca o host com aria-busy="true". |
Props — mk-treeview-item
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
label | string | — | Texto da linha. Quando ausente, o slot default é usado como conteúdo do label. |
value | string | — | Identificador do item na seleção. Sem value, o item nunca é selecionado. |
leadingIcon | IconName | — | Ícone antes do indicador de marcação. Sem ele, o slot leadingIcon assume como fallback. |
trailingIcon | IconName | — | Ícone ao final da linha. Sem ele, o slot trailingIcon assume como fallback. |
disabled | boolean | false | Desabilita o item. |
Eventos
| Evento | Componente | Payload | Descrição |
|---|---|---|---|
mkChange | mk-treeview | { value: string | string[] } | Sempre que a seleção muda por interação do usuário. Não dispara em selection-mode="none" nem quando o consumidor atribui value programaticamente. |
mkToggle | mk-treeview-node | { value: string | undefined, expanded: boolean } | A cada alternância de expansão, por clique no chevron ou teclado. |
mkExpand | mk-treeview-node | { value: string | undefined } | Apenas na primeira expansão de um nó lazy. Reexpansões posteriores não reemitem. |
mkClick | mk-treeview-node · mk-treeview-item | { value: string | undefined } | Na ativação da linha (clique, Enter ou Space), em qualquer selectionMode. Suprimido quando desabilitado. |
Slots
| Componente | Slot | Descrição |
|---|---|---|
mk-treeview | padrão | Linhas de primeiro nível: mk-treeview-node e mk-treeview-item. |
mk-treeview-node | padrão | Filhos do nó: mk-treeview-node e mk-treeview-item. |
mk-treeview-node | leadingIcon | Conteúdo antes do indicador de marcação, quando a prop leadingIcon não é usada. |
mk-treeview-node | trailingIcon | Conteúdo ao final da linha, quando a prop trailingIcon não é usada. |
mk-treeview-item | padrão | Conteúdo do label, quando a prop label não é usada. |
mk-treeview-item | leadingIcon | Conteúdo antes do indicador de marcação, quando a prop leadingIcon não é usada. |
mk-treeview-item | trailingIcon | Conteúdo ao final da linha, quando a prop trailingIcon não é usada. |
Acessibilidade
mk-treeviewrenderiza comrole="tree",aria-multiselectable="true"apenas emselection-mode="multiple", earia-labela partir da proplabel.- Cada linha renderiza com
role="treeitem"earia-level(profundidade + 1). Nós recebemaria-expandedsempre que têm filhos ou sãolazy;aria-selectedaparece emsingle/multiplee fica ausente emnone. - A árvore inteira é um único tab stop: o container mantém exatamente uma linha com
tabindex="0"(roving tabindex).
Teclado:
| Tecla | Ação |
|---|---|
Tab | Entra na árvore pela linha ativa do roving tabindex; o próximo Tab sai da árvore inteira |
Enter / Space | Ativa a linha focada — seleciona em single/multiple, expande em none |
↓ / ↑ | Move o foco para a próxima/anterior linha visível e habilitada |
→ | Nó colapsado: expande, sem mover o foco. Nó já expandido: move o foco para o primeiro filho. Folha: nada |
← | Nó expandido: colapsa, sem mover o foco. Nó colapsado ou folha: move o foco para o nó pai |
Home / End | Primeira/última linha visível e habilitada da árvore |
Em RTL, ← e → trocam de papel. Linhas com disabled efetivo (próprio ou herdado) são puladas pela navegação por setas e nunca recebem tabindex="0".