v1.6.x-dev NDS IQ v6

File Upload - National Design System

A file uploader with drag-and-drop or compact browse modes that validates and lists selected files, then sends them to your server or with a form submit

File Upload

Two modes for collecting files: a drag-and-drop zone for prominent upload areas, or a compact browse button for inline forms

Drag and drop files here to upload
Maximum file size allowed is 2MB, supported file formats include .jpg, .png, and .pdf.
<div class="nds-form-container nds-file-upload" data-state="dropbox"> <div class="nds-form-header"> <label for="fileUploadInput"> <span class="nds-label">Upload files</span> <span class="nds-info">Maximum file size allowed is 2MB, supported file formats include .jpg, .png, and .pdf.</span> </label> </div> <div class="nds-form-control"> <input type="file" id="fileUploadInput" multiple accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.txt" class="nds-file-input" /> <div class="nds-upload-zone"> <i class="hgi hgi-stroke hgi-file-upload nds-upload-icon"></i> <div class="nds-upload-text"> <span class="nds-drop-hint">Drag and drop files here to upload</span> </div> <div class="nds-upload-hint">Maximum file size allowed is 2MB, supported file formats include .jpg, .png, and .pdf.</div> </div> <div class="nds-form-action"> <button type="button" class="nds-btn nds-neutral nds-md nds-browse-btn"> <i class="hgi hgi-stroke hgi-folder-01"></i> <span class="nds-label">Browse Files</span> </button> </div> </div> <div class="nds-file-list"></div> <div class="nds-form-footer"></div> <!-- Hidden template for file items --> <div class="nds-file-item-template" style="display: none;"> <div class="nds-file-item"> <span class="nds-feedback"> <span class="nds-feedback-icon"> <i class="nds-icon" aria-hidden="true"></i> </span> </span> <div class="nds-progress-circle" style="--progress-size: 24px; --progress-value: 0;"> <svg width="24" height="24" viewBox="0 0 24 24"> <circle class="nds-progress-bg" cx="12" cy="12" r="10" fill="none" stroke-width="3" /> <circle class="nds-progress-track" cx="12" cy="12" r="10" fill="none" stroke-width="3" stroke-dasharray="62.83" stroke-dashoffset="62.83" stroke-linecap="round" /> </svg> <div class="nds-progress-info"> <span class="nds-progress-percentage"> <span class="nds-progress-number"></span> </span> </div> </div> <div class="nds-file-info"> <div class="nds-file-name nds-truncate"></div> <div class="nds-file-error"> <span class="nds-error-message"></span> </div> </div> <div class="nds-file-actions"> <button type="button" class="nds-btn nds-subtle nds-md nds-icon-only nds-remove-file" aria-label="Remove file"> <i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i> </button> </div> </div> </div> </div>

Built-in Features

Auto-initialization

Activates when .nds-file-upload is on the page. Dynamic elements added later are picked up automatically.

Drag and Drop

Files can be dragged onto the drop zone with visual feedback on hover. Toggled on and off with data-state="dropbox".

Client-side Validation

Validates file size, extension, and MIME type before upload. Rejected files appear in the list with an error message in Arabic or English.

Security

File names are sanitized to strip path traversal sequences, null bytes, and control characters before display and upload.

Upload Lifecycle

Five status stages (ready, uploading, processing, complete, error) with progress tracking, retry for failures, and abort for in-progress uploads.

Programmatic Control

Full JavaScript API to add, remove, upload, retry, and abort files. Intercept uploads via the cancelable beforeUpload event to set custom headers.

Bilingual Messages

Error and validation messages display in Arabic or English based on the page language setting.

Event-driven Integration

Nine custom events cover the full upload lifecycle, letting you hook into file selection, progress updates, success, and error handling.

Sending Files to Your Server

The component owns the file picker, validation, and the on-screen list; your code decides where the files go. Two patterns cover almost every case, chosen by file size and whether you want per-file progress.

PatternHow it worksBest for
Bundle on submitNo data-upload-url. Files stay in the component until you read them on submit and POST them with the rest of the form to a single endpoint.Forms and small attachments, atomic submit, simplest backend
Upload as you goSet data-upload-url with data-auto-upload="true" (or a manual button calling startUpload()). Each file uploads on its own with a progress ring, then a success check or a retry. The submit then references the uploaded files.Large files and media, when you want per-file progress and retry
// ── Pattern 1: Bundle on submit (no data-upload-url) ── // Send the files WITH the form fields, in a single request. form.addEventListener('nds:formValid', (e) => { e.preventDefault(); // you are sending it yourself const api = NDS.Upload.getInstance('.nds-file-upload'); const data = new FormData(form); // your text fields api.getAllFiles().forEach(f => data.append('attachments[]', f.file)); fetch(form.action, { method: 'POST', body: data }); }); // ── Pattern 2: Upload as you go (set data-upload-url) ── // Auto-upload each file the moment it is picked: // <div class="nds-file-upload" data-upload-url="/api/files" data-auto-upload="true">...</div> // Or trigger from your own button instead of data-auto-upload: uploadButton.addEventListener('click', () => { NDS.Upload.getInstance('.nds-file-upload').startUpload(); });

Three things to know with either pattern:

  • Files never ride a native form submit. The component clears the native <input> after selection, so always send them with getAllFiles() or data-upload-url.
  • data-upload-url receives one file per request, not all of them at once, so the endpoint should accept a single file field per POST.
  • On a failed upload, the file row shows the server's message when the response body is JSON with an error field (e.g. {"error": "Quota exceeded"}), falling back to the HTTP status text, then a localized generic message. Override manually anytime with setFileStatus(fileId, 'error', { error }).
  • The input's accept attribute only hints the OS picker and is advisory. Just set data-allowed-types (which actually enforces extensions) and the component fills accept from it automatically, so you never hand-write the picker filter or risk it drifting from what is enforced.

