# Nested collapsible list with actions

> Nested collapsible list with actions

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-collapsible-7).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-collapsible-7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-collapsible-7-light-2c9bbc53682b3139.png
- Registry item: https://reui.io/r/new-york-v4/c-collapsible-7.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-collapsible-7.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `button`, `collapsible`, `dropdown-menu`, `@reui/frame`, `@reui/use-copy-to-clipboard`

## Files

### `c-collapsible-7.tsx`

```tsx
"use client"

import { useState } from "react"
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import {
  Frame,
  FrameHeader,
  FramePanel,
  FrameTitle,
} from "@/components/reui/frame"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"

const initialKeys = [
  { id: "1", name: "Production", key: "AUDO230454*242SDIFPPL" },
  { id: "2", name: "Development", key: "DUILO30454*242SDIFUIP" },
  { id: "3", name: "Staging", key: "IPPODAS230454*242SDI" },
]

export function Pattern() {
  const [keys, setKeys] = useState(initialKeys)

  return (
    <div className="h-54 w-full max-w-xs">
      <Frame stacked dense spacing="sm">
        <Collapsible defaultOpen>
          <CollapsibleTrigger className="flex w-full">
            <FrameHeader className="flex grow flex-row items-center justify-between gap-1.5 py-1!">
              <IconPlaceholder
                lucide="ChevronRightIcon"
                tabler="IconChevronRight"
                hugeicons="ArrowRight01Icon"
                phosphor="CaretRightIcon"
                remixicon="RiArrowRightSLine"
                aria-hidden="true"
                className="text-muted-foreground size-4 transition-transform in-data-open:rotate-90"
              />
              <FrameTitle className="text-sm font-medium">API Keys</FrameTitle>
              <Button
                variant="ghost"
                size="icon-sm"
                className="hover:border-border ml-auto"
              >
                <IconPlaceholder
                  lucide="PlusIcon"
                  tabler="IconPlus"
                  hugeicons="PlusSignIcon"
                  phosphor="PlusIcon"
                  remixicon="RiAddLine"
                  aria-hidden="true"
                />
              </Button>
            </FrameHeader>
          </CollapsibleTrigger>
          <CollapsibleContent>
            <FramePanel>
              <div className="flex flex-col gap-2.5">
                {keys.map((item) => (
                  <ApiKeyItem
                    key={item.id}
                    item={item}
                    onDelete={(id) =>
                      setKeys((k) => k.filter((i) => i.id !== id))
                    }
                  />
                ))}
              </div>
            </FramePanel>
          </CollapsibleContent>
        </Collapsible>
      </Frame>
    </div>
  )
}

function ApiKeyItem({
  item,
  onDelete,
}: {
  item: (typeof initialKeys)[0]
  onDelete: (id: string) => void
}) {
  const { isCopied, copyToClipboard } = useCopyToClipboard()

  return (
    <div className="flex items-center justify-between gap-3.5">
      <div className="flex items-center gap-3">
        <div className="flex items-center gap-1.5">
          <div className="bg-muted border-border/60 flex size-5.5 shrink-0 items-center justify-center rounded-sm border-2">
            <IconPlaceholder
              lucide="LockIcon"
              tabler="IconLock"
              hugeicons="SquareLock01Icon"
              phosphor="LockSimpleIcon"
              remixicon="RiLockLine"
              aria-hidden="true"
              className="size-3.25 text-emerald-600"
            />
          </div>
          <div className="w-10 truncate text-xs">{item.name}</div>
        </div>
        <div className="bg-muted w-40 truncate rounded-md px-2 py-1 text-xs">
          {item.key}
        </div>
      </div>
      <div className="flex shrink-0 items-center">
        <DropdownMenu>
          <DropdownMenuTrigger
            render={
              <Button variant="ghost" size="icon-xs">
                <IconPlaceholder
                  lucide="MoreHorizontalIcon"
                  tabler="IconDots"
                  hugeicons="MoreHorizontalCircle01Icon"
                  phosphor="DotsThreeIcon"
                  remixicon="RiMoreLine"
                  aria-hidden="true"
                />
              </Button>
            }
          />
          <DropdownMenuContent align="start" className="min-w-32">
            <DropdownMenuItem onClick={() => copyToClipboard(item.key)}>
              {isCopied ? (
                <IconPlaceholder
                  lucide="CheckIcon"
                  tabler="IconCheck"
                  hugeicons="Tick02Icon"
                  phosphor="CheckIcon"
                  remixicon="RiCheckLine"
                  aria-hidden="true"
                  className="text-green-500"
                />
              ) : (
                <IconPlaceholder
                  lucide="CopyIcon"
                  tabler="IconCopy"
                  hugeicons="CopyIcon"
                  phosphor="CopyIcon"
                  remixicon="RiFileCopyLine"
                  aria-hidden="true"
                />
              )}
              <span>{isCopied ? "Copied" : "Copy key"}</span>
            </DropdownMenuItem>
            <DropdownMenuItem
              variant="destructive"
              onClick={() => onDelete(item.id)}
            >
              <IconPlaceholder
                lucide="TrashIcon"
                tabler="IconTrash"
                hugeicons="DeleteIcon"
                phosphor="TrashIcon"
                remixicon="RiDeleteBinLine"
                aria-hidden="true"
              />
              <span>Delete</span>
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </div>
    </div>
  )
}
```
