# Table with Accordion

> A table with accordion block.

A shadcn/ui block from the [@blocks-so](https://blocks.so) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/blocks-so/table-01).

- Page, with a live preview: https://shadcn.exchange/r/blocks-so/table-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/blocks-so/table-01-light-e0480f146aede1b3.png
- Registry item: https://blocks.so/r/table-01.json
- Categories: tables
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://blocks.so/r/table-01.json
```

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

## Dependencies

- npm packages: `lucide-react`
- Registry items: `button`, `collapsible`, `table`

## Files

### `content/components/tables/table-01.tsx`

```tsx
'use client';

import { ChevronDown, ChevronRight } from 'lucide-react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import { cn } from '@/lib/utils';

interface TableRowData {
  id: string;
  name: string;
  category: string;
  value: number;
  date: string;
  children?: TableRowData[];
}

const sampleData: TableRowData[] = [
  {
    id: '001',
    name: 'Project Alpha',
    category: 'Development',
    value: 45_000,
    date: '2024-01-15',
    children: [
      {
        id: '001-01',
        name: 'Frontend Module',
        category: 'Development',
        value: 15_000,
        date: '2024-01-16',
      },
      {
        id: '001-02',
        name: 'Backend Module',
        category: 'Development',
        value: 20_000,
        date: '2024-01-21',
      },
      {
        id: '001-03',
        name: 'Testing Suite',
        category: 'Development',
        value: 10_000,
        date: '2024-01-24',
      },
    ],
  },
  {
    id: '002',
    name: 'Marketing Campaign',
    category: 'Marketing',
    value: 28_500,
    date: '2024-01-18',
    children: [
      {
        id: '002-01',
        name: 'Social Media',
        category: 'Marketing',
        value: 12_000,
        date: '2024-01-19',
      },
      {
        id: '002-02',
        name: 'Email Marketing',
        category: 'Marketing',
        value: 8500,
        date: '2024-01-22',
      },
      {
        id: '002-03',
        name: 'SEO Optimization',
        category: 'Marketing',
        value: 8000,
        date: '2024-01-23',
      },
    ],
  },
  {
    id: '003',
    name: 'Infrastructure Upgrade',
    category: 'Operations',
    value: 67_200,
    date: '2024-01-20',
    children: [
      {
        id: '003-01',
        name: 'Cloud Migration',
        category: 'Operations',
        value: 35_000,
        date: '2024-01-21',
      },
      {
        id: '003-02',
        name: 'Security Enhancement',
        category: 'Operations',
        value: 32_200,
        date: '2024-01-24',
      },
    ],
  },
  {
    id: '004',
    name: 'Customer Support',
    category: 'Service',
    value: 19_800,
    date: '2024-01-25',
  },
];

interface AccordionRowProps {
  row: TableRowData;
  defaultOpen?: boolean;
}

function AccordionRow({ row, defaultOpen = false }: AccordionRowProps) {
  const [isOpen, setIsOpen] = useState(defaultOpen);
  const hasChildren = row.children && row.children.length > 0;

  return (
    <Collapsible
      onOpenChange={setIsOpen}
      open={isOpen}
      render={
        <TableBody className="[&_tr:last-child]:border-b last:[&_tr:last-child]:border-0" />
      }
    >
      <TableRow
        className={cn(
          'grid grid-cols-[40px_80px_180px_110px_100px_110px] bg-muted/50 hover:bg-muted/50',
          isOpen && 'border-b-0'
        )}
      >
        <TableCell className="p-0">
          <Button
            aria-label={isOpen ? 'Collapse row' : 'Expand row'}
            className={cn(
              'h-full w-full rounded-none p-3 text-muted-foreground transition-colors',
              hasChildren && 'hover:bg-transparent hover:text-foreground',
              !hasChildren && 'cursor-default opacity-30'
            )}
            disabled={!hasChildren}
            onClick={() => setIsOpen(!isOpen)}
            size="icon"
            variant="ghost"
          >
            {hasChildren ? (
              isOpen ? (
                <ChevronDown className="h-4 w-4 transition-transform duration-200" />
              ) : (
                <ChevronRight className="h-4 w-4 transition-transform duration-200" />
              )
            ) : (
              <div className="h-4 w-4" />
            )}
          </Button>
        </TableCell>
        <TableCell className="p-3 font-medium font-mono text-muted-foreground text-sm">
          {row.id}
        </TableCell>
        <TableCell className="p-3 font-medium text-sm">{row.name}</TableCell>
        <TableCell className="p-3 text-muted-foreground text-sm">
          {row.category}
        </TableCell>
        <TableCell className="p-3 text-right font-mono font-semibold text-sm tabular-nums">
          ${row.value.toLocaleString()}
        </TableCell>
        <TableCell className="p-3 text-muted-foreground text-sm">
          {row.date}
        </TableCell>
      </TableRow>

      {hasChildren && (
        <TableRow className="grid grid-cols-[40px_80px_180px_110px_100px_110px] border-b-0 hover:bg-transparent">
          <TableCell className="col-span-6 p-0" colSpan={6}>
            <CollapsibleContent>
              <div className="w-full border-border border-b bg-muted/20">
                <Table>
                  <TableHeader>
                    <TableRow className="grid grid-cols-[40px_80px_180px_110px_100px_110px] border-b-0 bg-muted/30">
                      <TableHead className="flex h-7 items-center border-border border-y px-3 py-1.5" />
                      <TableHead className="flex h-7 items-center border-border border-y px-3 py-1.5 text-xs">
                        ID
                      </TableHead>
                      <TableHead className="flex h-7 items-center border-border border-y px-3 py-1.5 text-xs">
                        Name
                      </TableHead>
                      <TableHead className="flex h-7 items-center border-border border-y px-3 py-1.5 text-xs">
                        Category
                      </TableHead>
                      <TableHead className="flex h-7 items-center justify-end border-border border-y px-3 py-1.5 text-right text-xs">
                        Value
                      </TableHead>
                      <TableHead className="flex h-7 items-center border-border border-y px-3 py-1.5 text-xs">
                        Date
                      </TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {row.children?.map((childRow) => (
                      <TableRow
                        className="grid grid-cols-[40px_80px_180px_110px_100px_110px]"
                        key={childRow.id}
                      >
                        <TableCell className="px-3 py-2" />
                        <TableCell className="px-3 py-2 font-mono text-muted-foreground text-xs tabular-nums">
                          {childRow.id}
                        </TableCell>
                        <TableCell className="px-3 py-2 font-medium text-xs">
                          {childRow.name}
                        </TableCell>
                        <TableCell className="px-3 py-2 text-muted-foreground text-xs">
                          {childRow.category}
                        </TableCell>
                        <TableCell className="px-3 py-2 text-right font-mono font-semibold text-xs tabular-nums">
                          ${childRow.value.toLocaleString()}
                        </TableCell>
                        <TableCell className="px-3 py-2 text-muted-foreground text-xs">
                          {childRow.date}
                        </TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </div>
            </CollapsibleContent>
          </TableCell>
        </TableRow>
      )}
    </Collapsible>
  );
}

export default function Table01() {
  return (
    <div className="max-w-fit overflow-hidden rounded-lg border border-border bg-card shadow-sm">
      <div className="overflow-x-auto">
        <Table>
          <TableHeader>
            <TableRow className="grid grid-cols-[40px_80px_180px_110px_100px_110px] bg-muted/50">
              <TableHead className="p-3" />
              <TableHead className="p-3 font-semibold text-foreground text-sm">
                ID
              </TableHead>
              <TableHead className="p-3 font-semibold text-foreground text-sm">
                Name
              </TableHead>
              <TableHead className="p-3 font-semibold text-foreground text-sm">
                Category
              </TableHead>
              <TableHead className="p-3 text-right font-semibold text-foreground text-sm">
                Value
              </TableHead>
              <TableHead className="p-3 font-semibold text-foreground text-sm">
                Date
              </TableHead>
            </TableRow>
          </TableHeader>
          {sampleData.map((row, index) => (
            <AccordionRow defaultOpen={index === 0} key={row.id} row={row} />
          ))}
        </Table>
      </div>
    </div>
  );
}
```
