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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
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
| Event | Type | Default | Description |
|---|---|---|---|
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 aFormDatapayload (default namefiles) 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.
| Token | Tier | Resolves 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 |