An app-wide upload provider, a drop-anywhere overlay and a background queue panel.
"use client"
import { MiB, mockAdapter } from "@uploadcn/core"
import { MousePointerClickIcon } from "lucide-react"
import {
GlobalDropzone,
UploadProvider,
UploadQueuePanel,
} from "@/components/global-upload"
const adapter = mockAdapter({ speed: 2 * MiB })
/**
* In your app, put <UploadProvider> in the root layout. While this example
* is on screen, drag a file from your desktop anywhere over the page.
*/
export default function FullWindowDropzoneExample() {
return (
<UploadProvider adapter={adapter} guard={false}>
<div className="flex flex-col items-center gap-2 rounded-xl border border-dashed py-12 text-center text-sm text-muted-foreground">
<MousePointerClickIcon className="size-5" aria-hidden />
Drag files over any part of this page
</div>
<GlobalDropzone />
<UploadQueuePanel />
</UploadProvider>
)
}
Installation
npx shadcn@latest add @uploadcn/global-uploadUsage
import {
GlobalDropzone,
UploadProvider,
UploadQueuePanel,
} from "@/components/global-upload"// app/layout.tsx
<UploadProvider persist>
{children}
<GlobalDropzone />
<UploadQueuePanel />
</UploadProvider>Any component under the provider can add files with useUploadContext().addFiles(files). Uploads keep running during client-side navigation because the provider stays mounted. GlobalDropzone ignores drops already handled by an inner dropzone.
Props
Prop
Type
Examples
Background uploads
"use client"
import * as React from "react"
import {
type BackgroundCapabilities,
MiB,
getBackgroundCapabilities,
requestPersistentStorage,
s3Adapter,
} from "@uploadcn/core"
import { CheckIcon, MinusIcon } from "lucide-react"
import { demoTransport } from "@/examples/_demo"
import { Button } from "@/components/ui/button"
import {
UploadProvider,
UploadQueuePanel,
} from "@/components/global-upload"
import { UploadTrigger } from "@/components/ui/upload"
const adapter = s3Adapter({
endpoint: "/api/upload",
transport: demoTransport, // docs only, see examples/_demo.ts
multipart: { threshold: 5 * MiB, partSize: 5 * MiB },
})
const LABELS: Record<keyof BackgroundCapabilities, string> = {
persistence: "IndexedDB file persistence",
persistentStorage: "Persistent storage (eviction protection)",
webLocks: "Web Locks (one tab resumes each upload)",
backgroundFetch: "Background Fetch (Chromium only, needs a service worker)",
}
/**
* Uploads live in an app-level provider, survive navigation, and, with
* `persist`: survive reloads and crashes: they come back paused and resume
* from the last finished part. Browsers can't keep JS uploads running after
* the tab closes; this is the honest, cross-browser approach.
*/
export default function BackgroundUploadExample() {
return (
<UploadProvider adapter={adapter} persist guard>
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-2">
<UploadTrigger variant="default">
Upload in the background
</UploadTrigger>
<PersistButton />
</div>
<Capabilities />
</div>
<UploadQueuePanel />
</UploadProvider>
)
}
function PersistButton() {
const [granted, setGranted] = React.useState<boolean | null>(null)
return (
<Button
variant="outline"
onClick={async () => setGranted(await requestPersistentStorage())}
>
{granted === null
? "Request durable storage"
: granted
? "Storage is durable"
: "Browser declined"}
</Button>
)
}
let detected: BackgroundCapabilities | null = null
const subscribe = () => () => {}
// Feature detection only runs in the browser; the server renders nothing.
const getCapabilities = () => (detected ??= getBackgroundCapabilities())
function Capabilities() {
const capabilities = React.useSyncExternalStore(
subscribe,
getCapabilities,
() => null
)
if (!capabilities) return null
return (
<ul
className="flex flex-col gap-1.5 text-sm"
aria-label="Browser capabilities"
>
{(Object.keys(LABELS) as (keyof BackgroundCapabilities)[]).map((key) => (
<li key={key} className="flex items-center gap-2">
{capabilities[key] ? (
<CheckIcon className="size-4" aria-label="Supported" />
) : (
<MinusIcon
className="size-4 text-muted-foreground"
aria-label="Not supported"
/>
)}
<span
className={capabilities[key] ? undefined : "text-muted-foreground"}
>
{LABELS[key]}
</span>
</li>
))}
</ul>
)
}