File Upload <mv-file-upload>

Zone de dépôt de fichiers : glisser-déposer avec mise en évidence (et refus visuel des types non autorisés), liste avec miniatures d’images, icônes, tailles et suppression, validation du type, de la taille et du nombre, et progression par fichier ; associé aux formulaires (FormData).

CatégorieFormulaires
TypeWeb Component (<mv-file-upload>)
Statutstable
Keywordsfile-upload, dropzone, drag-and-drop, upload, attachments, progress, form-associated

When to use

  • Users attach documents or images by drag and drop with type, size and count limits enforced
  • Uploads should show per-file progress, thumbnails and a way to remove a file before submitting
  • A form must submit selected files natively through FormData

Avoid when

  • A single optional file is picked in a dense form where a drop zone takes too much room → use Input instead
  • Files are dropped into a chat message composer → use AI Chat instead

Installation

node scripts/add.mjs file-upload --out ./src/marvelous

Agent IA avec le serveur MCP Marvelous UI : install_components({ slugs: ["file-upload"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/file-upload/file-upload.js, components/file-upload/file-upload.css.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<form id="file-upload-demo-form" style="display:grid;gap:.5rem;width:min(100%,30rem)">
  <label for="file-upload-demo" style="font-size:.875rem;font-weight:500">Supporting documents</label>
  <mv-file-upload id="file-upload-demo" name="attachments" accept="image/*,.pdf,.docx,.xlsx" multiple max-size="5 MB" max-files="6"></mv-file-upload>
</form>
<script type="module">
  const up = document.getElementById("file-upload-demo");

  // Simulated upload: progress ticks until 100%, one in eight fails.
  let seeding = false;
  const simulate = (file, from = 0) => {
    let pct = from;
    const fail = Math.random() < 0.125;
    const tick = () => {
      if (!up.files.includes(file)) return;
      pct += 6 + Math.random() * 16;
      if (fail && pct > 55) return up.setError(file, "Connection lost");
      up.setProgress(file, Math.min(100, pct));
      if (pct < 100) setTimeout(tick, 180 + Math.random() * 260);
    };
    setTimeout(tick, 150);
  };
  up.addEventListener("mv-add", (e) => { if (!seeding) e.detail.files.forEach((f) => simulate(f)); });

  // Seed the demo with a few realistic files.
  customElements.whenDefined("mv-file-upload").then(async () => {
    const canvas = document.createElement("canvas");
    canvas.width = 160; canvas.height = 160;
    const ctx = canvas.getContext("2d");
    const g = ctx.createLinearGradient(0, 0, 160, 160);
    g.addColorStop(0, "#f6d365"); g.addColorStop(0.55, "#fda085"); g.addColorStop(1, "#8e7cc3");
    ctx.fillStyle = g; ctx.fillRect(0, 0, 160, 160);
    ctx.fillStyle = "rgba(255,255,255,.85)";
    ctx.beginPath(); ctx.arc(112, 48, 18, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = "rgba(40,30,70,.55)";
    ctx.beginPath(); ctx.moveTo(0, 160); ctx.lineTo(56, 84); ctx.lineTo(96, 128); ctx.lineTo(124, 100); ctx.lineTo(160, 140); ctx.lineTo(160, 160); ctx.fill();
    const blob = await new Promise((r) => canvas.toBlob(r, "image/jpeg", 0.8));
    const pad = (n) => new Uint8Array(n);
    const photo = new File([blob, pad(1_380_000)], "site-facade.jpg", { type: "image/jpeg" });
    const quote = new File([pad(842_000)], "signed-quote-2026-09.pdf", { type: "application/pdf" });
    const plan = new File([pad(2_310_000)], "floor-plan-level-2.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
    seeding = true;
    const [a, b, c] = up.addFiles([photo, quote, plan]);
    seeding = false;
    up.setProgress(a, 100);
    up.setProgress(b, 100);
    up.setProgress(c, 64);
    setTimeout(() => simulate(c, 64), 2500);
  });
</script>

API

Attributes

NameTypeDefaultDescription
namestringName of the form entries (one per file).
acceptstringAccepted types, like <input type=file>: “image/*,.pdf,application/zip”.
multiplebooleanMultiple files (otherwise a new file replaces the previous one).
max-sizestringMax size per file: “5 MB”, “500KB”, “1048576”.
max-filesnumberMax number of files (with multiple).
labelstringMain text of the zone (default: “Drag and drop files or browse”).
hintstringHint text (generated default: types · size · count).
layoutlist | gridlistDetailed list or thumbnail grid.
requiredbooleanNative validation (valueMissing).
disabledbooleanDisables the zone.
localestringen-USSize formatting (Intl: “2.4 MB”).

Properties

NameTypeDescription
filesFile[]Kept files (read-only).
itemsArray<{ id, file, status, progress, error }>State of each file: idle | uploading | done | error.

Methods

NameDescription
open()Opens the native file picker.
addFiles(files)Validates and adds files; returns the accepted ones.
remove(file | id)Removes a file (and releases its preview).
clear()Clears the list.
setProgress(file | id, pct)Updates the progress bar (100 = uploaded).
setError(file | id, message)Marks a file as failed.
checkValidity() / reportValidity()Native validation.

Events

NameDescription
mv-adddetail: { files }, accepted files (hook point to start the upload).
mv-removedetail: { file }.
mv-changedetail: { files }, full list after an add/remove.
mv-rejectdetail: { errors: [{ file, reason: type | size | count, message }] }.

Content structure

NameDescription
child contentReplaces the default content of the drop zone.

CSS classes

NameDescription
mv-file-upload-zone / -item / -thumb / -progress / -errorsGenerated parts; data-dragging / data-drag-reject on the host while dragging.

CSS variables

NameDefaultDescription
--mv-file-upload-width32remMax width.
--mv-file-upload-zone-height10remMin height of the zone.
--mv-file-upload-radiusvar(--mv-radius-xl)Zone radius.

Accessibility

The zone is a focusable role=button (Enter/Space open the picker), named by the <label for> and described by the hint text. Validation errors go in a role=alert region; additions, removals and completed uploads are announced through a polite live region. Each file has a “Remove <name>” button (focus moves to the next one) and a role=progressbar bar while uploading.

Cette page a été traduite par IA. Signaler un problème de traduction