Upload Media
A drop-target / file-list surface for picking media. Reuses the .input-field wrapper, label, caption, and the --input-* token group from Text Input — same colour values, no new tokens. The "Choose file" trigger is the existing .btn.btn-primary.btn-sm. Four content variants (Empty · 1 file · 2 files · 3 files / limit) × two states (Default · Hover) per Figma node 3348-6794.
When to use
- Use for file attachments, image uploads, and document submissions
- Show accepted file types and size limits in the caption or subheading
- Show preview thumbnails after files are selected so users can confirm their choice
- Show progress feedback when files are uploading
- Allow users to remove already-selected files before submitting
- Use for simple text input — use Text Input instead
- Accept file types you cannot handle server-side without warning the user
- Allow uploads without a file size limit or without communicating the limit clearly
- Use a primary button for "Choose file" if a more prominent action exists on the same page
- Show more than 3 file previews without a scroll or collapse mechanism
DigiLawyer Patterns
No additional product-specific patterns for Upload Media.
Anatomy
Upload Media shares the .input-field wrapper with Text Input and Text Area. The drop-target shell is .upload, which changes content based on how many files have been selected.
Click to choose file
Max: 5MB
Caption
- 1Label —
.input-field-label. Always visible above the drop zone. - 2Wrapper —
.input-field. Shared with Text Input and Text Area. - 3Drop zone shell —
.upload. The clickable/draggable target. Background changes on hover. - 4Upload icon —
.upload-icon. Arrow-into-tray shape. Always present in the empty state. - 5Heading + subheading —
.upload-heading/.upload-subheading. File type and size limit guidance. - 6Choose file button —
.btn.btn-primary.btn-sm. Triggers the native file picker. Never change this to Secondary or Ghost. - 7Caption —
.input-field-caption. Optional helper text or error message below the drop zone.
Default
The minimal upload is a .input-field wrapper with three children: .input-field-label, the .upload shell, and .input-field-caption. The empty state lives inside .upload-empty (upload-arrow icon · Body 2 Semi-Bold heading · Caption 1 subheading) and is followed by the primary "Choose file" button. Hover state is browser-native (:hover on the shell), .is-hover forces it at rest for the matrix.
Click to choose file
Max: 5MB file
Try it — pick up to 3 images, hit × to remove.
<div class="input-field"> <label class="input-field-label">Label</label> <div class="upload" data-upload-interactive data-upload-max="3" data-upload-accept="image/*"> <div class="upload-empty"> <span class="upload-icon"><svg>…</svg></span> <p class="upload-heading">Click to choose file</p> <p class="upload-subheading">Max: 5MB file</p> </div> <button class="btn btn-primary btn-sm">Choose file</button> </div> <p class="input-field-caption">Caption</p> </div>
Variants
Four content variants tracking how many files the user has selected so far. Empty shows the hero illustration and the Choose-file CTA. 1 file and 2 files show a vertical list of .upload-file pills (white surface, 24×24 thumbnail · filename · × remove button) plus the same Choose-file button to add more. 3 files (the limit in this 5MB demo) keeps the same file list but drops the Choose button — once the limit is hit, the only affordance is removing an existing file.
Click to choose file
Max: 5MB file
- filebig…name.png
Max: 5MB file
- filebig…name.png
- filebig…name.png
Max: 5MB file
- filebig…name.png
- filebig…name.png
- filebig…name.png
Max: 5MB file
States
Two states: Default (bg --input-default-bg #F6F6F6) and Hover (bg --input-hover-bg #E7E7E7 + heading text darkens). Hover is browser-native — pointer over the shell to preview, or apply .is-hover to force it at rest. Per-file pills and the Choose-file button each carry their own hover behaviour independently.
Click to choose file
Max: 5MB file
Click to choose file
Max: 5MB file
Full matrix
4 variants × 2 states = 8 cells, mirroring Figma 1:1.
Click to choose file
Max: 5MB file
Caption
Click to choose file
Max: 5MB file
Caption
- filebig…name.png
Max: 5MB file
Caption
- filebig…name.png
Max: 5MB file
Caption
- filebig…name.png
- filebig…name.png
Max: 5MB file
Caption
- filebig…name.png
- filebig…name.png
Max: 5MB file
Caption
- filebig…name.png
- filebig…name.png
- filebig…name.png
Max: 5MB file
Caption
- filebig…name.png
- filebig…name.png
- filebig…name.png
Max: 5MB file
Caption
Accessibility
Upload Media is built on a native <input type="file"> triggered by the "Choose file" button. All keyboard and screen reader behaviour is implicit.
| Attribute / Behaviour | Value | Notes |
|---|---|---|
| Label association | <label for="id"> | Associate the visible label with the hidden file input via for / id |
| Keyboard — trigger | Tab + Enter / Space | Tab to the "Choose file" button then activate it to open the native file picker |
| Drop zone | role="button" + aria-label | If the drop zone itself is clickable, give it a role and label so keyboard users can activate it |
| File accepted | Announce via live region | After a file is selected, announce the filename to screen readers using aria-live="polite" |
| Remove file | aria-label="Remove [filename]" | Each remove button must include the filename in its label so the action is unambiguous |
| Error state | aria-describedby → caption | Link the upload zone to the caption error message for screen reader announcement |
API
| Class / attr | On | Description |
|---|---|---|
| .input-field | <div> | Shared wrapper with Text Input and Text Area. Column flex with 4px gap between label / shell / caption. |
| .upload | <div> | The drop-target shell — 240px tall, --input-default-bg, 8px radius, column flex centered, 16px gap between hero / file list / button. Handles Hover natively via :hover. |
| .upload-empty | <div> | The empty-state column. Holds an icon + heading + subheading, all centered. |
| .upload-icon | <span> | 32×32 hero glyph slot. Wrap an inline SVG; inherits currentColor. |
| .upload-heading | <p> | "Click to choose file" — Body 2 Semi-Bold (14/24, weight 600), centered. |
| .upload-subheading | <p> | "Max: 5MB file" — Caption 1 Regular (12/16), centered. Also reused under the file list when one or more files are uploaded. |
| .upload-files | <ul> | Reset list (no bullets, no padding). Column flex with 8px gap between .upload-file pills. |
| .upload-file | <li> | One file pill — 40px tall, white surface, 8px radius, 1px --color-border outline, holds thumb + name + remove button. |
| .upload-file-thumb | <span> | 24×24 leading slot. Use an inline SVG placeholder while the file is uploading, swap to an <img> once the thumbnail is ready (object-fit: cover). |
| .upload-file-name | <span> | The filename. Truncates with ellipsis on overflow. |
| .upload-file-remove | <button> | The × remove button (24×24). Native button — pair with aria-label="Remove file". |
| .btn.btn-primary.btn-sm | <button> | The "Choose file" trigger. Reuses the existing Button component at sm (32px) primary. Hidden once the upload limit is reached. |
| .input-field-disabled / disabled | .input-field / interactive children | Sets the disabled visual on the shell and locks the Choose-file button. Native disabled on the underlying file input is the source of truth. |
| .is-hover | .upload | Documentation helper — forces the hover bg at rest for the Full matrix. Not needed in production. |
| data-upload-interactive | .upload | Opt-in: when present, the page script wires the shell to a hidden <input type="file">. Clicking the Choose-file button opens the native picker, selected images render as .upload-file pills with live thumbnails, and the × button removes each file. When the limit is reached the Choose button is hidden automatically. (See the inline script at the end of this page for the reference implementation.) |
| data-upload-max | .upload[data-upload-interactive] | Maximum number of files (integer, default 3). Once reached, the Choose button is hidden until a file is removed. |
| data-upload-accept | .upload[data-upload-interactive] | MIME filter passed through to the file picker's accept attribute (e.g. image/*, application/pdf). Files over 5MB are silently skipped. |