HextaUI

Árbol

Filas anidadas y expandibles para archivos y jerarquías, con guías de sangría, líneas conectoras, checkboxes en cascada, typeahead y soporte completo de teclado.

app
layout.tsx
page.tsxM
globals.css
components
ui
button.tsxM
tree.tsxU
theme-provider.tsx
public
README.md
import {
  IconBrandTypescript,
  IconFileText,
  IconFolder,
  IconFolderOpen,
  IconPhoto,
} from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

function Folder({
  value,
  name,
  children,
}: {
  value: string
  name: string
  children: React.ReactNode
}) {
  return (
    <TreeItem value={value}>
      <TreeItemLabel icon={<IconFolder />} expandedIcon={<IconFolderOpen />}>
        {name}
      </TreeItemLabel>
      <TreeGroup>{children}</TreeGroup>
    </TreeItem>
  )
}

function File({
  value,
  name,
  icon = <IconBrandTypescript />,
  status,
}: {
  value: string
  name: string
  icon?: React.ReactNode
  status?: "M" | "U"
}) {
  return (
    <TreeItem value={value}>
      <TreeItemLabel
        icon={icon}
        meta={
          status ? (
            <span
              className={status === "M" ? "text-warning" : "text-success"}
              aria-label={status === "M" ? "Modified" : "Untracked"}
            >
              {status}
            </span>
          ) : null
        }
      >
        {name}
      </TreeItemLabel>
    </TreeItem>
  )
}

