# Split Panel (`split-panel`) - SignalOS widget

> Two-panel layout with a scrollable navigation list and a main content panel. Includes list item, list header, and empty-detail helper components.

- **Version:** 0.3.5
- **Kind:** widget · **Category:** layout
- **Install:** `npx shadcn@latest add @signalos/split-panel`
- **Registry dependencies (pulled automatically):** @signalos/tokens, @signalos/utils, @signalos/tooltip
- **npm dependencies:** none
- **Files installed:** `src/components/widgets/split-panel/SplitPanel.tsx`, `src/components/widgets/split-panel/SplitPanelListItem.tsx`, `src/components/widgets/split-panel/SplitPanelListHeader.tsx`, `src/components/widgets/split-panel/SplitPanelEmptyDetail.tsx`, `src/components/widgets/split-panel/SplitPanel.types.ts`

## Access

This is a private registry: pulling source requires a `SIGNALOS_REGISTRY_TOKEN`
(GitHub fine-grained PAT with read access to the signal-widgets repo) and an
`@signalos` entry in components.json `"registries"`. Previews and this document are public.

## Usage

```tsx
import { SplitPanel, SplitPanelEmptyDetail, SplitPanelListHeader, SplitPanelListItem } from "@/components/widgets/split-panel/SplitPanel"
```

## Example

```tsx
import { useState } from "react"

import {
  SplitPanel,
  SplitPanelEmptyDetail,
  SplitPanelListHeader,
  SplitPanelListItem,
} from "./SplitPanel"

const contacts = [
  { id: "1", name: "Ada Lovelace", subtitle: "Engineering" },
  { id: "2", name: "Grace Hopper", subtitle: "Engineering" },
  { id: "3", name: "Margaret Hamilton", subtitle: "Product" },
]

export default function Example() {
  const [activeId, setActiveId] = useState<string | null>(null)
  const active = contacts.find((c) => c.id === activeId)

  return (
    <div className="h-96">
      <SplitPanel
        listWidth="w-72"
        list={
          <>
            <SplitPanelListHeader title="Contacts" />

            {contacts.map((contact) => (
              <SplitPanelListItem
                key={contact.id}
                title={contact.name}
                subtitle={contact.subtitle}
                active={contact.id === activeId}
                onClick={() => setActiveId(contact.id)}
              />
            ))}
          </>
        }
        detail={
          active ? (
            <div className="p-6">
              <h2 className="text-lg font-semibold">{active.name}</h2>
              <p className="text-sm text-muted-foreground">{active.subtitle}</p>
            </div>
          ) : (
            <SplitPanelEmptyDetail />
          )
        }
      />
    </div>
  )
}
```

## Props

### `SplitPanelListItemProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `ReactNode` | yes | - | Primary label for the item. |
| `subtitle` | `ReactNode` | no | - | Secondary line under the title. |
| `icon` | `ReactNode` | no | - | Leading icon or avatar. |
| `badge` | `ReactNode` | no | - | Trailing badge/status indicator. |
| `active` | `boolean \| undefined` | no | - | Marks the item as the currently selected one. |
| `onClick` | `(() => void) \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - | Test identifier rendered as `data-testid` on the root element. |

### `SplitPanelListHeaderProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `ReactNode` | no | - | Header title. |
| `actions` | `ReactNode` | no | - | Trailing actions (buttons, icons, etc.). |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - | Test identifier rendered as `data-testid` on the root element. |

### `SplitPanelEmptyDetailProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `message` | `ReactNode` | no | - | Message shown when no item is selected. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - | Test identifier rendered as `data-testid` on the root element. |

### `SplitPanelProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `list` | `ReactNode` | yes | - | Left panel content. |
| `detail` | `ReactNode` | yes | - | Main panel content. |
| `listWidth` | `string \| undefined` | no | - | Width utility class for the left panel.  Example: "w-64" "w-72" "w-80" |
| `className` | `string \| undefined` | no | - | Additional classes. |
| `data-testid` | `string \| undefined` | no | - | Test identifier rendered as `data-testid` on the root element. |

## Changelog

## 0.3.5

- Content updates.

## 0.3.3

- Content updates.

# split-panel

## 0.3.1

- **Breaking:** rename the `testId` prop to `data-testid` to match the rest
  of the registry and the DOM attribute name.

## 0.2.3

- Format source with the repo prettier config (no behavior change).

## 0.2.2

- Content sync fix to bring source in line with published registry payload.
- Flattened `components/` subfolder into the widget root (`SplitPanelListItem`, `SplitPanelListHeader`, `SplitPanelEmptyDetail`).
- Fixed manifest: corrected description, dropped unused `lucide-react`/`@signalos/button`/`@signalos/tooltip` dependencies, fixed types file path (`.ts`, not `.tsx`), listed all shipped files, category `layout`.
- Rewrote `.example.tsx`, `.preview.tsx`, and `.test.tsx` to exercise `SplitPanel` (previously copied from `icon-button`).

## 0.1.0

- Initial release.
