Tree View
A component that is used to show a tree hierarchy
import { TreeView, type TreeViewData } from '~/components/ui/tree-view'
export const Demo = () => {
  return <TreeView data={data} maxW="2xs" />
}
const data: TreeViewData = {
  label: 'Root',
  children: [
    {
      value: '1',
      name: 'Item 1',
      children: [
        {
          value: '1.1',
          name: 'Item 1.1',
        },
        {
          value: '1.2',
          name: 'Item 1.2',
          children: [
            {
              value: '1.2.1',
              name: 'Item 1.2.1',
            },
            {
              value: '1.2.2',
              name: 'Item 1.2.2',
            },
          ],
        },
      ],
    },
    {
      value: '2',
      name: 'Item 2',
      children: [
        {
          value: '2.1',
          name: 'Item 2.1',
        },
        {
          value: '2.2',
          name: 'Item 2.2',
        },
      ],
    },
    {
      value: '3',
      name: 'Item 3',
    },
  ],
}
import { TreeView, type TreeViewData } from '~/components/ui/tree-view'
export const Demo = () => {
  return <TreeView data={data} maxW="2xs" />
}
const data: TreeViewData = {
  label: 'Root',
  children: [
    {
      value: '1',
      name: 'Item 1',
      children: [
        {
          value: '1.1',
          name: 'Item 1.1',
        },
        {
          value: '1.2',
          name: 'Item 1.2',
          children: [
            {
              value: '1.2.1',
              name: 'Item 1.2.1',
            },
            {
              value: '1.2.2',
              name: 'Item 1.2.2',
            },
          ],
        },
      ],
    },
    {
      value: '2',
      name: 'Item 2',
      children: [
        {
          value: '2.1',
          name: 'Item 2.1',
        },
        {
          value: '2.2',
          name: 'Item 2.2',
        },
      ],
    },
    {
      value: '3',
      name: 'Item 3',
    },
  ],
}
Usage
import { TreeView } from '~/components/ui/tree-view'Installation
npx @park-ui/cli components add tree-view1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/tree-view.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { TreeView } from '@ark-ui/react/tree-view'
import { type TreeViewVariantProps, treeView } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(treeView)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
  HTMLDivElement,
  Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootProviderBaseProps>, TreeViewVariantProps>
>(TreeView.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
  HTMLDivElement,
  Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootBaseProps>, TreeViewVariantProps>
>(TreeView.Root, 'root')
export const BranchContent = withContext<
  HTMLUListElement,
  Assign<HTMLStyledProps<'ul'>, TreeView.BranchContentBaseProps>
>(TreeView.BranchContent, 'branchContent')
export const BranchControl = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, TreeView.BranchControlBaseProps>
>(TreeView.BranchControl, 'branchControl')
export const BranchIndicator = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, TreeView.BranchIndicatorBaseProps>
>(TreeView.BranchIndicator, 'branchIndicator')
export const Branch = withContext<
  HTMLLIElement,
  Assign<HTMLStyledProps<'li'>, TreeView.BranchBaseProps>
>(TreeView.Branch, 'branch')
export const BranchText = withContext<
  HTMLSpanElement,
  Assign<HTMLStyledProps<'span'>, TreeView.BranchTextBaseProps>
>(TreeView.BranchText, 'branchText')
export const BranchTrigger = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, TreeView.BranchTriggerBaseProps>
>(TreeView.BranchTrigger, 'branchTrigger')
export const ItemIndicator = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, TreeView.ItemIndicatorBaseProps>
>(TreeView.ItemIndicator, 'itemIndicator')
export const Item = withContext<
  HTMLLIElement,
  Assign<HTMLStyledProps<'li'>, TreeView.ItemBaseProps>
>(TreeView.Item, 'item')
export const ItemText = withContext<
  HTMLSpanElement,
  Assign<HTMLStyledProps<'span'>, TreeView.ItemTextBaseProps>
>(TreeView.ItemText, 'itemText')
export const Label = withContext<
  HTMLLabelElement,
  Assign<HTMLStyledProps<'label'>, TreeView.LabelBaseProps>
>(TreeView.Label, 'label')
export const Tree = withContext<
  HTMLUListElement,
  Assign<HTMLStyledProps<'ul'>, TreeView.TreeBaseProps>
>(TreeView.Tree, 'tree')
export { TreeViewContext as Context } from '@ark-ui/react/tree-view'
import { type Assign, TreeView } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type TreeViewVariantProps, treeView } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(treeView)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
  Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootProviderBaseProps>, TreeViewVariantProps>
