FilePicker

Lets users select files via a native trigger, drag-and-drop zone, or compact chat-style affordance, with optional file list and constraints

Installation

pnpm add @wandercom/design-system-web

Usage

12 lines
import {
  FilePicker,
  FilePickerItem,
} from '@wandercom/design-system-web/ui/file-picker';

export function Example() {
  const [files, setFiles] = useState<File[]>([]);

  return (
    <FilePicker onFilesAccepted={setFiles} value={files} />
  );
}

Variants

Default

A compact native-style trigger that opens the file picker on click. Shows the selected filenames or a placeholder.

Loading example...
4 lines
<FilePicker
  onFilesAccepted={setFiles}
  value={files}
/>

Dropzone

A bordered drop region with a cloud-upload icon, title, helper text, and a primary "Select files" button. Helper text is generated automatically from accept and maxSize, or can be overridden via the description prop.

Loading example...
11 lines
<FilePicker
  variant="dropzone"
  accept={{
    'image/jpeg': ['.jpg', '.jpeg'],
    'image/png': ['.png'],
    'application/pdf': ['.pdf'],
    'video/mp4': ['.mp4'],
  }}
  maxSize={50 * 1024 * 1024}
  onFilesAccepted={(files) => attach(files)}
/>

Chat

For chat composers, pair FilePickerChatTrigger (a secondary icon button that opens the picker) with FilePickerChatThumbnail (a 28x42 image preview chip). Both render independently so the trigger and uploaded previews can sit anywhere in the composer.

Loading example...
15 lines
import {
  FilePickerChatThumbnail,
  FilePickerChatTrigger,
} from '@wandercom/design-system-web/ui/file-picker';

<FilePickerChatTrigger
  accept={{ 'image/*': ['.jpg', '.jpeg', '.png'] }}
  onFilesAccepted={(files) => attach(files)}
/>

<FilePickerChatThumbnail
  src={previewUrl}
  alt="Uploaded screenshot"
  onRemove={() => removeAttachment(id)}
/>

File list

Wrap selected files in FilePickerList (resets browser default <ul> margin / padding / list-style and stretches each <li> to full width) and render rows with FilePickerItem. For images, pass an object URL (or any image URL) via previewUrl to show the actual asset as a thumbnail; for non-image files the extension is rendered as a fallback. Pass loading for in-flight uploads (replaces the trash button with a spinner) and error (boolean or message string) to surface a failed state. Provide onRemove to display a ghost trash button on idle rows.

Loading example...
6 lines
<FilePickerItem
  name="wander-villa-sunset.jpg"
  size={2_400_000}
  previewUrl={URL.createObjectURL(imageFile)}
  onRemove={() => removeFile(id)}
/>

Constraints

accept and maxSize are forwarded to react-dropzone for validation and also drive the dropzone helper text. Pass onFilesRejected to surface validation errors.

7 lines
<FilePicker
  variant="dropzone"
  accept={{ 'application/pdf': ['.pdf'] }}
  maxSize={10 * 1024 * 1024}
  onFilesAccepted={(files) => handleUpload(files)}
  onFilesRejected={(rejections) => toast.error(rejections[0]?.errors[0]?.message)}
/>

Props

FilePicker

variant?

'default' | 'dropzone'
Visual presentation. Defaults to 'default'.

accept?

Accept
Accepted MIME types and extensions, forwarded to react-dropzone. Used to derive dropzone helper text.

maxSize?

number
Maximum file size in bytes. Used for validation and to derive dropzone helper text.

maxFiles?

number
Maximum number of files accepted at once.

multiple?

boolean
Allow multiple file selection. Defaults to true.

disabled?

boolean
Disables the picker and underlying input.

title?

string
Override the dropzone heading.

description?

string
Override the auto-generated helper text below the title.

chooseFileLabel?

string
Visible label on the default-variant trigger row. Defaults to "Choose file".

value?

File[]
Controlled list of currently selected files (display-only at this layer).

onFilesAccepted?

(files: File[]) => void
Fired when files pass validation.

onFilesRejected?

(rejections: FileRejection[]) => void
Fired when files fail validation.

className?

string
Additional CSS classes to apply.

FilePickerItem

name

string
File name to display. Truncates when long.

size

number
File size in bytes; rendered human-readable (e.g. "2.4 MB").

previewUrl?

string
Optional image preview URL.

previewAlt?

string
Alt text for the preview image. Defaults to "" (decorative — the filename is rendered next to the thumbnail).

extension?

string
File extension override; derived from name otherwise.

onRemove?

() => void
When provided, renders a ghost trash button. While `loading` is true the button shows a cross and re-labels to "Cancel upload"; the same callback is invoked.

loading?

boolean
Renders the row in an in-progress state with a spinner instead of the size meta and trash button.

error?

boolean | string
Renders the row in an error state. Pass a string to surface the message in place of the size meta.

uploadingLabel?

string
Accessible label for the in-progress upload spinner. Defaults to "Uploading".

className?

string
Additional CSS classes to apply.

FilePickerList

className?

string
Additional CSS classes to apply.

children

ReactNode
List items, typically `<li>` elements wrapping `<FilePickerItem>`.

FilePickerChatTrigger

accept?

Accept
Accepted MIME types and extensions.

maxSize?

number
Maximum file size in bytes.

maxFiles?

number
Maximum number of files accepted at once.

multiple?

boolean
Allow multiple file selection. Defaults to true.

onFilesAccepted?

(files: File[]) => void
Fired when files pass validation.

onFilesRejected?

(rejections: FileRejection[]) => void
Fired when files fail validation.

aria-label?

string
Accessible label for the icon-only trigger. Defaults to 'Attach file'.

FilePickerChatThumbnail

src

string
Source URL for the thumbnail image.

alt?

string
Accessible alternative text. Defaults to an empty string for decorative thumbnails.

onRemove?

() => void
When provided, renders a hover overlay with a remove control. Suppressed in the loading state.

loading?

boolean
Renders the chip in an in-flight upload state with a spinner overlaid on a dimmed image.

uploadingLabel?

string
Accessible label for the in-flight upload spinner. Defaults to "Uploading".

removeLabel?

string
Accessible label for the remove control. Defaults to "Remove attachment".

className?

string
Additional CSS classes to apply.

Accessibility

The underlying react-dropzone hook gives the dropzone region role="button" and tabIndex={0} so it is reachable by keyboard. The visually hidden <input type="file"> remains in the DOM for screen reader users, and Enter / Space open the picker.

For icon-only triggers (FilePickerChatTrigger, the remove button on FilePickerItem, and the optional remove overlay on FilePickerChatThumbnail), an aria-label is provided by default. Override it via the aria-label prop when the surrounding context calls for a more specific name.

When disabled is true the trigger is removed from the tab order and click / drop interactions are blocked.

FilePicker