Usage Guidelines

Best Practices

  • Use the drop zone mode (data-state="dropbox") for dedicated upload areas where file selection is the primary action on the page
  • Use the browse button mode (no dropbox state) when file upload is one field among many in a form
  • Use single file mode (data-state="single") for profile photos, document replacements, or anywhere only one file is expected
  • Always set data-max-file-size and data-allowed-types to give users immediate validation feedback rather than waiting for server rejection
  • Set data-max-files when the server has a file count limit. Excess files appear in the list with an error so users understand why they were rejected
  • Use the nds:upload:beforeUpload event to add authorization headers, CSRF tokens, or extra form fields. The component does not handle authentication.
  • Do not use this component for large file transfers (500MB+) that need chunked upload or resumable protocols. Build a custom solution with the events API as a starting point
  • Server-side validation must duplicate all client-side checks. Client validation improves UX but cannot be trusted for security
  • Combine data-allowed-types (extension) with data-allowed-mime-types for defense in depth: extensions can be spoofed, MIME types add a second check
  • Add aria-live="polite" to the .nds-file-list so newly added rows and per-file validation errors are announced to screen-reader users.
  • The hidden .nds-file-item-template is optional: when omitted, the component renders rows from its built-in markup. Supply your own template only to customize the per-file row.

Data Attributes

AttributeDescription
data-state="dropbox"Enables the drag-and-drop zone UI with dashed border and upload icon
data-state="single"Single file mode: new selection replaces the current file
data-upload-urlServer endpoint for XHR file uploads (POST)
data-auto-upload="true"Automatically upload files on selection instead of waiting for startUpload()
data-max-file-sizeMaximum file size in bytes. Default: 10485760 (10 MB)
data-max-filesMaximum number of files allowed. Default: unlimited
data-allowed-typesComma-separated file extensions: jpg,png,pdf
data-allowed-mime-typesComma-separated MIME types, supports wildcards: image/*,application/pdf

Events

Every fileData payload is the consistent shape { file, id, status, progress, error }. For selected it is an array of these, and for validationError each errors[] entry carries one as its fileData.

EventDetail
nds:upload:ready{ instance }
nds:upload:selected{ files, allFiles, fileData }
nds:upload:validationError{ errors }
nds:upload:beforeUpload (cancelable){ fileData, formData, xhr }
nds:upload:progress{ fileData, progress }
nds:upload:success{ fileData, response }
nds:upload:error{ fileData, error, status?, response? }: status (HTTP status code) and response (raw response body) are present for HTTP errors only; network-level errors omit them
nds:upload:removed{ fileData, fileId }
nds:upload:maxFilesReached{ maxFiles, currentCount }

JavaScript API

The NDS.Upload API provides static methods to access instances and instance methods to manage files, trigger uploads, and control the component state.

// ── Static methods ────────────────────────────────── NDS.Upload.init(); // Initialize all .nds-file-upload on page NDS.Upload.reinit(); // Re-scan DOM after dynamic changes NDS.Upload.create(element, options); // Create instance; returns it (or the existing one), null if it can't init NDS.Upload.getInstance('.nds-file-upload'); // Get instance by selector or element NDS.Upload.whenReady('.nds-file-upload', fn); // Call fn(instance) when ready // ── Configure in JS (options override the data-* attributes) ── NDS.Upload.create('.nds-file-upload', { uploadUrl: '/api/upload', autoUpload: true, maxFileSize: 2 * 1024 * 1024, // bytes maxFiles: 3, allowedTypes: ['jpg', 'png', 'pdf'], // or the 'jpg,png,pdf' string allowedMimeTypes: ['image/*', 'application/pdf'] }); // ── File management ───────────────────────────────── const upload = NDS.Upload.getInstance('.nds-file-upload'); const fileId = upload.addFile(file, { // Add file to queue status: 'ready', // 'ready' | 'uploading' | 'processing' | 'complete' | 'error' progress: 0, // 0-100 error: null, // Error message string validate: false // true → run size/type/MIME checks (sets 'error' on failure) }); // Returns fileId or null if max files reached upload.removeFile(fileId); // Remove file, abort if uploading upload.clearAllFiles(); // Remove all files, abort all uploads upload.getFile(fileId); // Returns { file, id, status, progress, error } upload.getAllFiles(); // Returns array of all file objects upload.getFilesByStatus('error'); // Filter by status // ── Upload control ────────────────────────────────── upload.startUpload(fileId); // Upload specific file upload.startUpload(); // Upload all 'ready' files upload.retry(fileId); // Reset error file and re-upload upload.abort(fileId); // Cancel in-progress upload // ── Status and progress ───────────────────────────── upload.setFileStatus(fileId, 'error', { error: 'Server rejected file' }); upload.setFileProgress(fileId, 75); // Auto-transitions to 'processing' at 100% // ── Component control ─────────────────────────────── upload.setDisabled(true); // Disable input, drag-and-drop, and buttons upload.refreshUI(); // Force full UI rebuild upload.getConfig(); // Returns frozen copy of current config upload.validateFile(file); // Size/type/MIME checks against live config, nothing staged — [] on pass, [messages] on fail upload.destroy(); // Remove listeners, abort uploads, clean DOM // ── Intercept uploads for custom headers ──────────── const el = document.querySelector('.nds-file-upload'); el.addEventListener('nds:upload:beforeUpload', (e) => { e.detail.xhr.setRequestHeader('Authorization', 'Bearer ' + token); e.detail.formData.append('folder', 'documents'); // e.preventDefault() cancels the upload });
Last Modified Date: 19/07/2026 - 03:40 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks