# FilesPanel

> The list of files shared in a place, one row per file with its type, name, size and time, which opens a preview and offers Jump to Message and Download.

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

The files panel lists the files shared in a place, such as #launch-week, newest first. Each row shows the file's type, its name, its size and when it was shared; clicking the row opens a preview, and two icon buttons jump to the message it came with and download it. It fills the Files card beside a conversation, or a full Files view.

```tsx
import { useCallback, useState } from "react";

import { FilesPanelComposition } from "./files-panel.composition.tsx";
import type { FileListEntry } from "./files-panel.composition.tsx";

const INITIAL_FILES: readonly FileListEntry[] = [
  {
    id: "raft-ui-mark",
    filename: "raft-ui-mark.svg",
    size: "6 KB",
    createdAt: "Today, 10:32 AM",
    type: "image",
    thumbnailUrl: "/raft-logo.svg",
    imageFit: "contain",
  },
  {
    id: "migration-notes",
    filename: "state-ownership-migration-notes.md",
    size: "2.1 KB",
    createdAt: "Today, 9:14 AM",
    type: "file",
  },
  {
    id: "release-checklist",
    filename: "release-checklist.pdf",
    size: "184 KB",
    createdAt: "Yesterday, 4:18 PM",
    type: "pdf",
  },
];

const MORE_FILE: FileListEntry = {
  id: "source-archive",
  filename: "project-assets.zip",
  size: "1.7 MB",
  createdAt: "Yesterday, 3:54 PM",
  type: "archive",
};

export function FilesPanelDefaultComposition() {
  const [files, setFiles] = useState(INITIAL_FILES);
  const [announcement, setAnnouncement] = useState("Ready");
  const hasMore = files.length === INITIAL_FILES.length;

  const handleDownload = useCallback((file: FileListEntry) => {
    setAnnouncement(`Download: ${file.filename}`);
  }, []);

  const handleJumpToOriginal = useCallback((file: FileListEntry) => {
    setAnnouncement(`Jump to original: ${file.filename}`);
  }, []);

  const handlePreview = useCallback((file: FileListEntry) => {
    setAnnouncement(`Preview: ${file.filename}`);
  }, []);

  const handleLoadMore = useCallback(() => {
    setFiles((current) => [...current, MORE_FILE]);
    setAnnouncement(`Loaded ${MORE_FILE.filename}`);
  }, []);

  return (
    <div className="flex h-[32rem] w-full max-w-2xl flex-col overflow-hidden border border-line-hairline">
      <FilesPanelComposition
        files={files}
        hasMore={hasMore}
        onDownload={handleDownload}
        onJumpToOriginal={handleJumpToOriginal}
        onLoadMore={handleLoadMore}
        onPreview={handlePreview}
      />
      <p
        aria-live="polite"
        className="border-t border-line-hairline bg-fill-muted px-4 py-2 text-xs text-foreground-muted"
      >
        {announcement}
      </p>
    </div>
  );
}
```

## When to use

