Skip to content

FRD: File Upload Recipe

Document Summary

FieldDetails
Feature NameFile Upload Seed Example
StatusDraft
OwnerDavid Holmes
ContributorsEngineering
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #78, ADR-027 (Default Tech Stack)
Last Updated2026-05-26

Introduction

Overview

The File Upload recipe provides a runnable example folder demonstrating a complete upload flow: file selection via file-dropzone.tsx, upload progress tracking, client-side validation, an uploaded asset list, and a signed-URL handoff pattern for secure server-side uploads. The recipe ships with stories, unit tests, and copy-pasteable code that developers can adapt for their applications.

Goals

  • Demonstrate a complete upload flow using the existing file-dropzone.tsx component.
  • Show upload progress tracking with cancel support.
  • Include client-side validation (file type, size, count limits).
  • Provide an uploaded asset list with preview, remove, and retry actions.
  • Document the signed-URL handoff pattern for secure uploads to cloud storage.
  • Ship as a runnable example folder with stories and tests.

Non-Goals

  • Building a new file-upload component (uses existing file-dropzone.tsx).
  • Implementing a real backend or cloud storage integration.
  • Providing image editing, cropping, or annotation.
  • Building a file manager or document management system.
  • Server-side virus scanning or content moderation.

Scope

In Scope

AreaDescription
Recipe folderSelf-contained example folder with source, stories, and tests
File selectionIntegration with file-dropzone.tsx for drag-and-drop and click-to-select
Upload progressProgress bar per file with percentage and cancel button
Client-side validationFile type allowlist, max file size, max file count
Validation error displayInline errors per file; rejected files listed with reasons
Uploaded asset listList of completed uploads with preview thumbnail, filename, size, and actions
Remove actionRemove a file from the upload list (before or after upload)
Retry actionRetry a failed upload
Signed-URL handoffDocumented pattern for requesting a signed URL from the server, uploading directly to storage
StoriesStorybook stories demonstrating each state
Unit testsTests covering validation, progress, and asset list behavior

Out of Scope

AreaReason
New file-upload primitiveUses existing file-dropzone.tsx
Real backend/cloud storageRecipe uses mock upload functions
Image editing/croppingSeparate concern
Chunked/resumable uploadsAdvanced feature deferred to future iteration
Server-side validationConsumer responsibility

Users and Pain Points

User Groups

UserDescriptionNeeds
Application developersEngineers building file upload featuresA complete reference implementation they can copy and customize
QA engineersTesters verifying upload flowsStories showing all upload states for visual testing
New team membersEngineers learning design system patternsA working example with tests showing best practices

Pain Points

UserPain PointImpact
Application developersfile-dropzone.tsx exists but there is no example showing the complete upload flowDevelopers build incomplete flows; miss progress tracking, validation, or error handling
Application developersNo documented pattern for signed-URL uploadsEach team invents its own server handoff pattern
Application developersUpload state management (pending, uploading, complete, failed) is built from scratch each timeDuplicated, inconsistent implementations

Definitions

TermDefinition
RecipeA runnable example folder with source code, stories, and tests that developers copy and adapt
Signed URLA time-limited, pre-authenticated URL that allows direct upload to cloud storage without exposing credentials
Upload progressA per-file percentage (0-100) indicating upload completion
ValidationClient-side checks on file type, size, and count before upload begins
Asset listA UI list showing uploaded files with preview, metadata, and actions

Current State

Existing Behavior

file-dropzone.tsx provides a drag-and-drop file selection area with configurable accept types and multiple file support. It handles file selection events but does not manage uploads, progress, validation, or asset display.

Current Limitations

  • No upload progress tracking pattern.
  • No client-side validation example (type, size, count).
  • No uploaded asset list component or pattern.
  • No signed-URL handoff documentation.
  • No runnable example tying the flow together.

Existing Workarounds

  • Developers build upload flows from scratch using file-dropzone.tsx as the starting point.
  • Progress tracking is often omitted or implemented with varying quality.
  • Signed-URL patterns are passed via tribal knowledge.

Proposed Solution

Summary

Create a recipe folder (src/recipes/file-upload/) containing a composed upload flow, a mock upload function with progress simulation, client-side validation logic, an uploaded asset list, and documentation for the signed-URL handoff pattern. Ship with Storybook stories and Vitest unit tests.

