Forms / React + CSS
Upload zone
A responsive drop area with a floating file stack and clear validation.
PNG, JPEG, WebP or SVG · up to 5 MB
"use client";
import styles from "./UploadZone.module.css";
import { useState, useRef, useId } from "react";
export function validateFile(file, maxBytes) {
if (!file) return "";
if (
!["image/png", "image/jpeg", "image/webp", "image/svg+xml"].includes(
file.type,
)
)
return "Choose a PNG, JPEG, WebP, or SVG image.";
if (file.size > maxBytes)
return "This file is too large. Choose a smaller image.";
return "";
}
export default function UploadZone({ onFile, maxBytes = 5 * 1024 * 1024 }) {
const [dragging, setDragging] = useState(false);
const [message, setMessage] = useState(
`PNG, JPEG, WebP or SVG · up to ${Math.round((maxBytes / 1024 / 1024) * 10) / 10} MB`,
);
const input = useRef(null);
const id = useId();
function accept(file) {
setDragging(false);
if (!file) return;
const error = validateFile(file, maxBytes);
if (error) {
setMessage(error);
return;
}
setMessage(`${file.name} selected. Nothing has been uploaded.`);
onFile?.(file);
}
return (
<div
className={styles.root}
data-dragging={dragging}
onDragOver={(event) => {
event.preventDefault();
setDragging(true);
}}
onDragLeave={(event) => {
if (!event.currentTarget.contains(event.relatedTarget))
setDragging(false);
}}
onDrop={(event) => {
event.preventDefault();
accept(event.dataTransfer.files[0]);
}}
>
<div className={styles.files} aria-hidden="true">
<i />
<i />
<span>↑</span>
</div>
<strong>Drop your next idea here.</strong>
<button
type="button"
aria-describedby={id}
onClick={() => input.current?.click()}
>
Choose an image
</button>
<input
ref={input}
type="file"
accept="image/png,image/jpeg,image/webp,image/svg+xml"
className={styles.hidden}
tabIndex={-1}
onChange={(event) => {
accept(event.target.files[0]);
event.target.value = "";
}}
/>
<p id={id} role="status">
{message}
</p>
</div>
);
}
Make it yours
Use as a file selection step. This component only selects and validates a file; connect upload, progress, server validation, and failure handling in your application. Do not trust client-side MIME checks on the server.
- Use a React project with CSS Modules support, such as Next.js or Vite.
- Save the JSX as
UploadZone.jsxand the CSS asUploadZone.module.cssin the same folder. - Import
UploadZoneand connect its props to your application. Controlled components need parent state and change handlers.
No third-party component or animation package is required. Use the theme controls to adjust accent, corners, spacing, and type. Copy the Theme CSS into your stylesheet and wrap your components in <div className="tdp-components">. The theme applies to every component inside that wrapper. TDP defaults work without a theme file.
Props
| Prop | Type | Use |
|---|---|---|
onFile | function | Receives the validated File; no network upload is performed. |
maxBytes | number | Maximum file size in bytes; defaults to 5 MiB. |
Accessibility & behavior
A keyboard-accessible button opens the file picker; drag-and-drop is optional. File selection and validation are announced. The native file input is triggered by the visible button.
Visible focus styles and reduced-motion support are included. Check labels, contrast, keyboard behavior, and screen-reader output again after adapting the component to your product.