File Row
betaOne 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.
Preview
upload
Drop your member list here or
states
Members 2026.csvUploaded
Members 2026.csv uploaded
Customers.xlsx
Orders.csvCouldn't upload
Couldn't upload Orders.csv
Contacts.csvCouldn't upload
Couldn't upload Contacts.csv
Handbook.pdf
Team photo.jpg
size
Members, sm.csvUploaded
Members, sm.csv uploaded
Members, md.csvUploaded
Members, md.csv uploaded
Members, lg.csvUploaded
Members, lg.csv uploaded
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/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
The file's name. Its extension picks the icon: a sheet, a document, an image, a video, an archive, or a plain file.
string
- bytes
The size in bytes, shown as "412 KB". A KB is 1024 bytes.
number
- meta
Extra detail after the size, like "214 rows".
ReactNode
- by
Who added it, shown with their photo.
{ person: string; photo?: string }
- status
Where the upload is. Without one, the row just lists the file.
"uploading" | "done" | "error"
- progress
0 to 1 while uploading. A thin bar under the row fills, and the percent shows after the name.
number
- error
Why it failed, under the name, in ink: "Row 12 has no email address".
ReactNode
- onRemove
Shows a remove button at the end of the row.
() => void
- onRetry
On a failure: a retry icon, named "Try again". Return a promise, and it waits on it.
() => void | Promise<unknown>
- onReplace
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.
(file: File) => void
- accept
File types for onReplace's picker, like ".csv".
string
- divider
A line above the row.
boolean · default false
- size
Padding and text size.
"sm" | "md" | "lg" · default "md"
- className
Classes for the row.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| 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 | false | A line above the row. |
| size | "sm" | "md" | "lg" | "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
<FileRow
name="Members 2026.csv"
bytes={421888}
meta="214 rows"
by={{ person: "Helen Ward" }}
status="done"
onRemove={() => {}}
/>The file is the problem
<FileRow
name="Contacts.csv"
bytes={20480}
status="error"
error="Row 12 has no email address."
accept=".csv"
onReplace={(file) => console.log(file)}
/>