export function TreeDemo() {
  return (
    <Tree
      aria-label="Project files"
      defaultExpandedValues={["app", "components", "ui"]}
      defaultSelectedValues={["button"]}
      className="w-full max-w-xs"
    >
      <Folder value="app" name="app">
        <File value="layout" name="layout.tsx" />
        <File value="page" name="page.tsx" status="M" />
        <File value="globals" name="globals.css" icon={<IconFileText />} />
      </Folder>
      <Folder value="components" name="components">
        <Folder value="ui" name="ui">
          <File value="button" name="button.tsx" status="M" />
          <File value="tree" name="tree.tsx" status="U" />
        </Folder>
        <File value="theme" name="theme-provider.tsx" />
      </Folder>
      <Folder value="public" name="public">
        <File value="og" name="og.png" icon={<IconPhoto />} />
      </Folder>
      <File value="readme" name="README.md" icon={<IconFileText />} />
    </Tree>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/tree.json

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

import {
  Tree,
  TreeGroup,
  TreeItem,
  TreeItemLabel,
} from "@/components/ui/tree"
<Tree aria-label="Files" defaultExpandedValues={["src"]}>
  <TreeItem value="src">
    <TreeItemLabel icon={<IconFolder />}>src</TreeItemLabel>
    <TreeGroup>
      <TreeItem value="index">
        <TreeItemLabel icon={<IconFile />}>index.ts</TreeItemLabel>
      </TreeItem>
    </TreeGroup>
  </TreeItem>
</Tree>

Un elemento con un <TreeGroup /> es una rama: recibe un chevron, se abre y se cierra, y responde a las flechas. Cada elemento necesita un value único dentro del árbol.

Tree
└── TreeItem
    ├── TreeItemLabel
    └── TreeGroup
        └── TreeItem
            └── TreeItemLabel

Guías de sangría

variant="lines" dibuja una guía por cada nivel. La guía de la rama que contiene la fila seleccionada o enfocada se oscurece, para que veas dónde estás en árboles profundos.

import { IconFile, IconFolder, IconFolderOpen } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

type Node = { name: string; children?: Node[] }

const files: Node[] = [
  {
    name: "packages",
    children: [
      {
        name: "core",
        children: [
          {
            name: "src",
            children: [
              { name: "index.ts" },
              { name: "store.ts" },
              {
                name: "utils",
                children: [{ name: "clamp.ts" }, { name: "debounce.ts" }],
              },
            ],
          },
          { name: "package.json" },
        ],
      },
      {
        name: "react",
        children: [{ name: "index.tsx" }, { name: "package.json" }],
      },
    ],
  },
  { name: "turbo.json" },
]

function renderNodes(nodes: Node[], parent = ""): React.ReactNode {
  return nodes.map((node) => {
    const value = `${parent}/${node.name}`
    return node.children ? (
      <TreeItem key={value} value={value}>
        <TreeItemLabel icon={<IconFolder />} expandedIcon={<IconFolderOpen />}>
          {node.name}
        </TreeItemLabel>
        <TreeGroup>{renderNodes(node.children, value)}</TreeGroup>
      </TreeItem>
    ) : (
      <TreeItem key={value} value={value}>
        <TreeItemLabel icon={<IconFile />}>{node.name}</TreeItemLabel>
      </TreeItem>
    )
  })
}

export function TreeLines() {
  return (
    <Tree
      aria-label="Monorepo"
      variant="lines"
      defaultExpandedValues={[
        "/packages",
        "/packages/core",
        "/packages/core/src",
        "/packages/core/src/utils",
      ]}
      defaultSelectedValues={["/packages/core/src/utils/clamp.ts"]}
      className="w-full max-w-xs"
    >
      {renderNodes(files)}
    </Tree>
  )
}

Conectores

variant="connectors" une cada elemento con su padre mediante un codo, como el listado de árbol de una terminal. Usa meta para versiones, tamaños o recuentos.

import { IconPackage } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

type Dependency = { name: string; version: string; dependencies?: Dependency[] }

const dependencies: Dependency[] = [
  {
    name: "next",
    version: "16.3.8",
    dependencies: [
      { name: "@next/env", version: "16.3.8" },
      {
        name: "postcss",
        version: "8.5.6",
        dependencies: [
          { name: "nanoid", version: "3.3.11" },
          { name: "picocolors", version: "1.1.1" },
          { name: "source-map-js", version: "1.2.1" },
        ],
      },
      { name: "styled-jsx", version: "5.1.7" },
    ],
  },
  {
    name: "react-dom",
    version: "19.3.0",
    dependencies: [{ name: "scheduler", version: "0.27.0" }],
  },
]

function renderDependencies(items: Dependency[], parent = ""): React.ReactNode {
  return items.map((item) => {
    const value = `${parent}/${item.name}`
    return (
      <TreeItem key={value} value={value}>
        <TreeItemLabel icon={<IconPackage />} meta={item.version}>
          {item.name}
        </TreeItemLabel>
        {item.dependencies ? (
          <TreeGroup>{renderDependencies(item.dependencies, value)}</TreeGroup>
        ) : null}
      </TreeItem>
    )
  })
}

export function TreeConnectors() {
  return (
    <Tree
      aria-label="Dependencies"
      variant="connectors"
      selectionMode="none"
      defaultExpandedValues={["/next", "/next/postcss", "/react-dom"]}
      className="w-full max-w-xs"
    >
      {renderDependencies(dependencies)}
    </Tree>
  )
}

Casillas de verificación

Con checkboxes, marcar una rama marca todos los elementos habilitados que contiene, y una rama parcialmente marcada muestra un guion. Marcar una rama también cuenta como marcar los hijos que cargues más tarde. Hacer clic en una fila la marca, y el chevron la abre.

"use client"

import * as React from "react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

const permissions = [
  {
    value: "projects",
    label: "Projects",
    children: [
      { value: "projects.view", label: "View projects" },
      { value: "projects.edit", label: "Edit projects" },
      { value: "projects.delete", label: "Delete projects" },
    ],
  },
  {
    value: "members",
    label: "Members",
    children: [
      { value: "members.invite", label: "Invite members" },
      { value: "members.remove", label: "Remove members" },
    ],
  },
  {
    value: "billing",
    label: "Billing",
    children: [
      { value: "billing.invoices", label: "View invoices" },
      { value: "billing.plan", label: "Change plan", disabled: true },
    ],
  },
]

const leaves = permissions.flatMap((group) => group.children)

export function TreeCheckboxes() {
  const [checked, setChecked] = React.useState<string[]>([
    "projects.view",
    "members.invite",
    "members.remove",
  ])
  const count = leaves.filter((leaf) => checked.includes(leaf.value)).length

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <Tree
        aria-label="Permissions"
        checkboxes
        checkedValues={checked}
        onCheckedValuesChange={setChecked}
        defaultExpandedValues={["projects", "billing"]}
      >
        {permissions.map((group) => (
          <TreeItem key={group.value} value={group.value}>
            <TreeItemLabel>{group.label}</TreeItemLabel>
            <TreeGroup>
              {group.children.map((permission) => (
                <TreeItem
                  key={permission.value}
                  value={permission.value}
                  disabled={"disabled" in permission}
                >
                  <TreeItemLabel
                    meta={"disabled" in permission ? "Owner only" : null}
                  >
                    {permission.label}
                  </TreeItemLabel>
                </TreeItem>
              ))}
            </TreeGroup>
          </TreeItem>
        ))}
      </Tree>
      <p className="text-sm text-muted-foreground" aria-live="polite">
        {count} of {leaves.length} permissions granted
      </p>
    </div>
  )
}

Renderiza las filas como enlaces con render. Pasa la página actual a selectedValues, abre su sección y define aria-current en su fila.

import Link from "next/link"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

const sections = [
  {
    value: "getting-started",
    title: "Getting started",
    pages: [{ value: "installation", title: "Installation" }],
  },
  {
    value: "components",
    title: "Components",
    pages: [
      { value: "accordion", title: "Accordion" },
      { value: "collapsible", title: "Collapsible" },
      { value: "tabs", title: "Tabs" },
      { value: "tree", title: "Tree" },
    ],
  },
]

const current = "tree"

export function TreeNavigation() {
  return (
    <nav aria-label="Docs" className="w-full max-w-60">
      <Tree
        aria-label="Docs pages"
        size="sm"
        selectedValues={[current]}
        defaultExpandedValues={["components"]}
      >
        {sections.map((section) => (
          <TreeItem key={section.value} value={section.value}>
            <TreeItemLabel>{section.title}</TreeItemLabel>
            <TreeGroup>
              {section.pages.map((page) => (
                <TreeItem key={page.value} value={page.value}>
                  <TreeItemLabel
                    render={<Link href={`/docs/${page.value}`} />}
                    aria-current={page.value === current ? "page" : undefined}
                  >
                    {page.title}
                  </TreeItemLabel>
                </TreeItem>
              ))}
            </TreeGroup>
          </TreeItem>
        ))}
      </Tree>
    </nav>
  )
}

Selección múltiple

selectionMode="multiple" funciona como un gestor de archivos: clic para seleccionar una fila, Cmd/Ctrl+clic para añadir o quitar una, Shift+clic para seleccionar un rango. En pantallas táctiles un toque añade o quita la fila.

"use client"

import * as React from "react"
import { IconFile, IconFolder, IconFolderOpen } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

const photos = ["beach.jpg", "city.jpg", "forest.jpg", "mountains.jpg"]
const documents = ["invoice.pdf", "notes.md", "resume.pdf"]

export function TreeMultiple() {
  const [selected, setSelected] = React.useState<string[]>(["city.jpg"])

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <Tree
        aria-label="Library"
        selectionMode="multiple"
        selectedValues={selected}
        onSelectedValuesChange={setSelected}
        defaultExpandedValues={["photos", "documents"]}
      >
        <TreeItem value="photos">
          <TreeItemLabel
            icon={<IconFolder />}
            expandedIcon={<IconFolderOpen />}
          >
            Photos
          </TreeItemLabel>
          <TreeGroup>
            {photos.map((name) => (
              <TreeItem key={name} value={name}>
                <TreeItemLabel icon={<IconFile />}>{name}</TreeItemLabel>
              </TreeItem>
            ))}
          </TreeGroup>
        </TreeItem>
        <TreeItem value="documents">
          <TreeItemLabel
            icon={<IconFolder />}
            expandedIcon={<IconFolderOpen />}
          >
            Documents
          </TreeItemLabel>
          <TreeGroup>
            {documents.map((name) => (
              <TreeItem key={name} value={name}>
                <TreeItemLabel icon={<IconFile />}>{name}</TreeItemLabel>
              </TreeItem>
            ))}
          </TreeGroup>
        </TreeItem>
      </Tree>
      <p className="text-sm text-muted-foreground" aria-live="polite">
        {selected.length === 0
          ? "Nothing selected"
          : `${selected.length} selected`}
      </p>
    </div>
  )
}

