File Upload Components
Three patterns: minimal input, a polished file:
-styled control, and a drag-and-drop card with Heroicons. Previews are UI-only; use the snippets to wire up behavior.
1) Minimal File Input
Plain browser file picker with good spacing and helpers.
<form class="space-y-3 max-w-xl">
<label for="file-minimal" class="block text-sm font-medium text-gray-900">Upload file</label>
<input id="file-minimal" name="file-minimal" type="file" class="block w-full text-sm text-gray-900 file:mr-4 file:px-3 file:py-2 file:rounded-md file:border file:border-black/10 file:bg-white file:text-gray-900 hover:file:bg-gray-50" />
<p class="text-xs text-gray-500">Max 5MB. Supported: JPG, PNG, PDF.</p>
<button type="button" class="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Submit</button>
</form>
<template>
<form class="space-y-3 max-w-xl">
<label for="file-minimal" class="block text-sm font-medium text-gray-900">Upload file</label>
<input id="file-minimal" name="file-minimal" type="file" class="block w-full text-sm text-gray-900 file:mr-4 file:px-3 file:py-2 file:rounded-md file:border file:border-black/10 file:bg-white file:text-gray-900 hover:file:bg-gray-50" />
<p class="text-xs text-gray-500">Max 5MB. Supported: JPG, PNG, PDF.</p>
<button type="button" class="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Submit</button>
</form>
</template>
<script setup>
// UI-only
</script>
export default function MinimalFileInput(){
return (
<form className="space-y-3 max-w-xl">
<label htmlFor="file-minimal" className="block text-sm font-medium text-gray-900">Upload file</label>
<input id="file-minimal" name="file-minimal" type="file" className="block w-full text-sm text-gray-900 file:mr-4 file:px-3 file:py-2 file:rounded-md file:border file:border-black/10 file:bg-white file:text-gray-900 hover:file:bg-gray-50" />
<p className="text-xs text-gray-500">Max 5MB. Supported: JPG, PNG, PDF.</p>
<button type="button" className="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Submit</button>
</form>
)
}
2) Styled File Input (Tailwind file:
)
Custom button via file:
variants, helper text, and disabled state preview.
<form class="space-y-6 max-w-xl">
<div>
<label for="file-styled" class="block text-sm font-medium text-gray-900 mb-1">Profile photo</label>
<input id="file-styled" name="file-styled" type="file" accept="image/*"
class="block w-full text-sm text-gray-900
file:mr-4 file:rounded-lg file:border-0 file:px-4 file:py-2.5
file:bg-violet/10 file:text-violet-700 hover:file:bg-violet/15
file:ring-1 file:ring-inset file:ring-violet/20" />
<p class="mt-2 text-xs text-gray-500">PNG or JPG up to 2MB.</p>
</div>
<div class="opacity-60">
<label for="file-styled-disabled" class="block text-sm font-medium text-gray-900 mb-1">Disabled state</label>
<input id="file-styled-disabled" name="file-styled-disabled" type="file" disabled
class="block w-full cursor-not-allowed text-sm text-gray-500
file:mr-4 file:rounded-lg file:border-0 file:px-4 file:py-2.5
file:bg-gray-100 file:text-gray-500" />
</div>
<div class="flex gap-3 pt-2">
<button type="button" class="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Save</button>
<button type="button" class="inline-flex items-center justify-center rounded-md border border-black/10 bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-50">Cancel</button>
</div>
</form>
<template>
<form class="space-y-6 max-w-xl">
<div>
<label for="file-styled" class="block text-sm font-medium text-gray-900 mb-1">Profile photo</label>
<input id="file-styled" name="file-styled" type="file" accept="image/*"
class="block w-full text-sm text-gray-900 file:mr-4 file:rounded-lg file:border-0 file:px-4 file:py-2.5 file:bg-violet/10 file:text-violet-700 hover:file:bg-violet/15 file:ring-1 file:ring-inset file:ring-violet/20" />
<p class="mt-2 text-xs text-gray-500">PNG or JPG up to 2MB.</p>
</div>
<div class="opacity-60">
<label for="file-styled-disabled" class="block text-sm font-medium text-gray-900 mb-1">Disabled state</label>
<input id="file-styled-disabled" name="file-styled-disabled" type="file" disabled
class="block w-full cursor-not-allowed text-sm text-gray-500 file:mr-4 file:rounded-lg file:border-0 file:px-4 file:py-2.5 file:bg-gray-100 file:text-gray-500" />
</div>
<div class="flex gap-3 pt-2">
<button type="button" class="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Save</button>
<button type="button" class="inline-flex items-center justify-center rounded-md border border-black/10 bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-50">Cancel</button>
</div>
</form>
</template>
<script setup>
// UI-only
</script>
export default function StyledFileInput(){
return (
<form className="space-y-6 max-w-xl">
<div>
<label htmlFor="file-styled" className="block text-sm font-medium text-gray-900 mb-1">Profile photo</label>
<input id="file-styled" name="file-styled" type="file" accept="image/*" className="block w-full text-sm text-gray-900 file:mr-4 file:rounded-lg file:border-0 file:px-4 file:py-2.5 file:bg-violet/10 file:text-violet-700 hover:file:bg-violet/15 file:ring-1 file:ring-inset file:ring-violet/20" />
<p className="mt-2 text-xs text-gray-500">PNG or JPG up to 2MB.</p>
</div>
<div className="opacity-60">
<label htmlFor="file-styled-disabled" className="block text-sm font-medium text-gray-900 mb-1">Disabled state</label>
<input id="file-styled-disabled" name="file-styled-disabled" type="file" disabled className="block w-full cursor-not-allowed text-sm text-gray-500 file:mr-4 file:rounded-lg file:border-0 file:px-4 file:py-2.5 file:bg-gray-100 file:text-gray-500" />
</div>
<div className="flex gap-3 pt-2">
<button type="button" className="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Save</button>
<button type="button" className="inline-flex items-center justify-center rounded-md border border-black/10 bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-50">Cancel</button>
</div>
</form>
)
}
3) Drag & Drop Upload (Heroicons)
Dashed dropzone using an inline Heroicon. Click or drop files. Multiple supported.
<form class="max-w-2xl">
<label class="relative block w-full rounded-2xl border-2 border-dashed border-gray-300 p-8 text-center hover:border-gray-400 transition-colors cursor-pointer">
<div class="mx-auto h-12 w-12 text-violet">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" class="h-12 w-12" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M15.75 10.5L12 6.75 8.25 10.5M12 6.75V15.75" />
<path d="M6.75 19.5h10.5a3.75 3.75 0 0 0 0-7.5h-.278A5.251 5.251 0 0 0 6.75 9.75h-.75a4.5 4.5 0 0 0 0 9h.75z" />
</svg>
</div>
<div class="mt-3">
<p class="text-sm font-semibold text-gray-900">Drag & drop files here</p>
<p class="text-sm text-gray-600">or <span class="text-violet">browse</span> your computer</p>
</div>
<input type="file" multiple class="absolute inset-0 opacity-0 cursor-pointer" aria-label="Upload files">
</label>
<p class="mt-3 text-xs text-gray-500">Up to 10 files. Max 10MB each. PDF, PNG, JPG.</p>
<div class="mt-6 grid gap-3 sm:grid-cols-2">
<div class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
<div class="min-w-0">
<p class="truncate text-sm font-medium text-gray-900">invoice-2025-01.pdf</p>
<p class="text-xs text-gray-500">842 KB</p>
</div>
<button type="button" class="rounded-md border border-gray-200 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50">Remove</button>
</div>
<div class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
<div class="min-w-0">
<p class="truncate text-sm font-medium text-gray-900">design-mockup.png</p>
<p class="text-xs text-gray-500">2.1 MB</p>
</div>
<button type="button" class="rounded-md border border-gray-200 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50">Remove</button>
</div>
</div>
<div class="mt-6 flex gap-3">
<button type="button" class="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Upload</button>
<button type="button" class="inline-flex items-center justify-center rounded-md border border-black/10 bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-50">Cancel</button>
</div>
</form>
<template>
<form class="max-w-2xl">
<label class="relative block w-full rounded-2xl border-2 border-dashed border-gray-300 p-8 text-center hover:border-gray-400 transition-colors cursor-pointer">
<div class="mx-auto h-12 w-12 text-violet">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" class="h-12 w-12" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M15.75 10.5L12 6.75 8.25 10.5M12 6.75V15.75" />
<path d="M6.75 19.5h10.5a3.75 3.75 0 0 0 0-7.5h-.278A5.251 5.251 0 0 0 6.75 9.75h-.75a4.5 4.5 0 0 0 0 9h.75z" />
</svg>
</div>
<div class="mt-3">
<p class="text-sm font-semibold text-gray-900">Drag & drop files here</p>
<p class="text-sm text-gray-600">or <span class="text-violet">browse</span> your computer</p>
</div>
<input type="file" multiple class="absolute inset-0 opacity-0 cursor-pointer" aria-label="Upload files" />
</label>
<p class="mt-3 text-xs text-gray-500">Up to 10 files. Max 10MB each. PDF, PNG, JPG.</p>
<div class="mt-6 grid gap-3 sm:grid-cols-2">
<div class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
<div class="min-w-0">
<p class="truncate text-sm font-medium text-gray-900">invoice-2025-01.pdf</p>
<p class="text-xs text-gray-500">842 KB</p>
</div>
<button type="button" class="rounded-md border border-gray-200 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50">Remove</button>
</div>
<div class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
<div class="min-w-0">
<p class="truncate text-sm font-medium text-gray-900">design-mockup.png</p>
<p class="text-xs text-gray-500">2.1 MB</p>
</div>
<button type="button" class="rounded-md border border-gray-200 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50">Remove</button>
</div>
</div>
<div class="mt-6 flex gap-3">
<button type="button" class="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Upload</button>
<button type="button" class="inline-flex items-center justify-center rounded-md border border-black/10 bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-50">Cancel</button>
</div>
</form>
</template>
<script setup>
// UI-only
</script>
export default function DragDropUpload(){
return (
<form className="max-w-2xl">
<label className="relative block w-full rounded-2xl border-2 border-dashed border-gray-300 p-8 text-center hover:border-gray-400 transition-colors cursor-pointer">
<div className="mx-auto h-12 w-12 text-violet">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" className="h-12 w-12" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M15.75 10.5L12 6.75 8.25 10.5M12 6.75V15.75" />
<path d="M6.75 19.5h10.5a3.75 3.75 0 0 0 0-7.5h-.278A5.251 5.251 0 0 0 6.75 9.75h-.75a4.5 4.5 0 0 0 0 9h.75z" />
</svg>
</div>
<div className="mt-3">
<p className="text-sm font-semibold text-gray-900">Drag & drop files here</p>
<p className="text-sm text-gray-600">or <span className="text-violet">browse</span> your computer</p>
</div>
<input type="file" multiple className="absolute inset-0 opacity-0 cursor-pointer" aria-label="Upload files" />
</label>
<p className="mt-3 text-xs text-gray-500">Up to 10 files. Max 10MB each. PDF, PNG, JPG.</p>
<div className="mt-6 grid gap-3 sm:grid-cols-2">
<div className="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
<div className="min-w-0">
<p className="truncate text-sm font-medium text-gray-900">invoice-2025-01.pdf</p>
<p className="text-xs text-gray-500">842 KB</p>
</div>
<button type="button" className="rounded-md border border-gray-200 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50">Remove</button>
</div>
<div className="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
<div className="min-w-0">
<p className="truncate text-sm font-medium text-gray-900">design-mockup.png</p>
<p className="text-xs text-gray-500">2.1 MB</p>
</div>
<button type="button" className="rounded-md border border-gray-200 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50">Remove</button>
</div>
</div>
<div className="mt-6 flex gap-3">
<button type="button" className="inline-flex items-center justify-center rounded-md bg-violet px-4 py-2 text-sm font-semibold text-white hover:brightness-110 border border-black/10">Upload</button>
<button type="button" className="inline-flex items-center justify-center rounded-md border border-black/10 bg-white px-4 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-50">Cancel</button>
</div>
</form>
)
}
Notes
- Use the
file:
variant to style the native file button without extra wrappers. - The drag-and-drop card uses an invisible input covering the label so the whole surface is clickable.
- Inline Heroicon is the 24/outline “CloudArrowUp” path; swap any icon from Heroicons the same way.