Add this component

npx zentauri-ui add file-upload

Accessibility notes

Keyboard accessible controls with labels, focus-visible rings, and state attributes. Keep visible labels or aria-labels when copying snippets.

Dependency notes

Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.

Forms

Drag-and-drop file uploads

FileUpload wraps a visually hidden file input with a large hit target. Drag files over the surface to highlight the active state, or activate the native picker with keyboard and pointer.

Custom label

File Upload variants playground

Pick an appearance to preview the file upload live. Toggle Show output / Show code and the snippet updates to match the selected variant.


All appearances

Every shipped appearance token at a glance. Click any swatch to load it into the playground above.

File Upload API

Generated from the package prop types and variant definitions.

FileUpload

FileUploadProps

Variants

PropTypeDefault
appearance
activeamberaquabluecrimsoncyandisabledelectricemeralderrorforestgreen+24
idle

Behavior

PropTypeDefault
acceptstring | undefinednone
disabledboolean | undefinednone
multipleboolean | undefinednone
nameVisually hidden input `name` for formsstring | undefinednone
onChange((event: ChangeEvent<HTMLInputElement>) => void) | undefinednone
onFilesCalled when the user selects or drops files((files: File[]) => void) | undefinednone

Content

PropTypeDefault
childrenOptional controlled file list (not enforced; mainly for forms)ReactNodenone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

FileUploadVariant

FileUploadVariantProps

Behavior

PropTypeDefault
appearance'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'purple' | 'pink' | 'orange' | 'teal' | 'indigo' | 'emerald' | 'success' | 'warning' | 'error' | 'info' | 'amber' | 'disabled' | 'idle' | 'active' | 'neutral' | null | undefinednone
CSS variable overrides

File upload CSS variables

Override these file upload variables on :root, a theme selector, or a component wrapper.

