DropZone
A styled drop target for files.
No photos queued
Whatever you drop in shows up here before it uploads.
View code
let session = new DragSession();
let queued: File[] = [];
function queueFiles(files: readonly File[]) {
queued = [...queued, ...files];
void handle.update();
}
<Header>Product photos</Header>
<DropZone
name="photos"
accept="image/png, image/jpeg, image/webp"
multiple
// The native input stays in the accessibility tree and the tab order; the
// dashed label is what a person sees, and it draws the focus ring for both.
parts={{ input: [visuallyHidden()] }}
mix={[
dropZone(session),
on<HTMLLabelElement, "ui:drop-files">("ui:drop-files", (event) => queueFiles(event.files)),
on<HTMLLabelElement, "change">("change", (event) => {
let input = event.target as HTMLInputElement;
queueFiles([...(input.files ?? [])]);
}),
]}
>
<ImageIcon aria-hidden="true" />
<span>Drag photos here, or click to browse</span>
<span>PNG, JPEG or WebP · up to 10 MB each</span>
</DropZone>
{queued.length === 0 ? (
<Empty>
<Empty.Title>No photos queued</Empty.Title>
<Empty.Description>Whatever you drop in shows up here before it uploads.</Empty.Description>
</Empty>
) : (
queued.map((file, index) => (
<Item key={`${file.name}-${index}`}>
<Item.Content>
<Item.Title>{file.name}</Item.Title>
<Item.Description>{formatFileSize(file.size)}</Item.Description>
</Item.Content>
<Item.Actions>
<Button
variant="ghost"
color="neutral"
aria-label={`Remove ${file.name}`}
mix={[on<HTMLButtonElement, "click">("click", () => removeFile(index))]}
>
<XIcon />
</Button>
</Item.Actions>
</Item>
))
)}Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/uiimport "@sdxc/ui/theme.css";Usage
import { DropZone } from "@sdxc/ui";Renders a native <input type="file"> wrapped in a <label> styled as a
dashed, centered drop surface; a paired drag-and-drop mixin toggles the
data-drop-target attribute this surface's styling reacts to.
Examples
<DropZone name="avatar" accept="image/png, image/jpeg" aria-label={t("profile.chooseAvatar")}>
<ImageIcon aria-hidden />
</DropZone><DropZone name="documents" multiple accept="application/pdf" required>
<p>{t("uploads.dropHint")}</p>
</DropZone>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Description |
|---|---|---|
name? | string | Native name submitted with an enclosing form. |
accept? | string | Comma-separated list of accepted MIME types or file extensions. |
multiple? | boolean | Whether more than one file may be chosen at once. |
capture? | TagProps<"input">["capture"] | Requests a specific camera where a mobile browser's picker offers one. |
required? | boolean | Marks the control required for its enclosing form. |
disabled? | boolean | Marks the control inert and excluded from the tab order. |
children? | RemixNode | The zone's visible content — an icon, an instructional caption, or both — rendered inside the <label> that wraps the file input, so clicking it opens the picker and names the input via label association. |
parts? | PartsProps | Per-part styling for this component's internally composed input. |
Also accepts everything in TagProps<"label">.