Controlado

Mantén las ramas abiertas en tu propio estado con expandedValues y onExpandedValuesChange. Este ejemplo también construye el árbol a partir de datos con una función recursiva.

"use client"

import * as React from "react"
import { IconFile, IconFolder, IconFolderOpen } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

type Node = { id: string; name: string; children?: Node[] }

const nodes: Node[] = [
  {
    id: "src",
    name: "src",
    children: [
      {
        id: "hooks",
        name: "hooks",
        children: [
          { id: "use-media", name: "use-media.ts" },
          { id: "use-copy", name: "use-copy.ts" },
        ],
      },
      { id: "main", name: "main.tsx" },
    ],
  },
  {
    id: "tests",
    name: "tests",
    children: [{ id: "smoke", name: "smoke.test.ts" }],
  },
]

function branchIds(items: Node[]): string[] {
  return items.flatMap((item) =>
    item.children ? [item.id, ...branchIds(item.children)] : []
  )
}

function renderNodes(items: Node[]): React.ReactNode {
  return items.map((item) =>
    item.children ? (
      <TreeItem key={item.id} value={item.id}>
        <TreeItemLabel icon={<IconFolder />} expandedIcon={<IconFolderOpen />}>
          {item.name}
        </TreeItemLabel>
        <TreeGroup>{renderNodes(item.children)}</TreeGroup>
      </TreeItem>
    ) : (
      <TreeItem key={item.id} value={item.id}>
        <TreeItemLabel icon={<IconFile />}>{item.name}</TreeItemLabel>
      </TreeItem>
    )
  )
}

