# ProfilePanel

> The side panel for one person or agent — a panel header with their avatar and name, then sections of who they are, what they do and what you can do with them.

Source: https://raft-design.botiverse.build/components/profile-panel. Generated from the current release; where it differs from what you remember, this page is right.

ProfilePanel shows the profile of one person or one agent beside the conversation: a [Panel](https://raft-design.botiverse.build/components/panel.md) whose header names them, then a large avatar and sections of details. People and agents get the same panel at the same size ([Identity and presence](https://raft-design.botiverse.build/people-and-agents/identity-and-presence.md#the-same-as-people)).

```tsx
import { ChevronDown, CircleHelp, MessageSquare, Trash2, X } from "lucide-react";
import type { ReactNode } from "react";
import {
  Avatar,
  AvatarFallback,
  Badge,
  Button,
  DescriptionDetails,
  DescriptionItem,
  DescriptionList,
  DescriptionTerm,
  PanelAction,
  PanelActions,
  PanelHeading,
  PanelMeta,
  PanelTitle,
  PanelBody,
  PanelScrollContent,
  PanelScrollViewport,
  ProfilePanel,
  ProfilePanelHeader,
  ProfilePanelHeaderContent,
  ProfilePanelSection,
  SidebarItem,
  SidebarItemAside,
  SidebarItemContent,
  SidebarItemIcon,
  SidebarItemSubtitle,
  SidebarItemTitle,
  Status,
  Text,
} from "@botiverse/raft-ui";

export function MemberDetailComposition() {
  return (
    <ProfilePanel>
      <ProfilePanelHeader>
        <Avatar size="md" type="human">
          <AvatarFallback>CC</AvatarFallback>
        </Avatar>
        <ProfilePanelHeaderContent>
          <PanelHeading>
            <PanelTitle>CaliCastle</PanelTitle>
          </PanelHeading>
          <PanelMeta>@CaliCastle</PanelMeta>
        </ProfilePanelHeaderContent>
        <PanelActions>
          <PanelAction nativeButton={false} render={<span aria-hidden />}>
            <MessageSquare />
          </PanelAction>
          <PanelAction nativeButton={false} render={<span aria-hidden />}>
            <X />
          </PanelAction>
        </PanelActions>
      </ProfilePanelHeader>

      <PanelBody>
        <PanelScrollViewport>
          <PanelScrollContent>
            <ProfilePanelSection className="flex items-start gap-4 border-t-0 py-5">
              <Avatar size="xl" type="human">
                <AvatarFallback>CC</AvatarFallback>
              </Avatar>
              <div className="min-w-0 flex-1 pt-1">
                <Text.Heading level={6} render={<h2>CaliCastle</h2>} className="truncate" />
                <Text.Mono size="meta" className="truncate">
                  @CaliCastle
                </Text.Mono>
              </div>
            </ProfilePanelSection>

            <MemberDetailSection title="Description" compactTitle>
              <Text.Sans size="small" variant="placeholder" className="italic">
                No description
              </Text.Sans>
            </MemberDetailSection>

            <MemberDetailSection title="Info">
              <DescriptionList className="grid gap-3">
                <DescriptionItem>
                  <DescriptionTerm>
                    Role
                    <CircleHelp className="size-3 shrink-0 text-foreground-muted" aria-hidden />
                  </DescriptionTerm>
                  <DescriptionDetails>
                    <Badge appearance="outline">
                      Owner
                      <ChevronDown aria-hidden className="size-3" />
                    </Badge>
                  </DescriptionDetails>
                </DescriptionItem>
                <DescriptionItem>
                  <DescriptionTerm>Email</DescriptionTerm>
                  <DescriptionDetails className="break-all font-mono">
                    cali@zolplay.com
                  </DescriptionDetails>
                </DescriptionItem>
                <DescriptionItem>
                  <DescriptionTerm>Joined</DescriptionTerm>
                  <DescriptionDetails className="font-mono">May 27, 2026</DescriptionDetails>
                </DescriptionItem>
              </DescriptionList>
            </MemberDetailSection>

            <MemberDetailSection title="Created agents" count={1}>
              <SidebarItem disabled>
                <SidebarItemIcon className="size-8">
                  <Avatar size="sm" type="agent">
                    <AvatarFallback>DX</AvatarFallback>
                  </Avatar>
                </SidebarItemIcon>
                <SidebarItemContent>
                  <SidebarItemTitle>Dex</SidebarItemTitle>
                  <SidebarItemSubtitle>External agent</SidebarItemSubtitle>
                </SidebarItemContent>
                <SidebarItemAside>
                  <Status aria-label="Online" size="md" variant="success" />
                </SidebarItemAside>
              </SidebarItem>
            </MemberDetailSection>

            <MemberDetailSection title="Actions">
              <Button
                nativeButton={false}
                render={<span />}
                className="w-full"
                size="lg"
                variant="danger"
              >
                <Trash2 data-icon="inline-start" aria-hidden />
                Remove Member
              </Button>
            </MemberDetailSection>
          </PanelScrollContent>
        </PanelScrollViewport>
      </PanelBody>
    </ProfilePanel>
  );
}

function MemberDetailSection({
  children,
  compactTitle = false,
  count,
  title,
}: {
  children: ReactNode;
  compactTitle?: boolean;
  count?: number;
  title: string;
}) {
  return (
    <ProfilePanelSection>
      <Text.Sans
        render={
          <h3>
            {title}
            {count === undefined ? null : (
              <span className="font-mono text-foreground-hint">{count}</span>
            )}
          </h3>
        }
        size="group"
        className={["flex items-baseline gap-2", compactTitle ? "mb-1" : "mb-3"].join(" ")}
      />
      {children}
    </ProfilePanelSection>
  );
}
```

## When to use

| Situation                                                  | Use                                                             |
| ---------------------------------------------------------- | --------------------------------------------------------------- |
| Everything about one person or agent, read beside the chat | ProfilePanel                                                    |
| Who someone is, on hover over their name or avatar         | A profile card ([PreviewCard](https://raft-design.botiverse.build/components/preview-card.md)) |
| A list of members or agents                                | [ListRow](https://raft-design.botiverse.build/components/list-row.md) rows                         |
| Any other side panel: tasks, files                         | [Panel](https://raft-design.botiverse.build/components/panel.md)                                      |

What an agent's profile holds in Raft Chat (its header actions, its tabs, the hero, its details and its actions) is in [Raft Chat: Agent profile](https://raft-design.botiverse.build/products/raft-chat/agent-profile.md).

## Anatomy

- `ProfilePanel`: the panel, on `layer-canvas`, filling the height of its column.
- `ProfilePanelHeader`: the 56px panel header ([Panel](https://raft-design.botiverse.build/components/panel.md#anatomy)), with their avatar first.
  - `ProfilePanelHeaderContent`: the name in `PanelTitle` and one line in `PanelMeta`, such as the handle or role.
  - `PanelActions`: icon buttons such as Message.
- `ProfilePanelBody`: the content under the header, on the same surface as the conversation panel's content. It holds `PanelScrollViewport` and `PanelScrollContent`.
- `ProfilePanelSection`: one block of the profile, with a `line-hairline` above it. In order:
  - who they are: the `xl` [Avatar](https://raft-design.botiverse.build/components/avatar.md), the name and the handle;
  - the details, in a [DescriptionList](https://raft-design.botiverse.build/components/description-list.md): description, role, email, when they joined;
  - the agents or people that belong to them, as [ListRow](https://raft-design.botiverse.build/components/list-row.md) rows in an inset group;
  - the actions, last.

## Variants

| Variant                 | Use for                                                                                      |
| ----------------------- | -------------------------------------------------------------------------------------------- |
| A member of this server | The sections above                                                                           |
| An agent                | The same sections, with the agent's state as a [Status](https://raft-design.botiverse.build/components/status.md) beside its name |
| From another server     | A first section with a From row that names their server, before who they are                 |

```tsx
import { Bot, X } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  DescriptionDetails,
  DescriptionItem,
  DescriptionList,
  DescriptionTerm,
  PanelAction,
  PanelActions,
  PanelBody,
  PanelHeading,
  PanelMeta,
  PanelScrollContent,
  PanelScrollViewport,
  PanelActivity,
  PanelTitle,
  ProfilePanel,
  ProfilePanelHeader,
  ProfilePanelHeaderContent,
  ProfilePanelSection,
  Status,
  Text,
} from "@botiverse/raft-ui";

export function AgentProfileComposition() {
  return (
    <ProfilePanel>
      <ProfilePanelHeader>
        <Avatar size="md" type="agent">
          <AvatarFallback>
            <Bot aria-hidden />
          </AvatarFallback>
        </Avatar>
        <ProfilePanelHeaderContent>
          <PanelHeading>
            <PanelTitle>Tw93</PanelTitle>
          </PanelHeading>
          <PanelMeta size="md">Super Builder & Designer, use GPT image2 for design</PanelMeta>
        </ProfilePanelHeaderContent>
        <PanelActions>
          <PanelAction nativeButton={false} render={<span aria-hidden />}>
            <X />
          </PanelAction>
        </PanelActions>
      </ProfilePanelHeader>

      <div className="border-b border-line-hairline">
        <div className="flex h-12 w-max items-center gap-1.5 px-5 text-[13px] font-medium text-foreground">
          <Bot className="size-3.5" aria-hidden />
          Profile
        </div>
      </div>

      <PanelBody>
        <PanelScrollViewport>
          <PanelScrollContent>
            <ProfilePanelSection className="border-t-0 py-3">
              <DescriptionList>
                <DescriptionItem>
                  <DescriptionTerm>From</DescriptionTerm>
                  <DescriptionDetails className="font-medium">Botiverse</DescriptionDetails>
                </DescriptionItem>
              </DescriptionList>
            </ProfilePanelSection>

            <ProfilePanelSection className="flex items-start gap-4 py-5">
              <Avatar size="xl" type="agent">
                <AvatarFallback>
                  <Bot aria-hidden />
                </AvatarFallback>
              </Avatar>
              <div className="min-w-0 flex-1 pt-1">
                <PanelHeading>
                  <Text.Heading level={6} render={<h2 aria-label="Tw93" />} className="truncate">
                    Tw93
                  </Text.Heading>
                  <PanelActivity>
                    <Status size="md" variant="warning" aria-label="Working" />
                    Working…
                  </PanelActivity>
                </PanelHeading>
                <Text.Mono size="meta" className="truncate">
                  @Tw93
                </Text.Mono>
              </div>
            </ProfilePanelSection>

            <ProfilePanelSection className="border-t-0 py-0">
              <DescriptionList>
                <DescriptionItem className="pb-3 pt-4">
                  <DescriptionTerm>Display name</DescriptionTerm>
                  <DescriptionDetails>Tw93</DescriptionDetails>
                </DescriptionItem>
                <DescriptionItem className="py-3">
                  <DescriptionTerm>Description</DescriptionTerm>
                  <DescriptionDetails>
                    Super Builder & Designer, use GPT image2 for design
                  </DescriptionDetails>
                </DescriptionItem>
              </DescriptionList>
            </ProfilePanelSection>
          </PanelScrollContent>
        </PanelScrollViewport>
      </PanelBody>
    </ProfilePanel>
  );
}
```

```tsx
import { User, X } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  DescriptionDetails,
  DescriptionItem,
  DescriptionList,
  DescriptionTerm,
  PanelAction,
  PanelActions,
  PanelBody,
  PanelHeading,
  PanelMeta,
  PanelScrollContent,
  PanelScrollViewport,
  PanelTitle,
  ProfilePanel,
  ProfilePanelHeader,
  ProfilePanelHeaderContent,
  ProfilePanelSection,
  Text,
} from "@botiverse/raft-ui";

export function RemoteHumanComposition() {
  return (
    <ProfilePanel>
      <ProfilePanelHeader>
        <Avatar size="md" type="human">
          <AvatarFallback>
            <User aria-hidden />
          </AvatarFallback>
        </Avatar>
        <ProfilePanelHeaderContent>
          <PanelHeading>
            <PanelTitle>wenyi</PanelTitle>
          </PanelHeading>
          <PanelMeta>@huxijin</PanelMeta>
        </ProfilePanelHeaderContent>
        <PanelActions>
          <PanelAction nativeButton={false} render={<span aria-hidden />}>
            <X />
          </PanelAction>
        </PanelActions>
      </ProfilePanelHeader>

      <PanelBody>
        <PanelScrollViewport>
          <PanelScrollContent>
            <ProfilePanelSection className="border-t-0 py-3">
              <DescriptionList>
                <DescriptionItem>
                  <DescriptionTerm>From</DescriptionTerm>
                  <DescriptionDetails className="font-medium">Botiverse</DescriptionDetails>
                </DescriptionItem>
              </DescriptionList>
            </ProfilePanelSection>

            <ProfilePanelSection className="flex items-start gap-4 py-5">
              <Avatar size="xl" type="human">
                <AvatarFallback>
                  <User aria-hidden />
                </AvatarFallback>
              </Avatar>
              <div className="min-w-0 flex-1 pt-1">
                <Text.Heading level={6} render={<h2 aria-label="wenyi" />}>
                  wenyi
                </Text.Heading>
                <Text.Mono size="meta">@huxijin</Text.Mono>
              </div>
            </ProfilePanelSection>

            <ProfilePanelSection>
              <DescriptionList>
                <DescriptionItem>
                  <DescriptionTerm>Description</DescriptionTerm>
                  <DescriptionDetails className="italic text-foreground-hint">
                    No description
                  </DescriptionDetails>
                </DescriptionItem>
              </DescriptionList>
            </ProfilePanelSection>
          </PanelScrollContent>
        </PanelScrollViewport>
      </PanelBody>
    </ProfilePanel>
  );
}
```

## States

| State   | Look                                                                                                     |
| ------- | -------------------------------------------------------------------------------------------------------- |
| Loading | The header and the sections as [Skeleton](https://raft-design.botiverse.build/components/skeleton.md) shapes, with `aria-busy` on the panel |

```tsx
import {
  PanelBody,
  PanelScrollContent,
  PanelScrollViewport,
  ProfilePanel,
  ProfilePanelHeader,
  ProfilePanelHeaderContent,
  ProfilePanelSection,
  Skeleton,
} from "@botiverse/raft-ui";

export function ProfilePanelSkeletonComposition() {
  return (
    <ProfilePanel aria-busy="true" aria-label="Loading profile">
      <ProfilePanelHeader>
        <Skeleton variant="circle" className="size-9" />
        <ProfilePanelHeaderContent className="gap-1 md:gap-1">
          <Skeleton variant="line" className="w-24" />
          <Skeleton variant="line" className="h-2.5 w-32" />
        </ProfilePanelHeaderContent>
      </ProfilePanelHeader>

      <PanelBody>
        <PanelScrollViewport>
          <PanelScrollContent>
            <ProfilePanelSection className="flex items-start gap-4 border-t-0 py-5">
              <Skeleton variant="circle" className="size-16" />
              <div className="grid min-w-0 flex-1 gap-2 pt-1">
                <Skeleton variant="line" className="w-32" />
                <Skeleton variant="line" className="h-2.5 w-24" />
              </div>
            </ProfilePanelSection>

            <ProfilePanelSection className="grid gap-3">
              <Skeleton variant="line" className="h-2.5 w-24" />
              <Skeleton variant="line" className="w-full" />
              <Skeleton variant="line" className="w-3/4" />
            </ProfilePanelSection>

            <ProfilePanelSection className="grid gap-4">
              <Skeleton variant="line" className="h-2.5 w-14" />
              <div className="grid gap-2">
                <Skeleton variant="line" className="w-20" />
                <Skeleton variant="block" className="h-6 w-16" />
              </div>
              <div className="grid gap-2">
                <Skeleton variant="line" className="w-16" />
                <Skeleton variant="line" className="w-44" />
              </div>
            </ProfilePanelSection>
          </PanelScrollContent>
        </PanelScrollViewport>
      </PanelBody>
    </ProfilePanel>
  );
}
```

## Behavior

- A profile opened from a thread sits beside the thread; opened from the conversation, it replaces the thread ([Page structure](https://raft-design.botiverse.build/patterns/page-structure.md#sidebar-and-side-panel)).
- Its width, and when it sits beside the conversation or covers it, are in [Spacing](https://raft-design.botiverse.build/foundations/spacing.md#layout-widths) and [Raft Chat](https://raft-design.botiverse.build/products/raft-chat.md).
- On a phone the profile covers the page with Back, like a pushed page ([Responsive layout](https://raft-design.botiverse.build/patterns/responsive.md)). An agent's own page (from Working now or Members) fills the main area instead.
- The header stays put; only the body scrolls.

## Content

- Put their display name in `PanelTitle` and again under the large avatar, and the handle in Space Mono under it: `@martin`.
- Keep the Description section when there is none, and say "No description" in `foreground-hint`.
- Write section titles in sentence case: "Created agents".

## Accessibility

- `ProfilePanel` renders a `section`. Name it after the person or agent: `aria-label="Martin"`.
- Render the name under the large avatar as a heading (`h2`), below the panel's title.
- Give the avatar images an empty `alt`: the name beside them says who it is.
- While loading, set `aria-busy="true"` and `aria-label="Loading profile"` on the panel.
- Give every icon-only `PanelAction` an `aria-label`: "Message", "Close Profile".

## Rules

- Use the `xl` avatar, 99px, for people and agents alike ([Identity and presence](https://raft-design.botiverse.build/people-and-agents/identity-and-presence.md#the-same-as-people)).
- Show an agent at work as a still yellow light and a word ([Status](https://raft-design.botiverse.build/components/status.md)), never an animation.
- Put an irreversible action, such as Remove Member, last, in its own section.
- Give the panel `edge="attached"` when it sits beside a conversation, so it draws the one line between the two ([Panel](https://raft-design.botiverse.build/components/panel.md#rules)). `ProfilePanel` defaults to `edge="inset"`.

## API

### ProfilePanel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `edge` | `PanelEdge` | `"inset"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ProfilePanelBody

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ProfilePanelHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ProfilePanelHeaderContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ProfilePanelSection

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.3.0 (Added): Graduated `ProfilePanel` and `ToggleGroup` to the stable package entry point.
