Upload Media

Molecule

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

✅ Do
  • 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
❌ Don't
  • 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

  1. 1Label.input-field-label. Always visible above the drop zone.
  2. 2Wrapper.input-field. Shared with Text Input and Text Area.
  3. 3Drop zone shell.upload. The clickable/draggable target. Background changes on hover.
  4. 4Upload icon.upload-icon. Arrow-into-tray shape. Always present in the empty state.
  5. 5Heading + subheading.upload-heading / .upload-subheading. File type and size limit guidance.
  6. 6Choose file button.btn.btn-primary.btn-sm. Triggers the native file picker. Never change this to Secondary or Ghost.
  7. 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.

HTML
<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.

Default
Hover
Empty

Click to choose file

Max: 5MB file

Caption

Click to choose file

Max: 5MB file

Caption

1 file
  • filebig…name.png

Max: 5MB file

Caption

  • filebig…name.png

Max: 5MB file

Caption

2 files
  • filebig…name.png
  • filebig…name.png

Max: 5MB file

Caption

  • filebig…name.png
  • filebig…name.png

Max: 5MB file

Caption

3 files (limit)
  • 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 / BehaviourValueNotes
Label association<label for="id">Associate the visible label with the hidden file input via for / id
Keyboard — triggerTab + Enter / SpaceTab to the "Choose file" button then activate it to open the native file picker
Drop zonerole="button" + aria-labelIf the drop zone itself is clickable, give it a role and label so keyboard users can activate it
File acceptedAnnounce via live regionAfter a file is selected, announce the filename to screen readers using aria-live="polite"
Remove filearia-label="Remove [filename]"Each remove button must include the filename in its label so the action is unambiguous
Error statearia-describedby → captionLink the upload zone to the caption error message for screen reader announcement

API

Class / attrOnDescription
.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 childrenSets 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.uploadDocumentation helper — forces the hover bg at rest for the Full matrix. Not needed in production.
data-upload-interactive.uploadOpt-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.