export function TreeControlled() {
  const [expanded, setExpanded] = React.useState<string[]>(["src"])
  const [selected, setSelected] = React.useState<string[]>([])

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <div className="flex gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => setExpanded(branchIds(nodes))}
        >
          Expand all
        </Button>
        <Button variant="outline" size="sm" onClick={() => setExpanded([])}>
          Collapse all
        </Button>
      </div>
      <Tree
        aria-label="Source"
        expandedValues={expanded}
        onExpandedValuesChange={setExpanded}
        selectedValues={selected}
        onSelectedValuesChange={setSelected}
      >
        {renderNodes(nodes)}
      </Tree>
      <p className="text-sm text-muted-foreground">
        Selected: {selected[0] ?? "none"}
      </p>
    </div>
  )
}

Carga diferida

Obtén los hijos en onExpandedChange de <TreeItem />. Renderiza una fila de carga deshabilitada hasta que lleguen, para que la rama conserve su chevron.

"use client"

import * as React from "react"
import { IconFile, IconFolder, IconFolderOpen } from "@tabler/icons-react"

import { Spinner } from "@/components/ui/spinner"
import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

function listFiles(folder: string) {
  return new Promise<string[]>((resolve) =>
    setTimeout(
      () => resolve([`${folder}-1.log`, `${folder}-2.log`, `${folder}-3.log`]),
      900
    )
  )
}

