# File Row

> One file in a list: an icon by type, the name, "412 KB · 214 rows", and who added it, with their photo. It shows an upload's progress, its failure and the fix, or that it is done.

- Name: `file-row`
- Status: beta
- Categories: dates, forms
- File: the install writes `file-row.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `lucide-react`. The install adds them.
- Other registry items: `@arbokui/motion`, `@arbokui/avatar`, `@arbokui/button`, `@arbokui/status-text`, `@arbokui/tooltip`. The install adds them.
- Gallery: https://arbokui.com/components/file-row
- Registry item: https://arbokui.com/r/file-row.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/file-row
   ```

## Use it for

- Each file a file drop takes in, and files already attached, like a member list or a handbook.
- The parent keeps the list and does the uploading. Pass status and progress as it goes.
- Pass by with who added it. Their photo shows, or their initial until there is one.
- When the connection dropped, pass onRetry: a retry icon, named "Try again", follows "Couldn't upload".
- When the file itself is the problem, say why in error and pass onReplace: an upload icon picks a new file instead, since the same file fails the same way.
- Pass divider on every row but the first.

## Avoid it for

- Bringing files in. Use a file drop, then show each file in one of these.
- A person's photo. Use a photo field.
- A long list to sort and filter. Use a table.

## Props

- `name`: `string`. The file's name. Its extension picks the icon: a sheet, a document, an image, a video, an archive, or a plain file.
- `bytes`: `number`. The size in bytes, shown as "412 KB". A KB is 1024 bytes.
- `meta`: `ReactNode`. Extra detail after the size, like "214 rows".
- `by`: `{ person: string; photo?: string }`. Who added it, shown with their photo.
- `status`: `"uploading" | "done" | "error"`. Where the upload is. Without one, the row just lists the file.
- `progress`: `number`. 0 to 1 while uploading. A thin bar under the row fills, and the percent shows after the name.
- `error`: `ReactNode`. Why it failed, under the name, in ink: "Row 12 has no email address".
- `onRemove`: `() => void`. Shows a remove button at the end of the row.
- `onRetry`: `() => void | Promise<unknown>`. On a failure: a retry icon, named "Try again". Return a promise, and it waits on it.
- `onReplace`: `(file: File) => void`. On a failure caused by the file: an upload icon opens the picker and hands back the new file. It takes the retry's place.
- `accept`: `string`. File types for onReplace's picker, like ".csv".
- `divider`: `boolean`. Default `false`. A line above the row.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Padding and text size.
- `className`: `string`. Classes for the row.

## Accessibility

- Every status shows in words: "Uploading 42%", "Uploaded" with a green check, or "Couldn't upload" in red.
- The bar is a progressbar named "Uploading" plus the file's name, with its percent.
- A polite status says when a file uploads or fails, once, as it happens.
- The retry and replace icons are buttons named "Try again" and "Replace file", with tooltips.
- Remove is a button named "Remove" plus the file's name, so a list of them is told apart.
- In forced-colors mode the bar's fill draws as a border, and the type icon keeps an edge.

## Examples

### Uploaded

```tsx
<FileRow
  name="Members 2026.csv"
  bytes={421888}
  meta="214 rows"
  by={{ person: "Helen Ward" }}
  status="done"
  onRemove={() => {}}
/>
```

### The file is the problem

```tsx
<FileRow
  name="Contacts.csv"
  bytes={20480}
  status="error"
  error="Row 12 has no email address."
  accept=".csv"
  onReplace={(file) => console.log(file)}
/>
```
