Building a Custom Radix UI File Upload Component with AI

Radix UI is the gold standard for accessible, unstyled React primitives — but it doesn't include a file upload component out of the box. You're expected to build your own, using Radix's composition patterns. This is both intentional and powerful, but it means you need to wire up accessible drag-and-drop logic, file validation, progress tracking, and keyboard navigation yourself.
That's a lot of boilerplate for something every SaaS app needs. The prompts in this guide generate the complete, accessible, production-ready Radix UI file upload component — including drag-and-drop, multi-file support, file type validation, preview thumbnails, and upload progress — in seconds.
Why Radix UI for File Uploads?
Most developers reach for a pre-built library like react-dropzone or uppy for file uploads. These are fine, but they come with their own CSS conventions and accessibility assumptions that may conflict with your design system.
Using Radix primitives as your base (specifically @radix-ui/react-dialog for the upload modal, @radix-ui/react-progress for progress tracking, and standard HTML input with custom styling) gives you:
- Full style control — no CSS specificity wars with library styles
- Built-in ARIA compliance — Radix handles focus management, keyboard navigation, and screen reader announcements
- Composability — you can mix file upload UX with your existing Radix form components
- No hidden dependencies — you own every line of the upload logic
Prompt 1: Base Drag-and-Drop Upload Zone
Start with the core upload zone component that handles drag events, click-to-browse, and initial file selection:
"Act as a Senior React UI Engineer specializing in accessible component design. Build a TypeScript React file upload dropzone component using only Radix UI primitives and Tailwind CSS. Requirements: (1) Implement drag-over visual feedback with a dashed blue border and subtle background color change using onDragOver, onDragLeave, and onDrop handlers. (2) Include a hidden <input type='file'> that opens on click using useRef and a visible trigger button styled with Tailwind. (3) Support multiple file uploads via the 'multiple' attribute. (4) Accept only images and PDFs (accept prop: 'image/*,.pdf'). (5) On file drop or selection, display a FileList preview showing filename, file size in KB/MB, and an X button to remove individual files. (6) Use Radix UI's visually hidden component (@radix-ui/react-visually-hidden) for the screen reader label on the input. (7) Export as 'FileDropzone' TypeScript component. No external upload logic — just selection and preview."Prompt 2: Upload Progress with Radix Progress Bar
Once files are selected, you need upload progress tracking. Radix UI's Progress component provides an accessible progress bar with proper ARIA attributes out of the box:
"Using @radix-ui/react-progress and TypeScript, build a multi-file upload manager component. Requirements: (1) Accept a File[] array as props. (2) For each file, show a row with: filename, progress bar using Radix Progress (value=0 to 100), percentage label, and status badge (pending/uploading/complete/error). (3) Upload files to a presigned S3 URL using the Fetch API and track progress using XMLHttpRequest's upload.onprogress event (not fetch, which doesn't support progress natively). (4) Upload files concurrently using Promise.all with a concurrency limit of 3 using a simple semaphore pattern. (5) On error, show a retry button for that specific file. (6) When all files complete, call an onComplete(urls: string[]) callback with the S3 object URLs. Include full TypeScript types."Prompt 3: Image Preview with Thumbnail Grid
For image uploads specifically, showing preview thumbnails before upload significantly improves UX. This requires using the FileReader API or URL.createObjectURL():
"Write a React TypeScript component named 'ImageUploadPreview' that: (1) Accepts dropped or clicked image files (PNG, JPG, WEBP, GIF). (2) Uses URL.createObjectURL() (not FileReader) for performance-optimal preview generation. (3) Shows a 3-column responsive thumbnail grid with the image preview, filename, and file size. (4) Implements cleanup: call URL.revokeObjectURL() for each preview URL when the component unmounts or the file is removed. (5) Limits uploads to 10 images maximum with a friendly error message if the user tries to add more. (6) Shows an animated shimmer skeleton placeholder while the thumbnail is loading (using CSS animation, no libraries). (7) Handles drag-reordering of images in the grid (using the HTML5 drag API, not react-dnd). The final order of images determines upload order."Prompt 4: File Validation & Error Handling
Production upload components need comprehensive validation before a single byte hits your server:
"Write a TypeScript file validation utility for a React upload component. Implement a validateFiles(files: File[], options: ValidationOptions) function that checks: (1) Maximum file size (configurable, default 10MB) — reject with 'File too large (max 10MB)'. (2) Allowed MIME types — check both file.type AND the actual file signature bytes (magic numbers) using a FileReader to read the first 4 bytes. This prevents filename extension spoofing. (3) Duplicate detection — reject files with identical names AND identical sizes (not just names). (4) Malicious file guard — reject .exe, .js, .php, .sh extensions regardless of MIME type. (5) Return a ValidationResult[] array with { file, valid: boolean, error: string | null } for each file. Include a 'ValidationOptions' TypeScript interface for all configuration options."Pro Tips: File Upload UX & Security
💡 Use Presigned URLs, Never Direct Backend Upload
Never stream file uploads through your application server. Instead, generate a presigned S3 URL on your backend (a 5-minute expiring PUT URL) and upload directly from the browser to S3. This removes your server from the upload path entirely, reduces latency, eliminates file size limits imposed by your web framework, and dramatically reduces egress costs.
⚠️ Always Re-validate on the Server
Client-side validation (like the prompt above) is for UX, not security. A sophisticated attacker can bypass any JavaScript validation. Always run the same file type, size, and content checks on your server using a library like file-type (npm) or Python's python-magic that reads actual file signatures.
✅ Add Keyboard Accessibility
A drag-and-drop zone must be keyboard accessible. The invisible <input type="file"> and a visible <button> that triggers it covers most cases, but also ensure the drop zone div has role="button", tabIndex=0, and handles the Enter/Space keypress to open the file dialog.
Frequently Asked Questions
Q: Does Radix UI have a native file input component?
A: No — Radix UI focuses on complex interactive components like Dialogs, Dropdowns, and Tooltips where accessibility is particularly challenging. For file inputs, the native HTML <input type="file"> is already reasonably accessible. The value Radix adds is through its other primitives (Progress for tracking, Dialog for upload modals, VisuallyHidden for labels) that you compose together.
Q: What's the difference between react-dropzone and building custom with Radix?
A: react-dropzone is excellent and production-tested — use it for most projects. The Radix custom approach makes sense when: (1) you have a strict design system and can't override react-dropzone's class assumptions, (2) you need very specific behavior not supported by react-dropzone's API, or (3) you're in an environment where bundle size is critical and react-dropzone is full overhead.
Q: How do I handle large file uploads (over 100MB) with progress?
A: Switch to multipart uploads using the AWS S3 Multipart Upload API. Files over 100MB should be split into 5–100MB chunks, each uploaded separately to S3 with a presigned URL, then combined using the CompleteMultipartUpload API call. The ai-sdk and @aws-sdk/client-s3 library both support this pattern. Ask the AI for a "S3 multipart upload with retry logic" prompt for the complete implementation.
Naveen Teja Palle
Frontend Architect · Accessibility Engineer
React specialist with expertise in headless UI libraries, WCAG accessibility compliance, and building production file management systems for SaaS applications. Writes engineering guides focused on practical implementation over theory.
1,000+ React Component Prompts
From Radix UI to Three.js — every advanced React pattern, accessible and production-ready.
Explore Web Component Prompts →