GitHub

File Upload

Form-associated drop zone with drag & drop, browse, size validation, and a removable file list. A Lit Web Component migrated from the Angular esa-file-upload.

Stable wc import '@esa/ecology/esa-file-upload';

Preview

<esa-file-upload label="Upload a document"></esa-file-upload>

States

Allow multiple files with multiple + accept, cap each file with max-size-mb, or disable the zone entirely.

<esa-file-upload label="Upload images" multiple accept="image/*"></esa-file-upload>
<esa-file-upload label="Max 2 MB" max-size-mb="2"></esa-file-upload>
<esa-file-upload label="Upload disabled" disabled></esa-file-upload>

API

PropTypeDefaultDescription
label string 'Upload files' Prompt text inside the drop zone.
accept string '' Native accept filter (e.g. image/*, .pdf).
multiple boolean false Allow selecting more than one file.
max-size-mb number 10 Per-file size limit in MB. Oversized files are rejected inline.
name string 'files' FormData field name used when submitting.
disabled boolean false Disables the drop zone and browse (reflected attribute).

Events

EventTypeDefaultDescription
change CustomEvent<{ files: File[] }> Fired when the file list changes (add or remove). Composed and bubbles.

Accessibility

  • The drop zone is keyboard-focusable; Enter or Space opens the native file picker.
  • Drag-over highlights the zone; drop adds the dragged files.
  • Oversized files are rejected with an inline error message rather than failing silently.
  • Form-associated via ElementInternals — selected files are set as a FormData payload (default name files) so they submit with the enclosing form.

Theming surface

Every public token this component reads, extracted from its source at build time. The Resolves to column walks each token's real lineage — component → semantic → primitive → raw value — so you can see exactly where re-pointing it lands. A spoke re-skins by overriding these in its theme-<slug>.css; component internals are never edited. No hook for what you need? File it with /request-lego.

TokenTierResolves to
--focus-ring-color component #65ba74 via --color-border-focus--color-grass-8
--focus-ring-width component 2px
--form-bg component #fcfcfc via --color-surface--color-gray-1
--form-border-color component #cecece via --color-border--color-gray-7
--form-border-color-focus component #65ba74 via --color-border-focus--color-grass-8
--form-font-size-md component clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --type-size-200
--form-font-size-sm component clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) via --type-size-100
--form-radius-md component 0.5rem via --radius-200
--form-radius-sm component 0.25rem via --radius-100
--icon-size-large component 24px
--icon-size-medium component 20px
--icon-size-small component 16px
--color-active-overlay semantic rgba(0, 88, 98, 0.08)
--color-border semantic #cecece via --color-gray-7
--color-danger semantic #e5484d via --color-red-9
--color-primary semantic #46a758 via --color-grass-9
--color-surface-sunken semantic #f0f0f0 via --color-gray-3
--color-text-muted semantic #838383 via --color-gray-10
--color-text-primary semantic #202020 via --color-gray-12
--font-sans semantic 'DM Sans', sans-serif
--transition-fast semantic 150ms ease
--type-size-100 semantic clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem)
--font-weight-medium primitive 500
--spacing-100 primitive 0.25rem
--spacing-200 primitive 0.5rem
--spacing-300 primitive 0.75rem
--spacing-400 primitive 1rem
--spacing-600 primitive 2rem