| Situation                                         | Use                                                                                        |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| All the files shared in a channel or conversation | FilesPanel                                                                                 |
| A file attached to one message                    | The file card ([MessageAttachment](https://raft-design.botiverse.build/components/message-attachment.md))           |
| A file in the composer, before it is sent         | [FilePreview](https://raft-design.botiverse.build/components/file-preview.md), in the [Composer](https://raft-design.botiverse.build/components/composer.md) |
| An image or video opened from a row               | [Lightbox](https://raft-design.botiverse.build/components/lightbox.md)                                                        |

In a Files card beside a conversation, list the files in the compact row the Tasks card uses ([Rows in a compact list](https://raft-design.botiverse.build/patterns/tasks.md#rows-in-a-compact-list)): the type icon, the name and its size, then the compact time (15:00 today, Oct 21 this year, 2025-10-21 before) and the actions at the right, all on one line at the card's default 360px, with hairlines between rows instead of a card for each. Jump to Message lands on the message with the located highlight ([Located](https://raft-design.botiverse.build/foundations/states.md#located)). `FileRow` is the card row of a full Files view and has no compact variant yet.

Where the files of a place sit on a screen (a side card or the channel details) is in [Navigation](https://raft-design.botiverse.build/patterns/navigation.md#several-views-of-one-place) and [Page structure](https://raft-design.botiverse.build/patterns/page-structure.md#sidebar-and-side-panel).

## Anatomy

- `FilesPanel`: a `section` on `layer-canvas` that fills its column. Required.
- `FilesViewport`: the scroll container. Required.
- `FilesList`: the column of rows, 8px apart.
- `FileRow`: one file, on `layer-card` with a `line-hairline` outline and `radius-container`.
  - `FileRowOpen`: the button that covers the type, name and details and opens the preview.
    - `FileThumbnail`: a 36px box for the file's [FilePreview](https://raft-design.botiverse.build/components/file-preview.md), or a thumbnail through `FileImage`.
    - `FileContent`: `FileName`, then `FileMeta` with one `FileMetaItem` per detail (size, time), in `text-mono-meta` and `foreground-hint`.
  - `FileActions`: the row's `FileAction` icon buttons, ghost by default.

```tsx
import { useCallback, useState } from "react";
import {
  FileAction,
  FileActions,
  FileContent,
  FileMeta,
  FileMetaItem,
  FileName,
  FilePreview,
  FilePreviewBadge,
  FilePreviewDocument,
  FileRow,
  FileRowOpen,
  FilesList,
  FilesPanel,
  FilesViewport,
  FileThumbnail,
} from "@botiverse/raft-ui";
import { Clock3, Download, ExternalLink } from "lucide-react";

export function FilesPanelFileRowComposition() {
  const [lastAction, setLastAction] = useState<string | null>(null);

  const handlePreview = useCallback(() => {
    setLastAction("Previewed");
  }, []);

  const handleJumpToOriginal = useCallback(() => {
    setLastAction("Jumped to message");
  }, []);

  const handleDownload = useCallback(() => {
    setLastAction("Downloaded");
  }, []);

  return (
    <FilesPanel aria-label="Files panel" className="w-full max-w-2xl">
      <FilesViewport>
        <FilesList>
          <FileRow>
            <FileRowOpen title="Preview file" onClick={handlePreview}>
              <FileThumbnail>
                <FilePreview>
                  <FilePreviewDocument />
                  <FilePreviewBadge>PDF</FilePreviewBadge>
                </FilePreview>
              </FileThumbnail>
              <FileContent>
                <FileName>release-checklist.pdf</FileName>
                <FileMeta>
                  <FileMetaItem>184 KB</FileMetaItem>
                  <FileMetaItem>
                    <Clock3 aria-hidden />
                    Yesterday, 4:18 PM
                  </FileMetaItem>
                  {lastAction ? <FileMetaItem>{lastAction}</FileMetaItem> : null}
                </FileMeta>
              </FileContent>
            </FileRowOpen>
            <FileActions>
              <FileAction
                aria-label="Jump to original message"
                title="Jump to original message"
                onClick={handleJumpToOriginal}
              >
                <ExternalLink aria-hidden />
              </FileAction>
              <FileAction aria-label="Download file" title="Download file" onClick={handleDownload}>
                <Download aria-hidden />
              </FileAction>
            </FileActions>
          </FileRow>
        </FilesList>
      </FilesViewport>
    </FilesPanel>
  );
}
```

## Variants

The file's type changes only its icon: a document, code, an archive or other file, or a thumbnail of an image or video ([FilePreview](https://raft-design.botiverse.build/components/file-preview.md#variants)).

```tsx
import {
  FileAction,
  FileActions,
  FileContent,
  FileImage,
  FileMeta,
  FileMetaItem,
  FileName,
  FilePreview,
  FilePreviewBadge,
  FilePreviewCode,
  FilePreviewDocument,
  FilePreviewFile,
  FilePreviewMedia,
  FileRow,
  FileRowOpen,
  FilesList,
  FilesPanel,
  FilesViewport,
  FileThumbnail,
} from "@botiverse/raft-ui";
import { Clock3, Download, ExternalLink } from "lucide-react";

export function FilesPanelFileTypesComposition() {
  return (
    <div className="flex h-[32rem] w-full max-w-2xl overflow-hidden border border-line-hairline">
      <FilesPanel aria-label="Files panel">
        <FilesViewport>
          <FilesList>
            <FileRow>
              <FileRowOpen disabled title="Preview file">
                <FileThumbnail>
                  <FilePreview>
                    <FilePreviewMedia>
                      <FileImage src="/raft-logo.svg" alt="" fit="contain" loading="lazy" />
                    </FilePreviewMedia>
                    <FilePreviewBadge>SVG</FilePreviewBadge>
                  </FilePreview>
                </FileThumbnail>
                <FileContent>
                  <FileName>raft-ui-mark.svg</FileName>
                  <FileMeta>
                    <FileMetaItem>6 KB</FileMetaItem>
                    <FileMetaItem>
                      <Clock3 aria-hidden />
                      Today, 10:32 AM
                    </FileMetaItem>
                  </FileMeta>
                </FileContent>
              </FileRowOpen>
              <FileActions>
                <FileAction
                  aria-label="Jump to original message"
                  title="Jump to original message"
                  disabled
                >
                  <ExternalLink aria-hidden />
                </FileAction>
                <FileAction aria-label="Download file" title="Download file" disabled>
                  <Download aria-hidden />
                </FileAction>
              </FileActions>
            </FileRow>

            <FileRow>
              <FileRowOpen disabled title="Preview file">
                <FileThumbnail>
                  <FilePreview>
                    <FilePreviewCode />
                    <FilePreviewBadge>TSX</FilePreviewBadge>
                  </FilePreview>
                </FileThumbnail>
                <FileContent>
                  <FileName>files-panel.tsx</FileName>
                  <FileMeta>
                    <FileMetaItem>8.4 KB</FileMetaItem>
                    <FileMetaItem>
                      <Clock3 aria-hidden />
                      Today, 9:14 AM
                    </FileMetaItem>
                  </FileMeta>
                </FileContent>
              </FileRowOpen>
              <FileActions>
                <FileAction
                  aria-label="Jump to original message"
                  title="Jump to original message"
                  disabled
                >
                  <ExternalLink aria-hidden />
                </FileAction>
                <FileAction aria-label="Download file" title="Download file" disabled>
                  <Download aria-hidden />
                </FileAction>
              </FileActions>
            </FileRow>

            <FileRow>
              <FileRowOpen disabled title="Preview file">
                <FileThumbnail>
                  <FilePreview>
                    <FilePreviewDocument />
                    <FilePreviewBadge>PDF</FilePreviewBadge>
                  </FilePreview>
                </FileThumbnail>
                <FileContent>
                  <FileName>release-checklist.pdf</FileName>
                  <FileMeta>
                    <FileMetaItem>184 KB</FileMetaItem>
                    <FileMetaItem>
                      <Clock3 aria-hidden />
                      Yesterday, 4:18 PM
                    </FileMetaItem>
                  </FileMeta>
                </FileContent>
              </FileRowOpen>
              <FileActions>
                <FileAction
                  aria-label="Jump to original message"
                  title="Jump to original message"
                  disabled
                >
                  <ExternalLink aria-hidden />
                </FileAction>
                <FileAction aria-label="Download file" title="Download file" disabled>
                  <Download aria-hidden />
                </FileAction>
              </FileActions>
            </FileRow>

            <FileRow>
              <FileRowOpen disabled title="Preview file">
                <FileThumbnail>
                  <FilePreview>
                    <FilePreviewFile />
                    <FilePreviewBadge>ZIP</FilePreviewBadge>
                  </FilePreview>
                </FileThumbnail>
                <FileContent>
                  <FileName>project-assets.zip</FileName>
                  <FileMeta>
                    <FileMetaItem>1.7 MB</FileMetaItem>
                    <FileMetaItem>
                      <Clock3 aria-hidden />
                      Yesterday, 3:54 PM
                    </FileMetaItem>
                  </FileMeta>
                </FileContent>
              </FileRowOpen>
              <FileActions>
                <FileAction
                  aria-label="Jump to original message"
                  title="Jump to original message"
                  disabled
                >
                  <ExternalLink aria-hidden />
                </FileAction>
                <FileAction aria-label="Download file" title="Download file" disabled>
                  <Download aria-hidden />
                </FileAction>
              </FileActions>
            </FileRow>

            <FileRow>
              <FileRowOpen disabled title="Preview file">
                <FileThumbnail>
                  <FilePreview>
                    <FilePreviewMedia />
                    <FilePreviewBadge>MP4</FilePreviewBadge>
                  </FilePreview>
                </FileThumbnail>
                <FileContent>
                  <FileName>mobile-files-pass.mp4</FileName>
                  <FileMeta>
                    <FileMetaItem>12.8 MB</FileMetaItem>
                    <FileMetaItem>
                      <Clock3 aria-hidden />
                      Monday, 11:06 AM
                    </FileMetaItem>
                  </FileMeta>
                </FileContent>
              </FileRowOpen>
              <FileActions>
                <FileAction
                  aria-label="Jump to original message"
                  title="Jump to original message"
                  disabled
                >
                  <ExternalLink aria-hidden />
                </FileAction>
                <FileAction aria-label="Download file" title="Download file" disabled>
                  <Download aria-hidden />
                </FileAction>
              </FileActions>
            </FileRow>
          </FilesList>
        </FilesViewport>
      </FilesPanel>
    </div>
  );
}
```

## States

How empty, loading and failed differ is in [Feedback](https://raft-design.botiverse.build/patterns/feedback.md#empty-loading-and-failed).

- **Empty:** an [EmptyState](https://raft-design.botiverse.build/components/empty-state.md) in the viewport, with a paperclip icon, No files yet, and where files come from.
- **Loading:** a skeleton of rows, the same size as the rows that replace it.
- **Failed:** the error at the panel's scope, with Retry, not an empty state.
- **More to load:** the next page loads as people near the end of the list. A Load More button below the last row covers the case where scrolling can't trigger it.
- **Menu open:** while a menu opened from one of its `FileAction`s is up, the row lays `fill-subtle` over itself, the same subtle tier as its hover, so the file the menu acts on stays marked ([Hover and press](https://raft-design.botiverse.build/foundations/states.md#hover-and-press)).
- **Not available:** disable a `FileAction` or `FileRowOpen` whose action isn't available.

## Content

- Show the file name as it was uploaded, with its extension. A long name is cut off at the end of the row.
- Give the size with its unit (184 KB) and the time as people read it elsewhere (Yesterday, 4:18 PM).

## Accessibility

FilesPanel adds no Base UI primitive; its parts are a `section`, buttons and an image.

- Name `FilesPanel` with `aria-label`: Files.
- Give every `FileAction` an `aria-label` and a tooltip that say what it does: Jump to Original Message, Download File.
- `FileRowOpen` is named by the file name inside it. The type drawing is decorative ([FilePreview](https://raft-design.botiverse.build/components/file-preview.md#accessibility)).
- Give a thumbnail in `FileImage` an empty `alt`: the name beside it already says what the file is.

## Rules

- Make the whole row the target for the preview, and keep the actions outside `FileRowOpen` so each is its own button.
- Keep `FileAction` at its default, a ghost 32px icon button, so the row stays quiet ([Button](https://raft-design.botiverse.build/components/button.md#icon-buttons)).
- Don't color a row by its file type ([FilePreview](https://raft-design.botiverse.build/components/file-preview.md#rules)).

## API

### FilesPanel

| 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.

### FilesViewport

| 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.

### FilesList

| 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.

### FileRow

| 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.

### FileRowOpen

| 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.

### FileThumbnail

| 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.

### FileImage

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `fit` | `"contain" \| "cover"` | `"cover"` |  |
| `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.

### FileContent

| 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.

### FileName

| 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.

### FileMeta

| 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.

### FileMetaItem

| 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.

### FileActions

| 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.

### FileAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` | `boolean` |  |  |
| `loadingLabel` | `string` |  |  |
| `size` | `"md" \| "lg" \| "xl" \| "icon-md" \| "icon-lg" \| DeprecatedButtonSize` | `"icon-md"` | `md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place. |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "outline" \| "information" \| "muted" \| "success" \| "danger-secondary" \| "danger-outline" \| "ghost" \| "on-frame"` |  |  |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `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.9.5 (Fixed): On a touch screen, a tap no longer leaves a hover highlight on: `TableBody` rows, an `InboxItem`'s pressed toggle action, the Activity inbox sort button and a `FileRow` (when its `FileRowOpen` is hovered) now only show their hover on devices that can hover. With a mouse they look and behave as before.
