Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Input file

Input file is a field for picking one or more files, by click or drag-and-drop, through a native picker wrapped in a custom dropzone. It can show selected files as a thumbnail grid or as a row list with an icon, name, and size. Each file can be removed on its own, all at once, or as part of a checked selection, and clicking a file opens it in a new tab.

Dependencies

eun-label · if it has a label eun-icon · for the dropzone and file icons
Overview API Examples Accessibility

When to use

Reach for an input file whenever someone needs to attach one or more files to a form, such as a profile picture, a set of product photos, a signed PDF, or supporting documents for a claim. It shows files as thumbnails by default, better suited to images, or as a compact row list for anything else, such as PDFs, spreadsheets, or other attachments. For a single free-text value, reach for input instead, since this component is only for actual files. See Alternatives below for a direct comparison.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/input-file.js";
<eun-input-file name="photos" label="Photos" multiple></eun-input-file>

Importing the file registers <eun-input-file> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers A single free-text value, not actual files eun-input

Guidance

  • Set kind="file" whenever the accepted content isn't images: a row list with a name and size reads far better than a thumbnail grid for PDFs or documents
  • Set max-size whenever there's a real backend limit, so oversized files are rejected client-side instead of failing on submit
  • Only turn on selectable when someone plausibly needs to remove several files at once: for a handful of files, the per-item remove button is already enough
  • Using this component for a single free-text value: see Alternatives above
  • Relying on accept/max-size alone as a security boundary: always re-validate file type and size server-side
  • Overriding colors with inline styles instead of the --input-file-* CSS variables

Live testing

Properties

Input file <eun-input-file>

Attributes

NameTypeDefaultDescription
max-sizenumber | undefinedMaximum size, in bytes, accepted per file.
max-filesnumber | undefinedMaximum total number of files accepted. Only relevant with `multiple`.
drop-labelstring'Drag and drop files here, or'Static text displayed in the dropzone
browse-labelstring'browse files'The visible text of the dropzone's browse link
aria-label-removestring'Remove file'The accessible label of each item's remove button
aria-label-selectstring'Select file'The accessible label prefix of each item's checkbox
remove-all-labelstring'Remove all'The label of the always-available bulk clear action
select-all-labelstring'Select all'The label of the selection toolbar's select-all checkbox
delete-selected-labelstring'Delete selected'The label of the selection toolbar's bulk delete button
aria-label-openstring'Open in a new tab'Appended to each item's accessible name
kind'image' | 'file''image'The accepted content kind: image thumbnails or generic file rows
acceptstringThe native accept filter, forwarded to the picker and used to filter drops. Defaults to images when kind is image, unset otherwise
multiplebooleanfalseWhether more than one file can be selected
selectablebooleanfalseShows per-item checkboxes, a select-all toggle, and a bulk delete action once more than one file is present

Import the exact TypeScript type behind any property above, see Types.

Properties

JS-only — no matching HTML attribute, set these from a script or a template binding.

NameTypeDefaultDescription
labelstringThe title label of the field, rendered through the label component
instructionsstringInstructions displayed below the label
namestringThe field name
valueFile[]The currently selected files
disabledbooleanfalseWhether the field is disabled
readonlybooleanfalseWhether the field is read only
requiredbooleanfalseWhether at least one file is required
hideErrorbooleanfalseWhether the errors are hidden
validatorsArray<Validators<File[]>>The list of validation rules applied to the field value

Events

NameTypeDescription
eunchangeChangeEventFired whenever the selected files change
eunfilerejectFileRejectEventFired whenever one or more offered files are rejected, for the wrong type or exceeding the size or count limit

Every event above follows the same naming convention, covered in Events.

CSS custom properties

NameDescription
--input-file-border-colorSets the border color of the dropzone
--input-file-border-color-hoverSets the border color on hover
--input-file-border-color-focusSets the border color on focus
--input-file-border-radiusSets the corner radius of the dropzone and items
--input-file-backgroundSets the background color of the dropzone
--input-file-background-hoverSets the background color on hover or drag over
--input-file-text-colorSets the text color
--input-file-icon-colorSets the color of icons and action buttons
--input-file-error-colorSets the color of the error message
--input-file-hint-colorSets the color of the hint message
--input-file-focus-outline-colorSets the color of the focus outline
--input-file-item-backgroundSets the background color of each item
--input-file-item-border-colorSets the border color of each item
--input-file-gapSets the gap between the dropzone, header, and items
--input-file-thumbnail-sizeSets the minimum size of image thumbnails

Basic

Image kind (default)

<eun-input-file name="avatar" label="Avatar" hint="PNG or JPG"></eun-input-file>

File kind

<eun-input-file
  name="document"
  label="Document"
  kind="file"
  accept="application/pdf"
>
</eun-input-file>

Multiple

With multiple, the dropzone stays visible above the item list/grid so more files can keep being added.

<eun-input-file name="gallery" label="Gallery" multiple></eun-input-file>

