File upload
Dropzone with accept/size/count validation, a hidden file input, and a removable file list.
Primitive · form
Install
Section titled “Install”npx shadcn@latest add https://ndigo.dev/r/file-upload.jsonTo use the shorter @ndigo/file-upload form, configure the namespace in components.json.
| Source | Target | Type |
|---|---|---|
registry/ui/file-upload/nd-file-upload.ts |
@ui/nd-file-upload/nd-file-upload.ts |
registry:ui |
registry/ui/file-upload/nd-file-upload.css |
@ui/nd-file-upload/nd-file-upload.css |
registry:file |
Live example
Section titled “Live example”See this component rendered live on the live examples page.
Dependencies
Section titled “Dependencies”Registry: Ndigo element foundation.
Packages: @zag-js/file-upload@~1.47.0 (installed automatically).
Documentation
Section titled “Documentation”<nd-file-upload> opens the browser picker from the dropzone or trigger button,
accepts drops, validates files, and lists them with remove and clear actions.
The hidden file input carries the form data.
Markup
Section titled “Markup”<nd-file-upload name="attachments" accept="image/*,.pdf" max-files="3"> <label data-part="label">Attachments</label> <div data-part="dropzone">Drop files here or use the button below.</div> <button data-part="trigger" type="button">Choose files</button> <button data-part="clear-trigger" type="button">Clear</button></nd-file-upload>The element is the machine root and creates the hidden file input and the
item-group list. The dropzone is the clickable/drop surface; keep the trigger
separate so the dropzone does not nest a button inside its role="button".
Attributes
Section titled “Attributes”| Attribute | Type | Description |
|---|---|---|
name |
string |
Form field name for the file input |
accept |
string |
Comma-separated MIME types/extensions |
max-files |
number |
Maximum number of files (default 1) |
max-file-size |
number |
Maximum size per file in bytes |
min-file-size |
number |
Minimum size per file in bytes |
disabled |
boolean |
Disable the control |
readonly |
boolean |
Keep the value but block changes |
required |
boolean |
Mark the file input required |
invalid |
boolean |
Mark the control invalid |
max-files above 1 sets multiple on the input.
Events
Section titled “Events”nd-file-change—detail: { acceptedFiles: File[], rejectedFiles: { file, errors }[] }.data-valuelists accepted file names joined with", ", anddata-countholds the accepted count.
Methods
Section titled “Methods”openFilePicker()— open the browser picker.clear()— remove every accepted file.
Accessibility
Section titled “Accessibility”The dropzone is a labelled role="button" and responds to Enter/Space; the
trigger is a native button. The hidden file input is taken out of the tab order
and the accessibility tree because the dropzone/trigger provide the interface.
Each item’s remove button is labelled per file. Rejected files are reported
through the event rather than rendered, so the accepted list stays unambiguous.
Styling
Section titled “Styling”Import nd-file-upload.css after the tokens. The dropzone is a dashed surface
that switches to the accent tokens on hover, focus, and drag; items are compact
surface rows with a muted size label.