File Upload

v1.0.0

Accessible 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

No files selected.
<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

No files selected.

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);