File Upload
v1.0.0Accessible native file selection with optional drag-and-drop presentation.
Single file
Keep a visible label and the native file input. The browser-provided browse button remains keyboard accessible and continues to expose the selected filename without custom scripting.
PNG or JPEG, up to 2 MB.
Choose a file or drop it here
<div class="file-upload">
<label class="form-label" for="avatar-file">Profile image</label>
<p class="file-upload-hint" id="avatar-file-hint">PNG or JPEG, up to 2 MB.</p>
<div class="file-upload-dropzone">
<p class="file-upload-prompt">Choose a file or drop it here</p>
<input
class="file-upload-input"
id="avatar-file"
name="avatar"
type="file"
accept="image/png,image/jpeg"
aria-describedby="avatar-file-hint avatar-file-summary" />
</div>
<div class="file-upload-summary" id="avatar-file-summary" aria-live="polite">
No files selected.
</div>
</div>
accept is a picker hint, not validation. Validate the actual file type and size in the application and again on the server. Never trust a filename, extension or client-provided MIME type by itself.
Multiple files and selected-file summary
Add the native multiple attribute. This Showcase example updates the summary after selection; applications can use the same live region while keeping the input itself visible.
Select one or more PDF documents, up to 10 MB each.
Choose files or drop them here
The optional summary uses a semantic list and .file-upload-file-name to wrap long, unbroken names. Announce one concise summary per change; do not announce drag pointer movement.
Validation
Set aria-invalid="true" only after validation fails and include the visible error in aria-describedby. The error is not inferred from accept: application code owns file type, size, count and upload-result validation.
PDF, up to 10 MB.
Choose a PDF smaller than 10 MB.
<input
class="file-upload-input is-invalid"
id="invalid-file"
type="file"
aria-invalid="true"
aria-describedby="invalid-file-hint invalid-file-error" />
<p class="file-upload-feedback file-upload-feedback-error" id="invalid-file-error">
Choose a PDF smaller than 10 MB.
</p>
Disabled
Use the native disabled attribute. Do not rely on subdued styling alone.
Drag and drop
Drag and drop is progressive enhancement. Keep the native browse control as the equivalent path, toggle .is-dragover only while accepted file items are over the target, and remove it on drop, drag leave and cancellation.
Visual preview of the active drop target.
Drop files to add them
Only accept DataTransferItem entries whose kind is file, ignore directory traversal APIs unless the product explicitly supports and audits them, and pass dropped files through the same count, type and size validation as files chosen through the picker. Do not remove the input, set it to display: none, or replace it with a pointer-only drop target.
CSS Variables
Override these variables on .file-upload or an ancestor.
--file-upload-gap: var(--spacer-2);
--file-upload-dropzone-gap: var(--spacer-3);
--file-upload-dropzone-padding: var(--spacer-4);
--file-upload-dropzone-bg-color: var(--bg-surface);
--file-upload-dropzone-border-width: var(--border-width);
--file-upload-dropzone-border-color: var(--control-border-color);
--file-upload-dropzone-border-radius: var(--border-radius-lg);
--file-upload-dropzone-hover-border-color: var(--primary-hover);
--file-upload-dropzone-active-bg-color: var(--bg-highlight);
--file-upload-dropzone-active-border-color: var(--primary);
--file-upload-input-bg-color: var(--bg-body);
--file-upload-input-border-color: var(--control-border-color);
--file-upload-input-border-radius: var(--border-radius);
--file-upload-input-padding: var(--spacer-2);
--file-upload-button-bg-color: var(--bg-highlight);
--file-upload-button-hover-bg-color: var(--bg-active);
--file-upload-button-color: var(--text-color);
--file-upload-button-padding-y: var(--spacer-1);
--file-upload-button-padding-x: var(--spacer-3);
--file-upload-summary-color: var(--text-muted);
--file-upload-invalid-color: var(--error);
--file-upload-disabled-text-color: var(--disabled-text-color);
--file-upload-disabled-bg-color: var(--disabled-bg-color);
--file-upload-disabled-border-color: var(--disabled-border-color);
--file-upload-focus-border-color: var(--primary-hover);
--file-upload-focus-outline-width: var(--focus-outline-width);
--file-upload-focus-outline-style: var(--focus-outline-style);
--file-upload-focus-outline-color: var(--focus-outline-color);
--file-upload-focus-outline-offset: var(--focus-offset);
--file-upload-transition-duration: var(--transition-duration);
--file-upload-transition-easing: var(--transition-easing);