UploadKit
Guides

Custom Styling Guide

Override CSS variables, add Tailwind classes, target inner elements with the appearance prop, and build fully custom UI with useUploadKit.

UploadKit components are designed to be styled — from quick color tweaks to completely custom UI. There are four levels of customization, each progressively more involved.

Level 1: CSS custom properties

Override --uk-* variables to match your brand with zero JavaScript:

app/globals.css
:root {
  /* Brand accent color */
  --uk-accent: #6366f1;
  --uk-accent-hover: #818cf8;
  --uk-accent-foreground: #ffffff;

  /* Surfaces */
  --uk-background: #ffffff;
  --uk-border: #e4e4e7;
  --uk-radius: 12px;

  /* Text */
  --uk-text-primary: #09090b;
  --uk-text-secondary: #71717a;

  /* States */
  --uk-drag-active: rgba(99, 102, 241, 0.08);
}

/* Dark mode */
.dark {
  --uk-background: #09090b;
  --uk-border: rgba(255, 255, 255, 0.06);
  --uk-text-primary: #fafafa;
  --uk-text-secondary: #a1a1aa;
  --uk-drag-active: rgba(99, 102, 241, 0.12);
}

These variables cascade to every UploadKit component in your app.

Level 2: className prop

Add Tailwind classes directly to the component container:

<UploadDropzone
  route="imageUploader"
  className="my-8 border-2 border-primary/20 bg-primary/5"
  onUploadComplete={handleComplete}
/>

Level 3: appearance prop

Target specific inner elements for surgical styling:

<UploadDropzone
  route="imageUploader"
  appearance={{
    container: 'border-dashed border-2 rounded-2xl p-10',
    uploadIcon: 'text-primary h-12 w-12',
    label: 'text-xl font-semibold text-foreground mt-4',
    allowedContent: 'text-muted-foreground text-sm mt-1',
    button: 'bg-primary text-primary-foreground rounded-full px-6 py-2 text-sm font-medium hover:bg-primary/90 transition-colors mt-4',
  }}
  onUploadComplete={handleComplete}
/>

<UploadButton
  route="imageUploader"
  appearance={{
    button: 'rounded-full bg-gradient-to-r from-violet-500 to-indigo-500 text-white px-4 py-2 text-sm font-semibold shadow-lg',
    allowedContent: 'text-xs text-muted-foreground mt-1',
  }}
/>

Available appearance keys:

KeyElement
containerOuter wrapper div
uploadIconSVG cloud icon
labelPrimary instruction text
allowedContent"Image, PDF up to 4MB" subtitle
buttonUpload trigger button

Level 4: Build custom UI with useUploadKit

For complete control over layout, animation, and interaction, use the useUploadKit hook:

components/CustomDropzone.tsx
'use client';

import { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useUploadKit } from '@uploadkitdev/react';

export function CustomDropzone() {
  const { upload, isUploading, progress } = useUploadKit({
    route: 'imageUploader',
    onUploadComplete: (files) => {
      console.log('Uploaded:', files.map((f) => f.url));
    },
    onUploadError: (error) => {
      console.error('Upload failed:', error.message);
    },
  });

  const onDrop = useCallback(
    (acceptedFiles: File[]) => {
      upload(acceptedFiles);
    },
    [upload],
  );

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop,
    accept: { 'image/*': [] },
    maxSize: 4 * 1024 * 1024,
    disabled: isUploading,
  });

  return (
    <div
      {...getRootProps()}
      className={[
        'relative flex flex-col items-center justify-center rounded-2xl border-2 border-dashed p-12 transition-all cursor-pointer',
        isDragActive
          ? 'border-primary bg-primary/5 scale-[1.01]'
          : 'border-border hover:border-primary/50 hover:bg-muted/30',
        isUploading ? 'pointer-events-none opacity-60' : '',
      ].join(' ')}
    >
      <input {...getInputProps()} />

      {isUploading ? (
        <div className="flex flex-col items-center gap-3 w-full max-w-xs">
          <span className="text-sm font-medium text-foreground">Uploading... {progress}%</span>
          <div className="h-1.5 w-full rounded-full bg-muted overflow-hidden">
            <div
              className="h-full rounded-full bg-primary transition-all duration-300"
              style={{ width: `${progress}%` }}
            />
          </div>
        </div>
      ) : (
        <div className="flex flex-col items-center gap-2 text-center">
          <div className="rounded-full bg-primary/10 p-4">
            <svg className="h-8 w-8 text-primary" /* ... upload icon */ />
          </div>
          <p className="font-semibold text-foreground">
            {isDragActive ? 'Drop to upload' : 'Drag images here'}
          </p>
          <p className="text-sm text-muted-foreground">or click to browse — up to 4 MB</p>
        </div>
      )}
    </div>
  );
}

Dark mode customization

If your app uses Tailwind's dark: variant, you can pair it with the CSS variable approach:

app/globals.css
/* Override --uk-* variables inside the dark class */
.dark {
  --uk-background: theme(colors.zinc.950);
  --uk-border: theme(colors.zinc.800);
  --uk-text-primary: theme(colors.zinc.50);
  --uk-text-secondary: theme(colors.zinc.400);
  --uk-accent: theme(colors.violet.500);
}

Or use the appearance prop with dark: classes when using Tailwind:

<UploadDropzone
  route="imageUploader"
  appearance={{
    container: 'dark:border-zinc-700 dark:bg-zinc-900/50',
    label: 'dark:text-zinc-100',
    allowedContent: 'dark:text-zinc-400',
  }}
/>

UploadKit components ship with dark mode support out of the box. The CSS variable approach requires the least code — start there before reaching for the appearance prop.

On this page