function RemoteFolder({ name }: { name: string }) {
  const [files, setFiles] = React.useState<string[] | null>(null)
  const loading = React.useRef(false)

  return (
    <TreeItem
      value={name}
      onExpandedChange={(expanded) => {
        if (expanded && files === null && !loading.current) {
          loading.current = true
          listFiles(name).then(setFiles)
        }
      }}
    >
      <TreeItemLabel icon={<IconFolder />} expandedIcon={<IconFolderOpen />}>
        {name}
      </TreeItemLabel>
      <TreeGroup aria-busy={files === null}>
        {files === null ? (
          <TreeItem value={`${name}/loading`} disabled>
            <TreeItemLabel icon={<Spinner size="sm" label="Loading files" />}>
              Loading…
            </TreeItemLabel>
          </TreeItem>
        ) : (
          files.map((file) => (
            <TreeItem key={file} value={`${name}/${file}`}>
              <TreeItemLabel icon={<IconFile />}>{file}</TreeItemLabel>
            </TreeItem>
          ))
        )}
      </TreeGroup>
    </TreeItem>
  )
}

export function TreeLazy() {
  return (
    <Tree aria-label="Server logs" className="w-full max-w-xs">
      <RemoteFolder name="api" />
      <RemoteFolder name="worker" />
      <RemoteFolder name="cron" />
    </Tree>
  )
}

Pequeño

size="sm" usa filas más cortas y sangrías más ajustadas, para sidebars. Las filas crecen en pantallas táctiles en ambos tamaños.

import { IconHash, IconVolume } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

export function TreeSmall() {
  return (
    <Tree
      aria-label="Channels"
      size="sm"
      defaultExpandedValues={["text", "voice"]}
      defaultSelectedValues={["general"]}
      className="w-full max-w-56"
    >
      <TreeItem value="text">
        <TreeItemLabel>Text channels</TreeItemLabel>
        <TreeGroup>
          <TreeItem value="general">
            <TreeItemLabel icon={<IconHash />} meta="12">
              general
            </TreeItemLabel>
          </TreeItem>
          <TreeItem value="design">
            <TreeItemLabel icon={<IconHash />}>design</TreeItemLabel>
          </TreeItem>
          <TreeItem value="releases">
            <TreeItemLabel icon={<IconHash />} meta="3">
              releases
            </TreeItemLabel>
          </TreeItem>
        </TreeGroup>
      </TreeItem>
      <TreeItem value="voice">
        <TreeItemLabel>Voice channels</TreeItemLabel>
        <TreeGroup>
          <TreeItem value="lounge">
            <TreeItemLabel icon={<IconVolume />}>Lounge</TreeItemLabel>
          </TreeItem>
        </TreeGroup>
      </TreeItem>
    </Tree>
  )
}

Deshabilitado

Un elemento deshabilitado no se puede seleccionar, marcar ni abrir, y las flechas lo omiten. Los elementos dentro de una rama deshabilitada también están deshabilitados.

import { IconFile, IconFolder, IconLock } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

export function TreeDisabled() {
  return (
    <Tree
      aria-label="Shared drive"
      defaultExpandedValues={["team"]}
      className="w-full max-w-xs"
    >
      <TreeItem value="team">
        <TreeItemLabel icon={<IconFolder />}>Team</TreeItemLabel>
        <TreeGroup>
          <TreeItem value="roadmap">
            <TreeItemLabel icon={<IconFile />}>roadmap.md</TreeItemLabel>
          </TreeItem>
          <TreeItem value="salaries" disabled>
            <TreeItemLabel icon={<IconLock />}>salaries.xlsx</TreeItemLabel>
          </TreeItem>
          <TreeItem value="notes">
            <TreeItemLabel icon={<IconFile />}>notes.md</TreeItemLabel>
          </TreeItem>
        </TreeGroup>
      </TreeItem>
      <TreeItem value="archive" disabled>
        <TreeItemLabel icon={<IconLock />}>Archive</TreeItemLabel>
        <TreeGroup>
          <TreeItem value="2024">
            <TreeItemLabel icon={<IconFile />}>2024.zip</TreeItemLabel>
          </TreeItem>
        </TreeGroup>
      </TreeItem>
    </Tree>
  )
}

