Forms & inputs
Animated File Upload
Physical drag-and-drop file uploader with per-file progress morphing
detent-animated-file-upload
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-forms
- Group
- Forms & inputs
8.2 KB gzip for the class module
Variants
Every upload on this page is simulated: choosing a file starts the documented 180 ms progress simulation and no request leaves the page.
Choose a file to see the events.
Recorded states
disabledrecorded state; no settererrorrecorded state; no setterreduced motionrecorded state; no settercompletedrecorded state; no setter
Usage
Import
import '/components/animated-file-upload/define.js';
// or register it yourself
import { DetentAnimatedFileUpload } from '/components/animated-file-upload/animated-file-upload.js';
Attributes
disableddrop-subtitledrop-titlemax-filesmax-sizemultiplenamerequiredvariant
Properties
acceptinitialFilesuploadHandlermultiplemaxSizemaxFilesdropTitledropSubtitlevariantdisablednamerequiredonFilesSelectedonUploadComplete
Slots
No slots are recorded for this component.
Events
detent-files-selected— { files: File[] }detent-upload-complete— { file: UploadFileItem }detent-progress— { progress: number }
Example
Runnable example: site/examples/animated-file-upload.ts. The snippet below is generated from the catalogued attributes.
<detent-animated-file-upload
disabled
drop-subtitle="or browse from your device"
drop-title="Drop files here"
max-files="10"
max-size
multiple
name
required
variant="standard"
></detent-animated-file-upload>
Property-only values
const element = document.querySelector('detent-animated-file-upload');
if (element !== null) {
// accept: string | string[]
element.accept = [];
// onFilesSelected: (files: File[]) => void
element.onFilesSelected = () => {};
// onUploadComplete: (file: UploadFileItem) => void
element.onUploadComplete = () => {};
// uploadHandler: (file, onProgress) => Promise<void>
element.uploadHandler = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/AnimatedFileUpload.tsxsha256 63bcb0d4ca27…
Native files
The native module and its artifacts:
src/components/animated-file-upload/animated-file-upload.tssrc/components/animated-file-upload/styles.csssrc/components/animated-file-upload/define.tsdocs/components/animated-file-upload.mdsite/examples/animated-file-upload.tstests/browser/animated-file-upload.spec.tsevidence/components/animated-file-upload.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
disabled | disabled | boolean | false | Presence semantics: present means true, absent means false. |
drop-subtitle | dropSubtitle | string | 'or browse from your device' | Kebab-case attribute; camelCase property. |
drop-title | dropTitle | string | 'Drop files here' | Kebab-case attribute; camelCase property. |
max-files | maxFiles | number | 10 | Kebab-case attribute; camelCase property. |
max-size | maxSize | number | 26214400 (25MB) | Kebab-case attribute; camelCase property. |
multiple | multiple | boolean | true | Presence semantics: present means true, absent means false. |
name | — | — | — | Recorded in the catalog as an attribute. |
required | — | — | — | Recorded in the catalog as an attribute. |
variant | variant | 'standard' | 'compact' | 'standard' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
accept | string | string[] | undefined | Property-only: assign from script; not accepted as an attribute. |
initialFiles | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
uploadHandler | (file, onProgress) => Promise<void> | undefined | Property-only: assign from script; not accepted as an attribute. |
multiple | boolean | true | Property-only: assign from script; not accepted as an attribute. |
maxSize | number | 26214400 (25MB) | Property-only: assign from script; not accepted as an attribute. |
maxFiles | number | 10 | Property-only: assign from script; not accepted as an attribute. |
dropTitle | string | 'Drop files here' | Property-only: assign from script; not accepted as an attribute. |
dropSubtitle | string | 'or browse from your device' | Property-only: assign from script; not accepted as an attribute. |
variant | 'standard' | 'compact' | 'standard' | Property-only: assign from script; not accepted as an attribute. |
disabled | boolean | false | Property-only: assign from script; not accepted as an attribute. |
name | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
required | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onFilesSelected | (files: File[]) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
onUploadComplete | (file: UploadFileItem) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-files-selected | onFilesSelected | (files: File[]) => void | Composed, bubbles; fires only for a user action. |
detent-upload-complete | onUploadComplete | (file: UploadFileItem) => void | Composed, bubbles; fires only for a user action. |
detent-progress | onProgress | (progress: number) => void | Composed, bubbles; fires only for a user action. |
Slots
No slots are recorded for this component.
CSS customisation
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- `accept` is enforced in the component with "File type is not accepted (accept: ...)" (the row records it as a documented constraint; upstream only passed it to the picker), while size rejection keeps the upstream message.
- `multiple` follows the accepted default-true rule (attribute reflected, `multiple="false"` resolves true, the property opts out).
- Native form participation added: name, required, reset to `initialFiles`, formStateRestoreCallback, setValidity, external `<label for>`, and a submitted FormData of the queued raw files (or the comma-joined names when only seeded names exist).
- `initialFiles` is assignable after connection: it replaces the queue and becomes the reset default.
- Framer Motion drag reaction and status morph are CSS transitions plus text and progress updates; reduced motion removes them.
- The upstream dark-only hex palette is replaced by theme tokens so both schemes work.
- `maxFiles` truncation stays silent (upstream `combined.slice(0, maxFiles)`); only size and type rejections message and announce.
- The upload is a documented 180ms simulation that makes no request; a consumer `uploadHandler` replaces it entirely.
What it does
A real <input type="file"> behind a drag-and-drop zone, a per-file queue with independent Uploading → Processing → Complete stages, rejection with a message, cancellation, retry, and one live region for announcements.
Upstream behaviour
- Subtle physical dropzone scaling and border reaction without exaggerated AI glow
- Automatic mime-type detection and contextual file icon attribution
- Multi-file queue management with independent Uploading → Processing → Complete stages
- Self-morphing progress bar into checkmark state with non-aggressive error recovery
- Customizable file constraints (maxSize, maxFiles, accept) with accessible screen reader labels
Differences from upstream
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- `accept` is enforced in the component with "File type is not accepted (accept: ...)" (the row records it as a documented constraint; upstream only passed it to the picker), while size rejection keeps the upstream message.
- `multiple` follows the accepted default-true rule (attribute reflected, `multiple="false"` resolves true, the property opts out).
- Native form participation added: name, required, reset to `initialFiles`, formStateRestoreCallback, setValidity, external `<label for>`, and a submitted FormData of the queued raw files (or the comma-joined names when only seeded names exist).
- `initialFiles` is assignable after connection: it replaces the queue and becomes the reset default.
- Framer Motion drag reaction and status morph are CSS transitions plus text and progress updates; reduced motion removes them.
- The upstream dark-only hex palette is replaced by theme tokens so both schemes work.
- `maxFiles` truncation stays silent (upstream `combined.slice(0, maxFiles)`); only size and type rejections message and announce.
- The upload is a documented 180ms simulation that makes no request; a consumer `uploadHandler` replaces it entirely.