Handoff: Dropzone
What and why
A compact area to add files by dragging them in or by browsing, from the designer's reference: a dashed rounded outline holding an upload icon and one line of text, "Drop an image or brief, or browse". PDS has the File Upload, a form field with a "Choose file" button and no drag and drop, so this is a new component, not an extension of it. The Chat Panel and Chat Input accept drops but have no visible area to drop on.
- Figma / Jira: none. Source: the designer's reference image (2026-10-21).
- Context:
context.md
Exploration and decisions
- New component, not a File Upload variant: different anatomy and purpose (a form field with a validation message and a chosen file name, versus a compact drop area).
- The sentence is two props,
labelandbrowseLabel, both required, so each half can be translated and browse is a realbutton. - Behavior: a drop or the dialog calls
onFilesDrop(files); the files are filtered byaccept(extensions, types andtype/*wildcards, as on a file input) and cut to one unlessmultiple. While a file is over the zone the outline turns solid with the interactive focus color and the selected background. The hidden file input is reset after each change so the same file can be chosen again. The zone shows no files: the screen owns the list. - Drag counting:
dragenteranddragleavefire for each child the pointer crosses, so they are counted (as in the Chat Panel). - Keyboard: dropping is a pointer shortcut; keyboard and screen reader users press browse. The zone itself is not focusable.
Full list: decisions.md.
API
DropzoneProps extends the props of a div (except onDrop):
| Prop | Type | Default | Purpose |
|---|---|---|---|
accept |
string |
none | Which files to accept, as on a file input (image/*,.pdf). Applies to the dialog and the drop. |
browseLabel |
string |
required | Text of the browse button ("browse"). Translated. |
className |
string |
none | Additional class names. |
disabled |
boolean |
false |
Drops and the browse button do nothing; the zone is faded. |
label |
string |
required | The text before the browse button ("Drop an image or brief, or"). Translated. |
multiple |
boolean |
false |
Allow more than one file at once. |
onFilesDrop |
(files: File[]) => void |
none | Called with the files from a drop or the dialog (filtered by accept). |
Variants and states: default, drag over, disabled; the browse button has hover, active and focus. Stories: Default, Disabled, Interactive.
Tokens used
--pds-animation-transition-input--pds-border-radius-default,--pds-border-radius-input--pds-border-width-default,--pds-border-width-outline--pds-color-border-default--pds-color-foreground-default-secondary--pds-color-interactive-background-selected,--pds-color-interactive-focus--pds-color-interactive-link-active,--pds-color-interactive-link-default,--pds-color-interactive-link-hover--pds-spacing-2xs,--pds-spacing-5xs,--pds-spacing-s,--pds-spacing-xs--pds-typography-ff-default,--pds-typography-fw-semibold,--pds-typography-size-s
Impact
From impact.md: no existing PDS component is reached and no --pds-* token is redefined. No other proposal depends on it.
Reused vs net-new
- Reused: the local
Iconwrapper (upload), thevisually-hiddenclass, thepds-copy-sclass. - Net-new:
.pds-dropzonewith--drag-over,--disabled,__browse,__iconand__label; all props. Seecustom.md(no custom variables, no raw values).
Accessibility
- Browse is a real
buttonthat opens the file dialog, with a visible focus ring; the file input is visually hidden and out of the tab order. - Dropping is a pointer-only shortcut. The zone has no role; the text and the button are what is announced.
- Not handled: announcing that files were added (the screen owns the list and should announce it), and a drag-over announcement.
- Contrast: the secondary text and the link color on the surface; check both in light and dark. The a11y panel has not been run, and dark mode was not checked by eye in a session.
Decisions and open questions
- A validation or error state (a rejected type or size)? Not built; the screen can show a message below.
- A size or file-count limit?
- A taller, multi-line version for pages where the dropzone is the main content?
- Should the drag-over highlight reuse the Chat Panel's drop overlay look?
Port notes
- New component (
npm run generate:component), probably next to the File Upload insrc/components/inputs/. Swap the localIconwrapper (the package hasupload) andmergeClassesfor the real ones. ThematchesAccepthelper is local; the port may move it to a util.
Preview
npm run storybook, then Component Proposals/Dropzone. Figma: none yet.