Marble UI
← Components

File Upload / Dropzone

Preview: Area

Drag-and-drop target plus a native file picker (React Aria DropZone + FileTrigger).

Import

import { FileDropZone, FileList } from '@marbleui/ui'
import { useFileUploads } from '@marbleui/ui'

Props

<FileDropZone>

PropTypeDefaultDescription
onFiles *required(files: File[]) => void—
acceptedFileTypesstring[]—
allowsMultiplebooleantrue
classNamestring—
hintReactNode—
titleReactNode'Drop files here'
variant'area' | 'compact''area'"area": large dashed drop target. "compact": a single row.

<FileList>

PropTypeDefaultDescription
files *requiredUploadedFile[]—
classNamestring—
onRemove(id: string) => void—

Hooks

useFileUploads(simulate?: boolean): { files: UploadedFile[]; add: (incoming: File[]) => void; remove: (id: string) => void; }

Local state helper for demos and simple forms: adds files and fakes upload progress.

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria DropZone · React Aria FileTrigger · React Aria Text. Follow the primitive's docs for the full keyboard map.

Usage notes

Drag-and-drop file target plus picker (FileDropZone), a list of files (FileList) and a state helper (useFileUploads). For just a picker button use FileTrigger.

const { files, add, remove } = useFileUploads()

<FileDropZone onFiles={add} acceptedFileTypes={['image/*', 'application/pdf']} hint="PNG, JPG or PDF" />
<FileList files={files} onRemove={remove} />
  • FileDropZone filters dropped and picked files by acceptedFileTypes (exact MIME types or image/* style wildcards). Set allowsMultiple={false} for one file; variant is area or compact.
  • useFileUploads() fakes progress by default. For real uploads pass false (files count as done) or manage your own UploadedFile[] with progress and error.
  • There is no size limit check: enforce it in onFiles.
Example source · show
import { FileDropZone, FileList, useFileUploads } from './file-upload'

function Area() {
  const { files, add, remove } = useFileUploads()
  return (
    <div className="flex w-[26rem] flex-col gap-3">
      <FileDropZone onFiles={add} hint="PNG, JPG or PDF, up to 10 MB" acceptedFileTypes={['image/*', 'application/pdf']} />
      <FileList files={files} onRemove={remove} />
    </div>
  )
}

function Compact() {
  const { files, add, remove } = useFileUploads()
  return (
    <div className="flex w-[26rem] flex-col gap-3">
      <FileDropZone variant="compact" onFiles={add} title="Attach files" hint="Drag here or browse" />
      <FileList files={files} onRemove={remove} />
    </div>
  )
}

function Avatar() {
  const { files, add } = useFileUploads(false)
  const last = files.at(-1)
  return (
    <div className="flex items-center gap-4">
      <span className="grid size-16 place-items-center overflow-hidden rounded-full bg-surface-sunken text-xs text-fg-subtle">
        {last ? <img src={URL.createObjectURL(last.file)} alt="" className="size-full object-cover" /> : 'None'}
      </span>
      <FileDropZone variant="compact" allowsMultiple={false} acceptedFileTypes={['image/*']} onFiles={add} title="Profile photo" hint="Square, at least 256px" />
    </div>
  )
}

export const styles = { Area, Compact, Avatar }

Source: packages/ui/src/file-upload/file-upload.tsx

Catalog

Category
inputs
Priority
Foundation
Primitive
react-aria
React Aria
DropZone + FileTrigger