UploadKit
SDK@uploadkitdev/react

UploadButton

A styled button that opens a file picker and shows upload progress inline.

Baseline button · inline progress

Basic usage

import { UploadButton } from '@uploadkitdev/react';

export default function Page() {
  return (
    <UploadButton
      route="imageUploader"
      onUploadComplete={(result) => {
        console.log('Uploaded:', result.url);
      }}
      onUploadError={(error) => {
        console.error('Upload failed:', error.message);
      }}
    />
  );
}

Props

PropTypeDefaultDescription
routestringRequired. Route name from your file router (e.g. 'imageUploader').
acceptstring[]Accepted MIME types for the file input (e.g. ['image/jpeg', 'image/png']). Client-side UX validation only — the server enforces authoritatively.
maxSizenumberMaximum file size in bytes. Client-side UX validation only.
metadataRecord<string, unknown>JSON-serializable metadata forwarded to onUploadComplete on the server.
onUploadComplete(result: UploadResult) => voidCalled after a successful upload. Receives the full file record.
onUploadError(error: Error) => voidCalled when an upload fails (network error, type rejection, size rejection).
variant'default' | 'outline' | 'ghost''default'Visual style variant.
size'sm' | 'md' | 'lg''md'Size variant.
disabledbooleanfalseDisables the button.
classNamestringAdditional CSS class(es) for the button wrapper.
appearancePartial<Record<'button' | 'progressBar' | 'progressText', string>>Override CSS classes on specific inner elements.
childrenReactNodeCustom button label content. Omit to use the built-in state labels.

UploadButton accepts a ref forwarded to the underlying <button> element.

Visual states

The button cycles through four states during an upload:

StateLabelIconButton color
idle"Upload file"Cloud-upload iconPrimary (--uk-primary)
uploading"Uploading 42%"Spinning loaderPrimary, semi-transparent
success"Uploaded"CheckmarkSuccess (--uk-success)
error"Failed"X iconError (--uk-error)

After success, the button automatically resets to idle after 3 seconds. On error, it stays in the error state until the user tries again.

Variants

All three variants at md size:

// Filled (default)
<UploadButton route="imageUploader" variant="default" />

// Outlined border, transparent background
<UploadButton route="imageUploader" variant="outline" />

// No border, no background — minimal
<UploadButton route="imageUploader" variant="ghost" />

Sizes

<UploadButton route="imageUploader" size="sm" />  {/* 12px text, compact padding */}
<UploadButton route="imageUploader" size="md" />  {/* 14px text — default */}
<UploadButton route="imageUploader" size="lg" />  {/* 16px text, spacious padding */}

Custom label

Replace the built-in state labels with custom content:

<UploadButton route="avatarUploader">
  <svg>...</svg>
  Replace your avatar
</UploadButton>

When children is provided, the button still handles all upload state logic — only the label content is customized.

Appearance — targeting inner elements

Override CSS classes on specific inner elements without affecting the outer wrapper:

<UploadButton
  route="imageUploader"
  className="w-full"              // outer wrapper
  appearance={{
    button: 'rounded-full',       // the <button> element
    progressBar: 'h-1 bg-blue-500', // progress bar fill
    progressText: 'text-xs font-mono', // "42%" text
  }}
/>

File type and size validation

<UploadButton
  route="imageUploader"
  accept={['image/jpeg', 'image/png', 'image/webp']}
  maxSize={4 * 1024 * 1024} // 4 MB in bytes
  onUploadError={(error) => {
    // Called immediately if the file fails client-side validation
    // Also called for server-side rejections
    alert(error.message);
  }}
/>

With metadata

<UploadButton
  route="projectFiles"
  metadata={{ projectId: currentProject.id, uploadedBy: session.user.id }}
  onUploadComplete={async (result) => {
    await saveFileToProject(result.url, result.key);
  }}
/>

Accessibility

  • aria-busy is set to true during upload
  • A live region announces progress changes and status transitions to screen readers
  • The hidden file <input> is aria-hidden="true" and tabIndex={-1}
  • focus-visible outline uses --uk-primary at 2px offset

See the Theming guide for appearance customization with CSS custom properties.

On this page