Nombres largos

Los nombres se cortan con puntos suspensivos y meta permanece visible. Pasa title para mostrar el nombre completo al pasar el cursor.

import { IconFile, IconFolder, IconFolderOpen } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

const longFolder = "quarterly-financial-reports-and-board-presentations"
const longFile = "2026-q3-consolidated-revenue-forecast-final-v12-approved.xlsx"
const unbroken = "averyveryverylongfilenamewithoutanyspacesoranybreaks.txt"

export function TreeLongNames() {
  return (
    <Tree
      aria-label="Reports"
      variant="lines"
      defaultExpandedValues={["reports"]}
      className="w-full max-w-56"
    >
      <TreeItem value="reports">
        <TreeItemLabel
          icon={<IconFolder />}
          expandedIcon={<IconFolderOpen />}
          title={longFolder}
        >
          {longFolder}
        </TreeItemLabel>
        <TreeGroup>
          <TreeItem value="forecast">
            <TreeItemLabel icon={<IconFile />} meta="2.4 MB" title={longFile}>
              {longFile}
            </TreeItemLabel>
          </TreeItem>
          <TreeItem value="unbroken">
            <TreeItemLabel icon={<IconFile />} title={unbroken}>
              {unbroken}
            </TreeItemLabel>
          </TreeItem>
        </TreeGroup>
      </TreeItem>
    </Tree>
  )
}

De derecha a izquierda

Las sangrías, los conectores y los chevrons se reflejan, y las flechas izquierda y derecha se intercambian.

import { IconFile, IconFolder, IconFolderOpen } from "@tabler/icons-react"

import { Tree, TreeGroup, TreeItem, TreeItemLabel } from "@/components/ui/tree"

export function TreeRtl() {
  return (
    <div dir="rtl" className="w-full max-w-xs">
      <Tree
        aria-label="المستندات"
        variant="connectors"
        defaultExpandedValues={["docs", "invoices"]}
        defaultSelectedValues={["march"]}
      >
        <TreeItem value="docs">
          <TreeItemLabel
            icon={<IconFolder />}
            expandedIcon={<IconFolderOpen />}
          >
            المستندات
          </TreeItemLabel>
          <TreeGroup>
            <TreeItem value="invoices">
              <TreeItemLabel
                icon={<IconFolder />}
                expandedIcon={<IconFolderOpen />}
              >
                الفواتير
              </TreeItemLabel>
              <TreeGroup>
                <TreeItem value="march">
                  <TreeItemLabel icon={<IconFile />} meta="١٢٠ ك.ب">
                    مارس.pdf
                  </TreeItemLabel>
                </TreeItem>
                <TreeItem value="april">
                  <TreeItemLabel icon={<IconFile />}>أبريل.pdf</TreeItemLabel>
                </TreeItem>
              </TreeGroup>
            </TreeItem>
            <TreeItem value="contract">
              <TreeItemLabel icon={<IconFile />}>العقد.docx</TreeItemLabel>
            </TreeItem>
          </TreeGroup>
        </TreeItem>
        <TreeItem value="notes">
          <TreeItemLabel icon={<IconFile />}>ملاحظات.txt</TreeItemLabel>
        </TreeItem>
      </Tree>
    </div>
  )
}
KeyAcción
↓Mueve el foco a la siguiente fila visible.
↑Mueve el foco a la fila visible anterior.
→Abre una rama cerrada. En una rama abierta, mueve el foco a su primer hijo.
←Cierra una rama abierta. En caso contrario, mueve el foco al padre.
HomeMueve el foco a la primera fila.
EndMueve el foco a la última fila visible.
EnterSelecciona la fila y la abre o la cierra si es una rama. Sigue el enlace en las filas de enlace.
SpaceSelecciona la fila, la añade o quita en modo múltiple, o la marca con checkboxes.
Shift + ↑Shift + ↓Amplía la selección en modo múltiple.
Cmd/Ctrl + ASelecciona todas las filas visibles en modo múltiple.
*Abre todas las ramas del nivel de la fila enfocada.
a–zMueve el foco a la siguiente fila cuyo nombre empieza por las letras escritas.