Key Capabilities

  • FileUploadDemo component composing FileDropzone, progress indicators, validation, and asset list.
  • useFileUpload hook managing upload state: pending, uploading (with progress), complete, failed.
  • validateFiles(files, config) function checking type, size, and count constraints.
  • UploadedAssetList component showing completed uploads with preview, metadata, and actions.
  • mockUpload(file) function simulating progress and completion for stories and tests.
  • Signed-URL handoff pattern documented in the recipe MDX page.

User Experience

Developers browse the recipe in Storybook, see the upload flow in action via stories, and copy the code into their application. They replace mockUpload with their real upload function (including signed-URL request) and customize the validation config.

Developer Experience

The recipe is a self-contained folder. Developers copy it, wire up their backend, and have a working upload flow. The useFileUpload hook encapsulates state management. Validation is configurable via a typed config object.


Requirements

IDRequirementPriorityNotes
FR-001The recipe must integrate with file-dropzone.tsxMustExisting component
FR-002The recipe must show upload progress per fileMustProgress bar with percentage
FR-003The recipe must include client-side validationMustType, size, count
FR-004The recipe must show an uploaded asset listMustPreview, filename, size, actions
FR-005The recipe must document the signed-URL handoff patternMustMDX documentation
FR-006The recipe must ship with storiesMustVisual reference
FR-007The recipe must ship with unit testsMustQuality gate
FR-008The recipe should support cancel during uploadShouldUX improvement
FR-009The recipe should support retry on failed uploadsShouldError recovery

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001useFileUpload hook accepts a config with accept, maxSizeMB, maxFilesConfigurable validationMust
FUNC-002useFileUpload returns { files, addFiles, removeFile, retryFile, cancelFile, clearAll }Complete state managementMust
FUNC-003Each file in files has { id, file, status, progress, error, previewUrl }Per-file state trackingMust
FUNC-004Status transitions: pending -> uploading -> complete or failedClear lifecycleMust
FUNC-005Files that fail validation are added with status: 'rejected' and an error messageInline error displayMust
FUNC-006Upload progress updates from 0 to 100 during uploadProgress visibilityMust
FUNC-007cancelFile(id) aborts an in-progress upload and sets status to cancelledUser can stop unwanted uploadsShould
FUNC-008retryFile(id) re-initiates upload for a failed fileError recoveryShould
FUNC-009UploadedAssetList renders completed files with thumbnail preview, filename, size, and a remove buttonVisual asset managementMust
FUNC-010Recipe MDX documents the signed-URL pattern: (1) request signed URL from server, (2) upload directly to storage, (3) confirm upload with serverSecure upload guidanceMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Recipe code must be copy-pasteable and self-containedMaintainabilityMust
NFR-002All recipe code must pass TypeScript strict modeCorrectnessMust
NFR-003Stories must render without errors in light and dark themesCompatibilityMust
NFR-004Upload progress must not cause excessive re-renders (throttle updates to ~100ms intervals)PerformanceShould
NFR-005Preview thumbnails must use URL.createObjectURL and revoke on cleanupPerformanceMust
NFR-006Tests must not perform real network requestsTestingMust

API / Interface Requirements

Public API

This is a recipe, not a published package. No new public API is exported from the design system. The recipe provides copy-pasteable code.

NameTypeDescriptionRequired
Recipe foldersourcesrc/recipes/file-upload/ with components, hook, and utilitiesYes
Recipe MDXdocumentationStorybook MDX page documenting the patternYes
Recipe storiesstoriesStorybook stories showing all statesYes
Recipe teststestsVitest tests covering hook and validationYes

Example Usage

import { FileDropzone } from "@dmwd/design-system";
import { useFileUpload, UploadedAssetList } from "./file-upload";
function UploadPage() {
const { files, addFiles, removeFile, retryFile } = useFileUpload({
accept: ["image/png", "image/jpeg", "application/pdf"],
maxSizeMB: 10,
maxFiles: 5,
uploadFn: async (file, onProgress) => {
const { url } = await requestSignedUrl(file.name, file.type);
await uploadToStorage(url, file, onProgress);
},
});
return (
<div>
<FileDropzone onDrop={addFiles} accept="image/*,.pdf" />
<UploadedAssetList
files={files}
onRemove={removeFile}
onRetry={retryFile}
/>
</div>
);
}

API Notes

  • useFileUpload accepts an uploadFn that receives the file and an onProgress callback.
  • The mockUpload function in the recipe simulates progress with configurable delay and failure rate.
  • previewUrl is generated via URL.createObjectURL for image files; non-image files show a file-type icon.

Accessibility Requirements

