UploadKit
SDK@uploadkitdev/react

UploadModal

A modal dialog for file uploads using the native HTML dialog element.

Drop files here or click to browse

Native <dialog> · controlled open state

Basic usage

UploadModal is a controlled component. You manage the open state and pass onClose to let the modal close itself.

'use client';

import { useState } from 'react';
import { UploadModal } from '@uploadkitdev/react';

export default function Page() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Upload a file</button>

      <UploadModal
        open={open}
        onClose={() => setOpen(false)}
        route="imageUploader"
        title="Upload image"
        onUploadComplete={(results) => {
          console.log('Uploaded:', results.map((r) => r.url));
          setOpen(false);
        }}
      />
    </>
  );
}

Props

PropTypeDefaultDescription
openbooleanRequired. Controls whether the modal is open.
onClose() => voidRequired. Called when the modal requests to close (ESC key, backdrop click).
routestringRequired. Route name from your file router.
acceptstring[]Accepted MIME types. Client-side UX only.
maxSizenumberMaximum file size in bytes. Client-side UX only.
maxFilesnumberMaximum number of files per upload session.
metadataRecord<string, unknown>JSON-serializable metadata for the server.
onUploadComplete(results: UploadResult[]) => voidCalled after all files upload successfully.
onUploadError(error: Error) => voidCalled when any upload fails.
classNamestringAdditional CSS class(es) for the <dialog> element.
titlestringHeading text shown inside the modal. Also used as aria-label.
appearancePartial<Record<'modal' | 'backdrop' | 'content' | 'container' | 'label' | 'icon' | 'fileItem' | 'progressBar' | 'button', string>>Override CSS classes on inner elements.

Native dialog element

UploadModal uses the native HTML <dialog> element rather than a div-based overlay. Benefits:

  • Built-in focus trap: Tab/Shift+Tab cycles through focusable elements inside the modal only
  • ::backdrop: The browser renders the backdrop pseudo-element — style it via appearance.backdrop or CSS
  • aria-modal semantics: Screen readers understand it's a modal without extra ARIA attributes
  • ESC key handling: Native dialog closes on ESC; UploadModal intercepts this event and calls onClose() instead so React state drives the close

Animation

The modal animates in with a scale + opacity effect (0.95 → 1.0 scale, 0 → 1 opacity) over 200ms ease-out. The animation is defined in the CSS as @keyframes uk-modal-enter and applied to dialog[open].

The backdrop uses backdrop-filter: blur(4px) and 50% black opacity.

Close behavior

The modal closes when:

  • User presses ESConClose() is called
  • User clicks the backdrop (outside the modal content) — detected by comparing event.target to the <dialog> element itself
// Controlled: set open=false in onClose to close the modal
<UploadModal
  open={isOpen}
  onClose={() => setIsOpen(false)}
  route="imageUploader"
/>

Accessibility

  • Uses native <dialog> for built-in role="dialog" and aria-modal
  • aria-label is set to the title prop (or 'Upload files' if not provided)
  • Focus trap is native — no JavaScript needed
  • ESC key handled via onCancel with preventDefault() to ensure React state controls the close
  • Respects prefers-reduced-motion — animation duration drops to near-zero when reduced motion is preferred

The <dialog> element's .showModal() and .close() imperative API is called via useEffect whenever the open prop changes. This keeps React state as the single source of truth while using the browser's native modal infrastructure.

Custom close button

Add a close button inside the modal by wrapping UploadModal or using UploadDropzone directly with custom children:

<UploadModal
  open={open}
  onClose={() => setOpen(false)}
  route="imageUploader"
  title="Upload files"
  onUploadComplete={(results) => {
    // Close automatically after upload
    saveFiles(results);
    setOpen(false);
  }}
/>

On this page