El árbol sigue el patrón tree view de WAI-ARIA. Cada fila es el treeitem y es dueña de su grupo mediante aria-owns, así que el nombre de una fila es solo su propia etiqueta y las filas de enlace funcionan como enlaces reales. Solo una fila está en el orden de tabulación a la vez. Cuando una rama se cierra alrededor de la fila enfocada, el foco pasa a esa rama en lugar de perderse.

Los grupos cerrados permanecen en la página, así que la búsqueda en página del navegador abre las carpetas alrededor de una coincidencia, y marcar una rama ve todos los elementos que contiene. Dale al árbol un aria-label.

Las ramas se abren y cierran con el collapsible de Base UI. <TreeItem /> y <TreeGroup /> aceptan las props de las partes que envuelven.

PropTipoPredeterminado
variant
"default" | "lines" | "connectors""default"
size
"default" | "sm""default"
selectionMode
"none" | "single" | "multiple""single"
selectedValues
string[]–
defaultSelectedValues
string[]–
onSelectedValuesChange
(values: string[]) => void–
expandedValues
string[]–
defaultExpandedValues
string[]–
onExpandedValuesChange
(values: string[]) => void–
checkboxesMuestra checkboxes en cascada. Las filas se marcan en lugar de seleccionarse.
booleanfalse
checkedValuesElementos marcados. Una rama en la lista cuenta como marcar a sus hijos.
string[]–
defaultCheckedValues
string[]–
onCheckedValuesChangeSe llama con cada hoja marcada y cada rama completamente marcada.
(values: string[]) => void–
disabled
booleanfalse
hiddenUntilFoundPermite que la búsqueda en página abra las ramas cerradas alrededor de una coincidencia.
booleantrue
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="tree"Apunta a la raíz en CSS.
data-variantLa variante actual.
data-sizeEl tamaño actual.
--tree-row-heightAltura de la fila. Mayor en pantallas táctiles.
--tree-indentCuánto se sangra cada nivel.
PropTipoPredeterminado
valueObligatorio. Único dentro del árbol.
string–
disabled
booleanfalse
onExpandedChangeSe llama cuando este elemento se abre o se cierra.
(expanded: boolean) => void–
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="tree-item"Apunta a los elementos en CSS.
data-openPresente cuando el elemento está abierto.
data-disabledPresente cuando el elemento está deshabilitado.
PropTipoPredeterminado
iconSe muestra antes de la etiqueta.
ReactNode–
expandedIconAparece con fundido cruzado para icon mientras la rama está abierta.
ReactNode–
metaSe muestra al final de la fila, como un tamaño, un recuento o un estado.
ReactNode–
className
string–
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="tree-item-label"Apunta a las filas en CSS.
data-valueEl valor del elemento.
data-expandedPresente cuando la rama está abierta.
data-selectedPresente cuando la fila está seleccionada.
data-checkedPresente cuando la fila está marcada.
data-indeterminatePresente cuando solo están marcados algunos hijos.
data-disabledPresente cuando el elemento está deshabilitado.
PropTipoPredeterminado
classNameSe aplica al contenedor interno, para que el padding nunca choque con la animación de altura.
string–
AtributoDescripción
data-slot="tree-group"Apunta a los grupos en CSS.
data-openPresente cuando el grupo está abierto.
data-starting-stylePresente mientras el grupo se anima al entrar.
data-ending-stylePresente mientras el grupo se anima al salir.
--collapsible-panel-heightLa altura medida del grupo, usada para la animación de altura.

Usado en bloques

Bloques que se construyen sobre Tree.