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:
: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:
| Key | Element |
|---|---|
container | Outer wrapper div |
uploadIcon | SVG cloud icon |
label | Primary instruction text |
allowedContent | "Image, PDF up to 4MB" subtitle |
button | Upload trigger button |
Level 4: Build custom UI with useUploadKit
For complete control over layout, animation, and interaction, use the useUploadKit hook:
'use client';
import { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useUploadKit } from '@uploadkitdev/react';
export function CustomDropzone() {
const { upload, isUploading, progress, result, error } = useUploadKit('imageUploader');
const onDrop = useCallback(
async (acceptedFiles: File[]) => {
for (const file of acceptedFiles) {
await upload(file);
}
},
[upload],
);
// React to result/error via effects or render branches
// useEffect(() => { if (result) console.log('Uploaded:', result.url); }, [result]);
// useEffect(() => { if (error) console.error('Upload failed:', error.message); }, [error]);
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:
/* 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.
Multipart Upload Guide
How multipart uploads work in UploadKit — automatic activation, progress tracking, abort behavior, and retry configuration.
AI Assistants (MCP)
Install the UploadKit MCP server so Claude Code, Cursor, Windsurf, and Zed know every component, scaffold boilerplate, and set up BYOS for you.