Skip to content
txbrendan/ui

File Row

beta

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.

Preview

upload

Drop your member list here or

CSV or Excel · up to 10 MB

states

Members 2026.csvUploaded

412 KB · 214 rowsHelen Ward

Members 2026.csv uploaded

Customers.xlsxUploading 42%

96 KBHelen Ward

Orders.csvCouldn't upload

The connection dropped.

12 KBHelen Ward

Couldn't upload Orders.csv

Contacts.csvCouldn't upload

Row 12 has no email address.

20 KB · 96 rowsHelen Ward

Couldn't upload Contacts.csv

Handbook.pdf

1.4 MBTom Hale

Team photo.jpg

3.2 MB

size

Members, sm.csvUploaded

412 KB · 214 rowsHelen Ward

Members, sm.csv uploaded

Members, md.csvUploaded

412 KB · 214 rowsHelen Ward

Members, md.csv uploaded

Members, lg.csvUploaded

412 KB · 214 rowsHelen Ward

Members, lg.csv uploaded

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. 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

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)}
/>