Headless / Specialized

FileUpload

FileUpload is a drag-and-drop file region. It wraps a hidden native file input and hands you a render prop with the current drag state and a function to open the file picker.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A single component with a render-prop child. It owns a hidden <input type="file"> and a drop-zone wrapper that tracks drag events. Your render function receives isDragging — true while a file is dragged over the zone — and openPicker, which opens the native file dialog. Files arrive through onFiles, whether dropped or chosen.

Because the input is real, the file dialog, the accept filter, and multiple are all the platform's, not a reimplementation.

Install

example.tsx
yarn add @usemotif/headless
example.tsx
import { FileUpload } from '@usemotif/headless';

API

FileUpload

stable
function FileUpload(props: FileUploadProps): JSX.Element
children(info: { isDragging: boolean; openPicker: () => void }) => ReactNoderequired

Render prop for the drop zone. `isDragging` is true during a drag-over; `openPicker` opens the native file dialog.

onFiles(files: File[]) => void

Called with the selected files — whether dropped or chosen through the dialog.

acceptstring

The native `accept` filter, e.g. `"image/*"` or `".pdf,.docx"`.

multipleboolean= false

Allow selecting more than one file.

disabledboolean= false

Ignore drops and picker opens.

styleCSSProperties

Inline style for the drop-zone wrapper.

Accessibility

Drag-and-drop is a pointer-only gesture — a keyboard or screen-reader user cannot drag a file. openPicker is the path that keeps FileUpload usable: wire it to a real <button> inside your render prop, so the file dialog can be opened from the keyboard. The drop zone is an enhancement on top of that button, not a replacement for it.

Treat the drag zone as visual sugar. The button labelled "Choose files" — or similar — is the control that must always work.

Examples

A drop zone with a fallback button:

example.tsx
<FileUpload accept="image/*" multiple onFiles={handleUpload}>
{({ isDragging, openPicker }) => (
  <Center
    h={160}
    borderWidth={2}
    borderStyle="dashed"
    borderColor={isDragging ? '$colors.accent.base' : '$colors.line.base'}
    borderRadius="$radii.md"
  >
    <VStack gap="$2" alignItems="center">
      <Text>{isDragging ? 'Drop to upload' : 'Drag images here'}</Text>
      <Button onPress={openPicker}>Choose files</Button>
    </VStack>
  </Center>
)}
</FileUpload>