HextaUI

Tree

Verschachtelte, aufklappbare Zeilen für Dateien und Hierarchien, mit Einrückungslinien, Verbindungslinien, kaskadierenden Checkboxen, Typeahead und voller Tastaturunterstützung.

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

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

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>

Ein Item mit <TreeGroup /> ist ein Zweig: Es erhält ein Chevron, öffnet und schließt sich und reagiert auf die Pfeiltasten. Jedes Item braucht einen value, der im Baum eindeutig ist.

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

Einrückungslinien

variant="lines" zeichnet für jede Ebene eine Führungslinie. Die Linie des Zweigs, der die ausgewählte oder fokussierte Zeile enthält, wird dunkler, sodass du in tiefen Bäumen siehst, wo du bist.

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>
  )
}

Verbindungslinien

variant="connectors" verbindet jedes Item mit einem Winkel mit seinem Elternelement, wie eine Terminal-Baumauflistung. Nutze meta für Versionen, Größen oder Anzahlen.

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>
  )
}

Checkboxen

Mit checkboxes hakt das Anhaken eines Zweigs jedes aktivierte Item darin an, und ein teilweise angehakter Zweig zeigt einen Strich. Das Anhaken eines Zweigs zählt auch als Anhaken von Kindern, die du später lädst. Ein Klick auf eine Zeile hakt sie an, und das Chevron öffnet sie.

"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>
  )
}

Rendere Zeilen mit render als Links. Übergib die aktuelle Seite an selectedValues, öffne ihren Abschnitt und setze aria-current an ihrer Zeile.

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>
  )
}

Mehrfachauswahl

selectionMode="multiple" funktioniert wie ein Dateimanager: Klick wählt eine Zeile, Cmd/Strg+Klick fügt eine hinzu oder entfernt sie, Shift+Klick wählt einen Bereich. Auf Touchscreens fügt ein Tippen die Zeile hinzu oder entfernt sie.

"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>
  )
}

Kontrolliert

Halte offene Zweige in deinem eigenen State mit expandedValues und onExpandedValuesChange. Dieses Beispiel baut den Baum auch mit einer rekursiven Funktion aus Daten auf.

"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>
  )
}

Lazy Loading

Lade Kinder in onExpandedChange an <TreeItem />. Rendere eine deaktivierte Ladezeile, bis sie eintreffen, damit der Zweig sein Chevron behält.

"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>
  )
}

Klein

size="sm" nutzt kürzere Zeilen und engere Einrückungen, für Sidebars. Zeilen wachsen auf Touchscreens in beiden Größen.

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>
  )
}

Deaktiviert

Ein deaktiviertes Item kann nicht ausgewählt, angehakt oder geöffnet werden, und die Pfeiltasten überspringen es. Items in einem deaktivierten Zweig sind ebenfalls deaktiviert.

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>
  )
}

Lange Namen

Namen werden mit einer Ellipse abgeschnitten, und meta bleibt sichtbar. Übergib title, um den vollen Namen bei Hover zu zeigen.

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>
  )
}

Rechts nach links

Einrückungen, Verbindungslinien und Chevrons spiegeln sich, und die Pfeiltasten links und rechts tauschen.

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>
  )
}
TasteAktion
↓Setzt den Fokus auf die nächste sichtbare Zeile.
↑Setzt den Fokus auf die vorherige sichtbare Zeile.
→Öffnet einen geschlossenen Zweig. Bei einem offenen Zweig wandert der Fokus zu seinem ersten Kind.
←Schließt einen offenen Zweig. Sonst wandert der Fokus zum Elternelement.
HomeSetzt den Fokus auf die erste Zeile.
EndSetzt den Fokus auf die letzte sichtbare Zeile.
EnterWählt die Zeile aus und öffnet oder schließt sie, wenn sie ein Zweig ist. Folgt bei Link-Zeilen dem Link.
SpaceWählt die Zeile aus, fügt sie im Mehrfachmodus hinzu oder entfernt sie, oder hakt sie mit Checkboxen an.
Shift + ↑Shift + ↓Erweitert die Auswahl im Mehrfachmodus.
Cmd/Ctrl + AWählt im Mehrfachmodus jede sichtbare Zeile aus.
*Öffnet jeden Zweig auf der Ebene der fokussierten Zeile.
a–zSetzt den Fokus auf die nächste Zeile, deren Name mit den getippten Buchstaben beginnt.

Der Baum folgt dem WAI-ARIA-Tree-View-Muster. Jede Zeile ist das treeitem und besitzt ihre Gruppe über aria-owns, sodass der Name einer Zeile nur ihr eigenes Label ist und Link-Zeilen als echte Links funktionieren. Immer nur eine Zeile ist in der Tab-Reihenfolge. Schließt sich ein Zweig um die fokussierte Zeile, wandert der Fokus zu diesem Zweig, statt verloren zu gehen.

Geschlossene Gruppen bleiben in der Seite, sodass die Seitensuche des Browsers die Ordner um einen Treffer öffnet und das Anhaken eines Zweigs jedes Item darin erfasst. Gib dem Baum ein aria-label.

Zweige öffnen und schließen sich mit dem Base UI Collapsible. <TreeItem /> und <TreeGroup /> akzeptieren die Props der Teile, die sie umschließen.

PropTypStandard
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–
checkboxesZeige kaskadierende Checkboxen. Zeilen werden angehakt statt ausgewählt.
booleanfalse
checkedValuesAngehakte Items. Ein Zweig in der Liste zählt als Anhaken seiner Kinder.
string[]–
defaultCheckedValues
string[]–
onCheckedValuesChangeWird mit jedem angehakten Blatt und jedem vollständig angehakten Zweig aufgerufen.
(values: string[]) => void–
disabled
booleanfalse
hiddenUntilFoundLass die Seitensuche geschlossene Zweige um einen Treffer öffnen.
booleantrue
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="tree"Das Root in CSS ansprechen.
data-variantDie aktuelle Variante.
data-sizeDie aktuelle Größe.
--tree-row-heightZeilenhöhe. Auf Touchscreens größer.
--tree-indentWie weit jede Ebene eingerückt wird.
PropTypStandard
valueErforderlich. Im Baum eindeutig.
string–
disabled
booleanfalse
onExpandedChangeWird aufgerufen, wenn dieses Item öffnet oder schließt.
(expanded: boolean) => void–
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="tree-item"Items in CSS ansprechen.
data-openVorhanden, wenn der Eintrag geöffnet ist.
data-disabledVorhanden, wenn das Item deaktiviert ist.
PropTypStandard
iconVor dem Label angezeigt.
ReactNode–
expandedIconBlendet für icon ein, während der Zweig offen ist.
ReactNode–
metaAm Ende der Zeile angezeigt, etwa Größe, Anzahl oder Status.
ReactNode–
className
string–
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="tree-item-label"Zeilen per CSS ansprechen.
data-valueDer Wert des Items.
data-expandedVorhanden, wenn der Zweig offen ist.
data-selectedVorhanden, wenn die Zeile ausgewählt ist.
data-checkedVorhanden, wenn die Zeile angehakt ist.
data-indeterminateVorhanden, wenn nur einige Kinder angehakt sind.
data-disabledVorhanden, wenn das Item deaktiviert ist.
PropTypStandard
classNameWird auf den inneren Wrapper angewendet, damit Padding nie mit der Höhenanimation kollidiert.
string–
AttributBeschreibung
data-slot="tree-group"Gruppen in CSS ansprechen.
data-openVorhanden, wenn die Gruppe offen ist.
data-starting-styleVorhanden, solange die Gruppe einblendet.
data-ending-styleVorhanden, solange die Gruppe ausblendet.
--collapsible-panel-heightDie gemessene Höhe der Gruppe, für die Höhenanimation verwendet.

In Blocks verwendet

Blocks, die auf Tree aufbauen.