Forms

File Upload

A button-triggered file picker paired with a list of selected files, each showing its name, size, upload progress, and a remove control.

Preview
01 Installation

Install

Pull File Upload from the barrel for everyday use, or the granular path when you want a tighter bundle.

tsxBarrel1 line
import { FileUpload } from "@brika/clay";
tsxGranular1 line
import { FileUpload } from "@brika/clay/components/file-upload";
02 Usage

A minimal example

Drop this into a page. Native HTML attributes pass through to the underlying primitive.

tsxFile Upload.tsx64 lines
'use client';

import { Button } from '@brika/clay/components/button';
import {
  FileUpload,
  FileUploadItem,
  FileUploadItemContent,
  FileUploadItemName,
  FileUploadItemPreview,
  FileUploadItemRemove,
  FileUploadItemSize,
  FileUploadList,
  FileUploadTrigger,
} from '@brika/clay/components/file-upload';
import { FileText, ImageIcon, Upload } from 'lucide-react';
import { useState } from 'react';

interface PickedFile {
  readonly id: string;
  readonly name: string;
  readonly size: number;
}

function toPicked(file: File): PickedFile {
  return { id: `${file.name}-${file.size}`, name: file.name, size: file.size };
}

/** Pick files, then list each with its name, size, and a remove button. */
export default function FileUploadDefaultDemo() {
  const [files, setFiles] = useState<PickedFile[]>([
    { id: 'a', name: 'quarterly-report.pdf', size: 2_411_724 },
    { id: 'b', name: 'cover-photo.jpg', size: 845_120 },
  ]);

  const addFiles = (selected: File[]) => setFiles((prev) => [...prev, ...selected.map(toPicked)]);
  const removeFile = (id: string) => setFiles((prev) => prev.filter((f) => f.id !== id));

  return (
    <FileUpload className="w-full max-w-md" multiple onFilesSelected={addFiles}>
      <FileUploadTrigger asChild>
        <Button variant="outline">
          <Upload aria-hidden />
          Choose files
        </Button>
      </FileUploadTrigger>
      {files.length > 0 && (
        <FileUploadList>
          {files.map((file) => (
            <FileUploadItem key={file.id}>
              <FileUploadItemPreview>
                {file.name.endsWith('.jpg') ? <ImageIcon aria-hidden /> : <FileText aria-hidden />}
              </FileUploadItemPreview>
              <FileUploadItemContent>
                <FileUploadItemName>{file.name}</FileUploadItemName>
                <FileUploadItemSize bytes={file.size} />
              </FileUploadItemContent>
              <FileUploadItemRemove onClick={() => removeFile(file.id)} />
            </FileUploadItem>
          ))}
        </FileUploadList>
      )}
    </FileUpload>
  );
}
03 Examples

File Upload, every way

With Progress

Show per-file upload progress with an inline progress bar.

04 Accessibility

Accessibility

  • The native file <input> is visually hidden but stays in the DOM, so the trigger and assistive tech reach the OS file picker.
  • FileUploadTrigger renders a real <button> (or your own element via asChild) and forwards focus and keyboard activation.
  • FileUploadItemRemove carries an aria-label (defaults to "Remove file") so its purpose is announced.
  • Progress bars expose role="progressbar" with the current value through the underlying Progress primitive.
05 Tokens 14 theme-overridable

Theme tokens

Every CSS variable File Upload reads, with its default and the dotted path you'd write in a ThemeConfig JSON to override it. Set any of these in your theme to retune File Upload without touching component code.

File Upload tokens

14 tokens
ColorFill, border, and text colors.6
  • --file-upload-item-containercolorvar(--card)
    components.fileUpload.itemContainer

    Background of a file-list row.

  • --file-upload-item-bordercolorvar(--border)
    components.fileUpload.itemBorder

    Border color of a file-list row.

  • --file-upload-preview-bgcolorvar(--muted)
    components.fileUpload.previewBg

    Background of the leading preview/icon tile on each row.

  • --file-upload-iconcolorvar(--muted-foreground)
    components.fileUpload.icon

    Color of the file-type glyph inside the preview tile.

  • --file-upload-namecolorvar(--foreground)
    components.fileUpload.name

    File name text color.

  • --file-upload-metacolorvar(--muted-foreground)
    components.fileUpload.meta

    Secondary text color for file size and status.

GeometrySizes, lengths, and corner radii.4
  • --file-upload-radiusradiusvar(--radius-control)
    components.fileUpload.radius

    Corner radius of a file-list row and its preview tile.

  • --file-upload-padding-xsizecalc(var(--spacing) * 3)
    components.fileUpload.paddingX

    Inline padding inside the file-upload.

  • --file-upload-padding-ysizecalc(var(--spacing) * 2)
    components.fileUpload.paddingY

    Block padding inside the file-upload.

  • --file-upload-gapsizecalc(var(--spacing) * 3)
    components.fileUpload.gap

    Gap between adjacent children inside the file-upload.

BorderBorder width and style.2
  • --file-upload-border-widthborder-width1px
    components.fileUpload.borderWidth

    Border width on the file-upload. Set non-zero for outline-style variants.

  • --file-upload-border-styleborder-stylesolid
    components.fileUpload.borderStyle

    Border style on the file-upload (`solid`, `dashed`, `double`, `none`).

MotionAnimation duration and easing.2
  • --file-upload-durationdurationvar(--motion-standard-duration)
    components.fileUpload.duration

    Transition duration for file-upload state changes.

  • --file-upload-easingeasingvar(--motion-standard-easing)
    components.fileUpload.easing

    Transition easing for file-upload state changes.

Override in a theme

Authoring a theme is plain JSON. Drop overrides under components.fileUpload, the names are camelCase versions of the variable suffix.

jsonmy-theme.json12 lines
{
  "id": "my-theme",
  "name": "My Theme",
  "description": "...",
  "accentSwatches": ["#000"],

  "components": {
    "fileUpload": {
      "paddingX": "calc(var(--spacing) * 3)"
    }
  }
}

Then apply with applyTheme(myTheme) (see the theming guide), scope it to a subtree with themeToCssVars(myTheme, mode), or consume any token directly in your own JSX with the matching Tailwind utility (e.g. bg-file-upload-…) — see the chips on each token row.

06 API reference

API reference

Props specific to File Upload. Native HTML attributes pass through to the underlying primitive, see the component source on GitHub for the full type signature.

<FileUpload />

4 props
  • string
    Comma-separated list of accepted types, forwarded to the native input.
  • disableddefault false
    boolean
    Disable the trigger and the underlying input.
  • multipledefault false
    boolean
    Allow selecting more than one file at a time.
  • (files: File[]) => void
    Fires with the chosen files whenever the picker resolves.

<FileUploadContext />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadItem />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadItemContent />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadItemName />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadItemPreview />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadItemProgress />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadItemRemove />

1 prop
  • aria-labeldefault 'Remove file'
    string

    No description.

<FileUploadItemSize />

1 prop
  • number
    Byte count, formatted via formatFileSize when no children are given.

<FileUploadList />

passthrough

No wrapper-specific props, all attributes pass through to the underlying primitive.

<FileUploadTrigger />

1 prop
  • asChilddefault false
    boolean
    Render the trigger behavior onto your own child element (e.g. a Button).