>(TreeView.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
  Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootBaseProps>, TreeViewVariantProps>
>(TreeView.Root, 'root')
export const BranchContent = withContext<
  Assign<HTMLStyledProps<'ul'>, TreeView.BranchContentBaseProps>
>(TreeView.BranchContent, 'branchContent')
export const BranchControl = withContext<
  Assign<HTMLStyledProps<'div'>, TreeView.BranchControlBaseProps>
>(TreeView.BranchControl, 'branchControl')
export const BranchIndicator = withContext<
  Assign<HTMLStyledProps<'div'>, TreeView.BranchIndicatorBaseProps>
>(TreeView.BranchIndicator, 'branchIndicator')
export const Branch = withContext<Assign<HTMLStyledProps<'li'>, TreeView.BranchBaseProps>>(
  TreeView.Branch,
  'branch',
)
export const BranchText = withContext<
  Assign<HTMLStyledProps<'span'>, TreeView.BranchTextBaseProps>
>(TreeView.BranchText, 'branchText')
export const BranchTrigger = withContext<
  Assign<HTMLStyledProps<'div'>, TreeView.BranchTriggerBaseProps>
>(TreeView.BranchTrigger, 'branchTrigger')
export const ItemIndicator = withContext<
  Assign<HTMLStyledProps<'div'>, TreeView.ItemIndicatorBaseProps>
>(TreeView.ItemIndicator, 'itemIndicator')
export const Item = withContext<Assign<HTMLStyledProps<'li'>, TreeView.ItemBaseProps>>(
  TreeView.Item,
  'item',
)
export const ItemText = withContext<Assign<HTMLStyledProps<'span'>, TreeView.ItemTextBaseProps>>(
  TreeView.ItemText,
  'itemText',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, TreeView.LabelBaseProps>>(
  TreeView.Label,
  'label',
)
export const Tree = withContext<Assign<HTMLStyledProps<'ul'>, TreeView.TreeBaseProps>>(
  TreeView.Tree,
  'tree',
)
export { TreeViewContext as Context } from '@ark-ui/solid'
No snippet found2
Add Composition
Copy the composition snippet below into ~/components/ui/tree-view.tsx
'use client'
import { forwardRef } from 'react'
import * as StyledTreeView from './styled/tree-view'
interface Child {
  value: string
  name: string
  children?: Child[]
}
export interface TreeViewData {
  label: string
  children: Child[]
}
export interface TreeViewProps extends StyledTreeView.RootProps {
  data: TreeViewData
}
export const TreeView = forwardRef<HTMLDivElement, TreeViewProps>((props, ref) => {
  const { data, ...rootProps } = props
  const renderChild = (child: Child) => (
    <StyledTreeView.Branch key={child.value} value={child.value}>
      <StyledTreeView.BranchControl>
        <StyledTreeView.BranchIndicator>
          <ChevronRightIcon />
        </StyledTreeView.BranchIndicator>
        <StyledTreeView.BranchText>{child.name}</StyledTreeView.BranchText>
      </StyledTreeView.BranchControl>
      <StyledTreeView.BranchContent>
        {child.children?.map((child) =>
          child.children ? (
            renderChild(child)
          ) : (
            <StyledTreeView.Item key={child.value} value={child.value}>
              <StyledTreeView.ItemText>{child.name}</StyledTreeView.ItemText>
            </StyledTreeView.Item>
          ),
        )}
      </StyledTreeView.BranchContent>
    </StyledTreeView.Branch>
  )
  return (
    <StyledTreeView.Root ref={ref} aria-label={data.label} {...rootProps}>
      <StyledTreeView.Tree>{data.children.map(renderChild)}</StyledTreeView.Tree>
    </StyledTreeView.Root>
  )
})
TreeView.displayName = 'TreeView'
const ChevronRightIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <title>Chevron Right Icon</title>
    <path
      fill="none"
      stroke="currentColor"
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth="2"
      d="m9 18l6-6l-6-6"
    />
  </svg>
)
import { For, Show, splitProps } from 'solid-js'
import * as StyledTreeView from './styled/tree-view'
interface Child {
  value: string
  name: string
  children?: Child[]
}
export interface TreeViewData {
  label: string
  children: Child[]
}
export interface TreeViewProps extends StyledTreeView.RootProps {
  data: TreeViewData
}
export const TreeView = (props: TreeViewProps) => {
  const [localProps, rootProps] = splitProps(props, ['data'])
  const renderChild = (child: Child) => (
    <Show
      when={child.children}
      fallback={
        <StyledTreeView.Item value={child.value}>
          <StyledTreeView.ItemText>{child.name}</StyledTreeView.ItemText>
        </StyledTreeView.Item>
      }
    >
      <StyledTreeView.Branch value={child.value}>
        <StyledTreeView.BranchControl>
          <StyledTreeView.BranchIndicator>
            <ChevronRightIcon />
          </StyledTreeView.BranchIndicator>
          <StyledTreeView.BranchText>{child.name}</StyledTreeView.BranchText>
        </StyledTreeView.BranchControl>
        <StyledTreeView.BranchContent>
          <For each={child.children}>{(child) => renderChild(child)}</For>
        </StyledTreeView.BranchContent>
      </StyledTreeView.Branch>
    </Show>
  )
  return (
    <StyledTreeView.Root aria-label={localProps.data.label} {...rootProps}>
      <StyledTreeView.Tree>
        <For each={localProps.data.children}>{(child) => renderChild(child)}</For>
      </StyledTreeView.Tree>
    </StyledTreeView.Root>
  )
}
const ChevronRightIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <title>Chevron Right Icon</title>
    <path
      fill="none"
      stroke="currentColor"
      stroke-linecap="round"
      stroke-linejoin="round"
      stroke-width="2"
      d="m9 18l6-6l-6-6"
    />
  </svg>
)
3
Integrate Recipe
If you're not using @park-ui/preset, add the following recipe to yourpanda.config.ts:
import { treeViewAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const treeView = defineSlotRecipe({
  className: 'treeView',
  slots: treeViewAnatomy.keys(),
  base: {
    root: {
      width: 'full',
    },
    branch: {
      "&[data-depth='1'] > [data-part='branch-content']": {
        _before: {
          bg: 'border.default',
          content: '""',
          height: 'full',
          left: '3',
          position: 'absolute',
          width: '1px',
          zIndex: '1',
        },
      },
    },
    branchContent: {
      position: 'relative',
    },
    branchControl: {
      alignItems: 'center',
      borderRadius: 'l2',
      color: 'fg.muted',
      display: 'flex',
      fontWeight: 'medium',
      gap: '1.5',
      ps: 'calc((var(--depth) - 1) * 22px)',
      py: '1.5',
      textStyle: 'sm',
      transitionDuration: 'normal',
      transitionProperty: 'background, color',
      transitionTimingFunction: 'default',
      "&[data-depth='1']": {
        ps: '1',
      },
      "&[data-depth='1'] > [data-part='branch-text'] ": {
        fontWeight: 'semibold',
        color: 'fg.default',
      },
      _hover: {
        background: 'gray.a2',
        color: 'fg.default',
      },
    },
    branchIndicator: {
      color: 'accent.default',
      transformOrigin: 'center',
      transitionDuration: 'normal',
      transitionProperty: 'transform',
      transitionTimingFunction: 'default',
      '& svg': {
        fontSize: 'md',
        width: '4',
        height: '4',
      },
      _open: {
        transform: 'rotate(90deg)',
      },
    },
    item: {
      borderRadius: 'l2',
      color: 'fg.muted',
      cursor: 'pointer',
      fontWeight: 'medium',
      position: 'relative',
      ps: 'calc(((var(--depth) - 1) * 22px) + 22px)',
      py: '1.5',
      textStyle: 'sm',
      transitionDuration: 'normal',
      transitionProperty: 'background, color',
      transitionTimingFunction: 'default',
      "&[data-depth='1']": {
        ps: '6',
        fontWeight: 'semibold',
        color: 'fg.default',
        _selected: {
          _before: {
            bg: 'transparent',
          },
        },
      },
      _hover: {
        background: 'gray.a2',
        color: 'fg.default',
      },
      _selected: {
        background: 'accent.a2',
        color: 'accent.text',
        _hover: {
          background: 'accent.a2',
          color: 'accent.text',
        },
        _before: {
          content: '""',
          position: 'absolute',
          left: '3',
          top: '0',
          width: '2px',
          height: 'full',
          bg: 'accent.default',
          zIndex: '1',
        },
      },
    },
    tree: {
      display: 'flex',
      flexDirection: 'column',
      gap: '3',
    },
  },
})