IDRequirementNotes
A11Y-001File dropzone must be keyboard-accessible (Enter/Space to open file picker)Existing file-dropzone.tsx behavior
A11Y-002Upload progress must be announced to screen readersaria-live="polite" on progress or role="progressbar"
A11Y-003Remove and retry buttons must have accessible labels including the filename”Remove invoice.pdf”, “Retry report.xlsx”
A11Y-004Validation errors must be associated with the rejected filearia-describedby linking error to file entry
A11Y-005Cancel button must have an accessible label”Cancel upload of photo.jpg”

Checklist

  • Keyboard support is defined.
  • Focus behavior is defined.
  • Screen reader behavior is defined.
  • Color contrast requirements are met.
  • Reduced motion behavior is considered.
  • Semantic HTML expectations are documented.
  • ARIA usage is defined only where needed.

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Recipe MDX page with complete upload flow documentationStorybook MDXMust
DOC-002Signed-URL handoff pattern explanation with sequence diagramStorybook MDXMust
DOC-003Validation configuration guideStorybook MDXMust
DOC-004”How to customize” section for replacing mockUpload with real uploadStorybook MDXMust
DOC-005Accessibility notes for the upload flowStorybook MDXMust

Documentation Should Include

  • Overview of the upload flow
  • When to use this recipe
  • Prerequisites
  • File selection and validation
  • Upload progress and state management
  • Signed-URL handoff pattern
  • Asset list and actions
  • Customization guide
  • Accessibility notes
  • Testing notes
  • Common mistakes (e.g., not revoking object URLs, not throttling progress)

Dependencies

DependencyTypeOwnerStatusNotes
file-dropzone.tsxDesign SystemDavid HolmesReadyExisting component
React 18+EngineeringDavid HolmesReadyPeer dependency
VitestEngineeringDavid HolmesReadyTest runner

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Recipe uses mock upload; real integrations vary significantlyDevelopers may need substantial adaptation for their backendDocument the signed-URL pattern clearly; keep the upload interface generic
No chunked/resumable upload supportLarge files may fail on slow connectionsDocument as a known limitation; suggest chunked upload as a future enhancement
Preview thumbnails consume memory for large image filesBrowser memory pressure with many large filesRevoke object URLs on cleanup; document memory considerations
Recipe folder structure may not match all project conventionsDevelopers may need to reorganize filesKeep the folder flat and simple; document how to adapt

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should the recipe include drag-and-drop reordering of the asset list?David HolmesOpen
Q-002Should the recipe show inline image preview during upload (before completion)?David HolmesOpen
Q-003Should useFileUpload support concurrent upload limits (e.g., max 3 simultaneous)?David HolmesOpen
Q-004Should the recipe include a file-type icon mapping utility?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001Recipe integrates with file-dropzone.tsx for file selectionFR-001
AC-002Upload progress is displayed per file as a percentageFR-002
AC-003Files exceeding size limit are rejected with an inline error messageFR-003
AC-004Files with disallowed types are rejected with an inline error messageFR-003
AC-005Uploaded asset list shows preview, filename, size, and remove buttonFR-004
AC-006Recipe MDX documents the signed-URL handoff patternFR-005
AC-007Stories demonstrate: empty state, file selected, uploading, complete, failed, rejectedFR-006
AC-008Unit tests cover validation, progress state transitions, and cancel/retryFR-007
AC-009All recipe code passes TypeScript strict modeNFR-002
AC-010Object URLs are revoked on component unmountNFR-005

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • Use the existing file-dropzone.tsx component; do not rebuild it.
  • The useFileUpload hook must accept a generic uploadFn so consumers can inject their own upload logic.
  • Use URL.createObjectURL for previews and revoke them on cleanup.
  • Throttle progress updates to avoid excessive re-renders.
  • The mock upload function should support configurable failure probability for testing error states.
  • Place the recipe in src/recipes/file-upload/ or a similar recipe folder.

LLM Should Not

  • Invent undocumented product behavior.
  • Implement real cloud storage uploads.
  • Add new dependencies without justification.
  • Change the existing file-dropzone.tsx component.
  • Skip object URL cleanup.
  • Perform real network requests in tests.

Decision Log

DateDecisionReasonOwner
2026-05-26Recipe with runnable folder, not a published packageRecipes give developers full control and understandingDavid Holmes
2026-05-26Generic uploadFn injection in useFileUploadKeeps the hook testable and backend-agnosticDavid Holmes
2026-05-26Client-side validation only (no server-side)Server-side validation is consumer responsibility; recipe focuses on the UI flowDavid Holmes
2026-05-26Signed-URL pattern documented, not implementedReal implementation requires backend; recipe shows the contractDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft