Makuco UI
ComponentesNavigation

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
Prefira uma alternativa quando:
  • A hierarquia for um campo de formulário dentro de um dropdown flutuante — use mk-select com tree
  • 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ível

Por 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

PropTipoPadrãoDescriçã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.
valuestring | 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.
disabledbooleanfalseDesabilita a árvore inteira: nenhuma linha é focável, selecionável ou expansível.
labelstringNome acessível da árvore, aplicado como aria-label no host.

Props — mk-treeview-node

PropTipoPadrãoDescrição
labelstringTexto da linha. Obrigatório.
valuestringIdentificador do nó na seleção. Sem value, o nó continua expansível e navegável, mas nunca é selecionado.
leadingIconIconNameÍcone antes do indicador de marcação. Sem ele, o slot leadingIcon assume como fallback.
trailingIconIconNameÍcone ao final da linha. Sem ele, o slot trailingIcon assume como fallback.
expandedbooleanfalseEstado de expansão. Mutável — o nó alterna sozinho ao ser acionado, e o consumidor pode expandir ou colapsar programaticamente.
disabledbooleanfalseDesabilita este nó e toda a sua subárvore.
lazybooleanfalseDeclara que o nó tem filhos ainda não carregados. Faz o chevron aparecer mesmo com o slot vazio e libera mkExpand.
loadingbooleanfalseExibe um mk-skeleton no lugar dos filhos e marca o host com aria-busy="true".

Props — mk-treeview-item

PropTipoPadrãoDescrição
labelstringTexto da linha. Quando ausente, o slot default é usado como conteúdo do label.
valuestringIdentificador do item na seleção. Sem value, o item nunca é selecionado.
leadingIconIconNameÍcone antes do indicador de marcação. Sem ele, o slot leadingIcon assume como fallback.
trailingIconIconNameÍcone ao final da linha. Sem ele, o slot trailingIcon assume como fallback.
disabledbooleanfalseDesabilita o item.

Eventos

EventoComponentePayloadDescrição
mkChangemk-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.
mkTogglemk-treeview-node{ value: string | undefined, expanded: boolean }A cada alternância de expansão, por clique no chevron ou teclado.
mkExpandmk-treeview-node{ value: string | undefined }Apenas na primeira expansão de um nó lazy. Reexpansões posteriores não reemitem.
mkClickmk-treeview-node · mk-treeview-item{ value: string | undefined }Na ativação da linha (clique, Enter ou Space), em qualquer selectionMode. Suprimido quando desabilitado.

Slots

ComponenteSlotDescrição
mk-treeviewpadrãoLinhas de primeiro nível: mk-treeview-node e mk-treeview-item.
mk-treeview-nodepadrãoFilhos do nó: mk-treeview-node e mk-treeview-item.
mk-treeview-nodeleadingIconConteúdo antes do indicador de marcação, quando a prop leadingIcon não é usada.
mk-treeview-nodetrailingIconConteúdo ao final da linha, quando a prop trailingIcon não é usada.
mk-treeview-itempadrãoConteúdo do label, quando a prop label não é usada.
mk-treeview-itemleadingIconConteúdo antes do indicador de marcação, quando a prop leadingIcon não é usada.
mk-treeview-itemtrailingIconConteúdo ao final da linha, quando a prop trailingIcon não é usada.

Acessibilidade

  • mk-treeview renderiza com role="tree", aria-multiselectable="true" apenas em selection-mode="multiple", e aria-label a partir da prop label.
  • Cada linha renderiza com role="treeitem" e aria-level (profundidade + 1). Nós recebem aria-expanded sempre que têm filhos ou são lazy; aria-selected aparece em single/multiple e fica ausente em none.
  • A árvore inteira é um único tab stop: o container mantém exatamente uma linha com tabindex="0" (roving tabindex).

Teclado:

TeclaAção
TabEntra na árvore pela linha ativa do roving tabindex; o próximo Tab sai da árvore inteira
Enter / SpaceAtiva 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 / EndPrimeira/ú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".

On this page