Selectable, with bulk delete

selectable adds a checkbox to each item, plus a select-all checkbox and a "Delete selected" action once at least one is checked, independent from the always-available "Remove all".

<eun-input-file
  name="attachments"
  label="Attachments"
  kind="file"
  multiple
  selectable
>
</eun-input-file>

Restricting size and count

max-size (bytes) rejects oversized files, while max-files caps the total count once multiple is set. Both emit eunfilereject instead of silently dropping the file, so listen for it to surface why a file didn't make it in.

No rejections yet.
<eun-input-file
  name="receipts"
  label="Receipts"
  kind="file"
  multiple
  max-size="2000000"
  max-files="3"
  hint="Up to 3 files, 2 MB each"
>
</eun-input-file>
const field = document.querySelector("#receipts");

field.addEventListener("eunfilereject", (event) => {
  const summary = event.files
    .map(({ file, reason }) => `${file.name}: ${reason}`)
    .join("\n");
  console.log(summary);
});

States

Disabled

<eun-input-file label="Disabled" disabled></eun-input-file>

Readonly

A readonly field still shows and submits its files, but nothing can be added or removed.

<eun-input-file label="Readonly" readonly></eun-input-file>

Required

<eun-input-file
  label="Proof of address"
  kind="file"
  required
  hint="A recent utility bill or bank statement"
>
</eun-input-file>

Forms

Like every other Eunomia field, eun-input-file is a real form-associated custom element, so the selected File objects themselves are submitted under its name, exactly like a native <input type="file">.

Submit Reset
[]
<form id="my-form">
  <eun-input-file name="attachments" label="Attachments" kind="file" multiple>
  </eun-input-file>
  <button type="submit">Submit</button>
  <button type="reset">Reset</button>
</form>
const form = document.querySelector("#my-form");

form.addEventListener("eunchange", () => {
  const files = new FormData(form).getAll("attachments");
  console.log(files.map((file) => file.name));
});

Custom

Override the --input-file-* CSS variables, listed in full in the API tab.

<eun-input-file class="custom-input-file" label="Custom"></eun-input-file>
.custom-input-file {
  --input-file-border-color: #be185d;
  --input-file-border-color-hover: #be185d;
  --input-file-border-color-focus: #be185d;
  --input-file-focus-outline-color: #be185d;
}

Keyboard interactions

Key Action
Tab / Shift+Tab Moves focus in/out of the dropzone (or the first item, once a file already fills its place), then through each item and its own remove/checkbox buttons
Enter / Space on the dropzone Opens the native OS file picker, natively
Enter / Space on an item Opens that file in a new tab
Delete / Backspace on an item Removes that file directly, without needing to reach its own remove button
Enter / Space on a remove/checkbox/action button Activates that button, natively

Drag-and-drop is a pointer-only shortcut for the exact same selection the native picker already provides through the dropzone's Enter/Space, so nothing here is drag-and-drop-only.

Aria attributes

  • aria-invalid is not set directly (no native constraint beyond required, itself surfaced through checkValidity()/validity like every other field here)
  • aria-label on the native, visually-hidden file field: set from the label property directly, for the same cross-shadow-boundary reason documented on eun-input's own Accessibility tab (a plain-text aria-label instead of aria-labelledby/for pointing at the rendered <eun-label>)
  • aria-describedby="description": set on the native field whenever a hint or error message is actually rendered below it
  • aria-disabled on the dropzone: reflects both disabled and "max-files already reached"
  • Each item is role="button" with an aria-label combining the file name and a short "Open in a new tab" hint (aria-label-open), so its accessible name and its primary action always match
  • Each item's remove button and (when selectable) checkbox carry their own aria-label including the file name, so multiple items' controls stay distinguishable to assistive technology
  • The file count above the items ("3 files") is aria-live="polite", so additions and removals are announced without moving focus

Label accessibility

Always set label (or slot content into the label slot): describe what the files are for ("Proof of address", "Product photos"), not just that a file is expected.

Disabled vs. readonly

disabled removes the field from the tab order (tabindex="-1", aria-disabled="true"), disables every action, and excludes the value from form submission entirely. readonly keeps the field and its items focusable and their values submitted, but blocks adding, removing, or reordering, communicating "you can't change this right now", not "this doesn't apply". Prefer readonly over disabled whenever the already-selected files are still meaningful context for the user.

Required field

required behaves like a native required <input type="file">: an empty selection is invalid, any non-empty selection is valid, regardless of multiple.

Rejections aren't validation errors

Files rejected for type/size/max-files never reach value in the first place, so there's nothing invalid to report through checkValidity(). A native file picker behaves the same way, since you simply can't pick a mismatched file. Listen for eunfilereject if you want to surface why a specific file didn't make it in (see the Examples tab).

Reference links

WAI Web Accessibility Tutorials: Labeling Controls
WHATWG HTML: The file upload state of the input element