308 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-file-upload-ring-focus: oklch(70.2% 0.183 293.541 / 0.6);
  --zui-file-upload-ring-offset-focus: oklch(98.4% 0.003 247.858);
  --zui-file-upload-idle-border: #00000026;
  --zui-file-upload-idle-bg: #0000000d;
  --zui-file-upload-idle-fg: oklch(44.6% 0.043 257.281);
  --zui-file-upload-idle-border-hover: #00000040;
  --zui-file-upload-idle-bg-hover: #00000012;
  --zui-file-upload-active-border: oklch(70.2% 0.183 293.541 / 0.7);
  --zui-file-upload-active-bg: oklch(60.6% 0.25 292.717 / 0.1);
  --zui-file-upload-active-fg: oklch(20.8% 0.042 265.755);
  --zui-file-upload-error-border: oklch(70.4% 0.191 22.216 / 0.7);
  --zui-file-upload-error-bg: oklch(63.7% 0.237 25.331 / 0.1);
  --zui-file-upload-error-fg: oklch(57.7% 0.245 27.325);
  --zui-file-upload-error-border-hover: oklch(70.4% 0.191 22.216 / 0.8);
  --zui-file-upload-error-bg-hover: oklch(63.7% 0.237 25.331 / 0.15);
  --zui-file-upload-success-border: oklch(79.2% 0.209 151.711 / 0.7);
  --zui-file-upload-success-bg: oklch(72.3% 0.219 149.579 / 0.1);
  --zui-file-upload-success-fg: oklch(62.7% 0.194 149.214);
  --zui-file-upload-success-border-hover: oklch(79.2% 0.209 151.711 / 0.8);
  --zui-file-upload-success-bg-hover: oklch(72.3% 0.219 149.579 / 0.15);
  --zui-file-upload-warning-border: oklch(85.2% 0.199 91.936 / 0.7);
  --zui-file-upload-warning-bg: oklch(79.5% 0.184 86.047 / 0.1);
  --zui-file-upload-warning-fg: oklch(68.1% 0.162 75.834);
  --zui-file-upload-warning-border-hover: oklch(85.2% 0.199 91.936 / 0.8);
  --zui-file-upload-warning-bg-hover: oklch(79.5% 0.184 86.047 / 0.15);
  --zui-file-upload-info-border: oklch(70.7% 0.165 254.624 / 0.7);
  --zui-file-upload-info-bg: oklch(62.3% 0.214 259.815 / 0.1);
  --zui-file-upload-info-fg: oklch(54.6% 0.245 262.881);
  --zui-file-upload-info-border-hover: oklch(70.7% 0.165 254.624 / 0.8);
  --zui-file-upload-info-bg-hover: oklch(62.3% 0.214 259.815 / 0.15);
  --zui-file-upload-neutral-border: oklch(70.7% 0.022 261.325 / 0.7);
  --zui-file-upload-neutral-bg: oklch(55.1% 0.027 264.364 / 0.1);
  --zui-file-upload-neutral-fg: oklch(44.6% 0.03 256.802);
  --zui-file-upload-neutral-border-hover: oklch(70.7% 0.022 261.325 / 0.8);
  --zui-file-upload-neutral-bg-hover: oklch(55.1% 0.027 264.364 / 0.15);
  --zui-file-upload-purple-border: oklch(71.4% 0.203 305.504 / 0.7);
  --zui-file-upload-purple-bg: oklch(62.7% 0.265 303.9 / 0.1);
  --zui-file-upload-purple-fg: oklch(55.8% 0.288 302.321);
  --zui-file-upload-purple-border-hover: oklch(71.4% 0.203 305.504 / 0.8);
  --zui-file-upload-purple-bg-hover: oklch(62.7% 0.265 303.9 / 0.15);
  --zui-file-upload-indigo-border: oklch(67.3% 0.182 276.935 / 0.7);
  --zui-file-upload-indigo-bg: oklch(58.5% 0.233 277.117 / 0.1);
  --zui-file-upload-indigo-fg: oklch(51.1% 0.262 276.966);
  --zui-file-upload-indigo-border-hover: oklch(67.3% 0.182 276.935 / 0.8);
  --zui-file-upload-indigo-bg-hover: oklch(58.5% 0.233 277.117 / 0.15);
  --zui-file-upload-emerald-border: oklch(76.5% 0.177 163.223 / 0.7);
  --zui-file-upload-emerald-bg: oklch(69.6% 0.17 162.48 / 0.1);
  --zui-file-upload-emerald-fg: oklch(59.6% 0.145 163.225);
  --zui-file-upload-emerald-border-hover: oklch(76.5% 0.177 163.223 / 0.8);
  --zui-file-upload-emerald-bg-hover: oklch(69.6% 0.17 162.48 / 0.15);
  --zui-file-upload-amber-border: oklch(82.8% 0.189 84.429 / 0.7);
  --zui-file-upload-amber-bg: oklch(76.9% 0.188 70.08 / 0.1);
  --zui-file-upload-amber-fg: oklch(66.6% 0.179 58.318);
  --zui-file-upload-amber-border-hover: oklch(82.8% 0.189 84.429 / 0.8);
  --zui-file-upload-amber-bg-hover: oklch(76.9% 0.188 70.08 / 0.15);
  --zui-file-upload-pink-border: oklch(71.8% 0.202 349.761 / 0.7);
  --zui-file-upload-pink-bg: oklch(65.6% 0.241 354.308 / 0.1);
  --zui-file-upload-pink-fg: oklch(59.2% 0.249 0.584);
  --zui-file-upload-pink-border-hover: oklch(71.8% 0.202 349.761 / 0.8);
  --zui-file-upload-pink-bg-hover: oklch(65.6% 0.241 354.308 / 0.15);
  --zui-file-upload-orange-border: oklch(75% 0.183 55.934 / 0.7);
  --zui-file-upload-orange-bg: oklch(70.5% 0.213 47.604 / 0.1);
  --zui-file-upload-orange-fg: oklch(64.6% 0.222 41.116);
  --zui-file-upload-orange-border-hover: oklch(75% 0.183 55.934 / 0.8);
  --zui-file-upload-orange-bg-hover: oklch(70.5% 0.213 47.604 / 0.15);
  --zui-file-upload-teal-border: oklch(77.7% 0.152 181.912 / 0.7);
  --zui-file-upload-teal-bg: oklch(70.4% 0.14 182.503 / 0.1);
  --zui-file-upload-teal-fg: oklch(60% 0.118 184.704);
  --zui-file-upload-teal-border-hover: oklch(77.7% 0.152 181.912 / 0.8);
  --zui-file-upload-teal-bg-hover: oklch(70.4% 0.14 182.503 / 0.15);
  --zui-file-upload-blue-border: #2563eb;
  --zui-file-upload-blue-bg: #2563eb14;
  --zui-file-upload-blue-fg: #0f172a;
  --zui-file-upload-blue-bg-hover: #2563eb24;
  --zui-file-upload-cyan-border: #0891b2;
  --zui-file-upload-cyan-bg: #0891b214;
  --zui-file-upload-cyan-fg: #0f172a;
  --zui-file-upload-cyan-bg-hover: #0891b224;
  --zui-file-upload-green-border: #16a34a;
  --zui-file-upload-green-bg: #16a34a14;
  --zui-file-upload-green-fg: #0f172a;
  --zui-file-upload-green-bg-hover: #16a34a24;
  --zui-file-upload-lime-border: #65a30d;
  --zui-file-upload-lime-bg: #65a30d14;
  --zui-file-upload-lime-fg: #0f172a;
  --zui-file-upload-lime-bg-hover: #65a30d24;
  --zui-file-upload-mint-border: #10b981;
  --zui-file-upload-mint-bg: #10b98114;
  --zui-file-upload-mint-fg: #0f172a;
  --zui-file-upload-mint-bg-hover: #10b98124;
  --zui-file-upload-ocean-border: #0284c7;
  --zui-file-upload-ocean-bg: #0284c714;
  --zui-file-upload-ocean-fg: #0f172a;
  --zui-file-upload-ocean-bg-hover: #0284c724;
  --zui-file-upload-sapphire-border: #1d4ed8;
  --zui-file-upload-sapphire-bg: #1d4ed814;
  --zui-file-upload-sapphire-fg: #0f172a;
  --zui-file-upload-sapphire-bg-hover: #1d4ed824;
  --zui-file-upload-lavender-border: #8b5cf6;
  --zui-file-upload-lavender-bg: #8b5cf614;
  --zui-file-upload-lavender-fg: #0f172a;
  --zui-file-upload-lavender-bg-hover: #8b5cf624;
  --zui-file-upload-ruby-border: #be123c;
  --zui-file-upload-ruby-bg: #be123c14;
  --zui-file-upload-ruby-fg: #0f172a;
  --zui-file-upload-ruby-bg-hover: #be123c24;
  --zui-file-upload-red-border: #dc2626;
  --zui-file-upload-red-bg: #dc262614;
  --zui-file-upload-red-fg: #0f172a;
  --zui-file-upload-red-bg-hover: #dc262624;
  --zui-file-upload-slate-border: #475569;
  --zui-file-upload-slate-bg: #47556914;
  --zui-file-upload-slate-fg: #0f172a;
  --zui-file-upload-slate-bg-hover: #47556924;
  --zui-file-upload-zinc-border: #52525b;
  --zui-file-upload-zinc-bg: #52525b14;
  --zui-file-upload-zinc-fg: #0f172a;
  --zui-file-upload-zinc-bg-hover: #52525b24;
  --zui-file-upload-stone-border: #57534e;
  --zui-file-upload-stone-bg: #57534e14;
  --zui-file-upload-stone-fg: #0f172a;
  --zui-file-upload-stone-bg-hover: #57534e24;
  --zui-file-upload-royal-border: #4338ca;
  --zui-file-upload-royal-bg: #4338ca14;
  --zui-file-upload-royal-fg: #0f172a;
  --zui-file-upload-royal-bg-hover: #4338ca24;
  --zui-file-upload-electric-border: #0ea5e9;
  --zui-file-upload-electric-bg: #0ea5e914;
  --zui-file-upload-electric-fg: #0f172a;
  --zui-file-upload-electric-bg-hover: #0ea5e924;
  --zui-file-upload-forest-border: #166534;
  --zui-file-upload-forest-bg: #16653414;
  --zui-file-upload-forest-fg: #0f172a;
  --zui-file-upload-forest-bg-hover: #16653424;
  --zui-file-upload-sunset-border: #ea580c;
  --zui-file-upload-sunset-bg: #ea580c14;
  --zui-file-upload-sunset-fg: #0f172a;
  --zui-file-upload-sunset-bg-hover: #ea580c24;
  --zui-file-upload-magenta-border: #c026d3;
  --zui-file-upload-magenta-bg: #c026d314;
  --zui-file-upload-magenta-fg: #0f172a;
  --zui-file-upload-magenta-bg-hover: #c026d324;
  --zui-file-upload-crimson-border: #b91c1c;
  --zui-file-upload-crimson-bg: #b91c1c14;
  --zui-file-upload-crimson-fg: #0f172a;
  --zui-file-upload-crimson-bg-hover: #b91c1c24;
  --zui-file-upload-aqua-border: #0f766e;
  --zui-file-upload-aqua-bg: #0f766e14;
  --zui-file-upload-aqua-fg: #0f172a;
  --zui-file-upload-aqua-bg-hover: #0f766e24;
  --zui-file-upload-plum-border: #7e22ce;
  --zui-file-upload-plum-bg: #7e22ce14;
  --zui-file-upload-plum-fg: #0f172a;
  --zui-file-upload-plum-bg-hover: #7e22ce24;
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-file-upload-ring-focus-dark: oklch(70.2% 0.183 293.541 / 0.6);
  --zui-file-upload-ring-offset-focus-dark: oklch(12.9% 0.042 264.695);
}

Overview

The component clears the native input value after each selection so selecting the same file twice still emits callbacks. Use accept and multiple to mirror platform constraints.

Common use cases

  • CSV and media imports in admin tools.
  • Profile and cover photo pickers.
  • Attachment surfaces on support forms.
  • Bulk asset ingestion during migrations.

FAQ

How do I handle selected files?

Use the onFiles callback to receive a File array. You can also listen to onChange for the underlying input event when integrating with form libraries.

Can I customize the inner content?

Yes. Pass children to replace the default helper copy while keeping the same focusable label wiring to the hidden input.