File upload
BetaZona para arrastrar o elegir archivos, con la lista de lo subido.
registry/ds/file-upload.tsxo arrastralos acá
PDF o imagen, hasta 10 MB cada uno.
import { FileUpload } from "@/registry/ds/file-upload";
export default function FileUploadBasic() {
return (
<div className="w-full max-w-md">
<FileUpload label="Documentación" accept=".pdf,image/*" multiple maxSize={10 * 1024 * 1024} hint="PDF o imagen, hasta 10 MB cada uno.">
o arrastralos acá
</FileUpload>
</div>
);
}Un solo archivo, controlado
value y onChange dejan el archivo en manos del padre: lo muestra, lo
valida con sus propias reglas y lo quita. accept y maxSize filtran antes.
o arrastrala acá
JPG o PNG, hasta 2 MB. De frente y sin anteojos oscuros.
"use client";
import { useState } from "react";
import { Button } from "@/registry/ds/button";
import { FileUpload } from "@/registry/ds/file-upload";
/** Un solo archivo, controlado: el padre guarda el File, lo muestra y permite quitarlo; el error viene de tu validación */
export default function FileUploadSingle() {
const [files, setFiles] = useState<File[]>([]);
const [error, setError] = useState<string | undefined>();
const foto = files[0];
return (
<div className="flex w-full max-w-md flex-col gap-4">
<FileUpload
size="sm"
label="Foto para el carnet"
accept="image/jpeg,image/png"
maxSize={2 * 1024 * 1024}
value={files}
onChange={(next) => {
setFiles(next.slice(0, 1));
setError(next.length > 1 ? "Solo una foto. Nos quedamos con la primera." : undefined);
}}
error={error}
hint="JPG o PNG, hasta 2 MB. De frente y sin anteojos oscuros."
>
o arrastrala acá
</FileUpload>
{foto && (
<div className="flex items-center justify-between gap-3 rounded-lg border border-border bg-bg-elevated px-3 py-2 text-sm">
<span className="truncate text-fg">
{foto.name} <span className="text-fg-muted">· {(foto.size / 1024).toFixed(0)} KB</span>
</span>
<Button size="sm" variant="text" onClick={() => setFiles([])}>
Quitar
</Button>
</div>
)}
</div>
);
}Reglas
- Decí qué se acepta y cuánto pesa en la ayuda, antes de que fallen: "PDF o imagen, hasta 10 MB".
- Los archivos rechazados se explican con nombre y motivo; los demás se aceptan igual.
- Mostrá cada archivo con nombre, tamaño y cómo quitarlo.
- Si la subida tarda, un Progress por archivo.
- En mobile no hay arrastre: el link "Elegí un archivo" es el camino principal y abre la cámara o los archivos del sistema.
| Parte | Medida |
|---|---|
| Zona | padding 32 × 24 (sm 20 × 16) · radius-xl · borde punteado |
| Icono | círculo 40, glifo 20 |
| Archivo | fila padding 8 × 12 · radius-md · gap 6 |
| Parte | Propiedad | Token |
|---|---|---|
| Zona | Fondo | bg/elevated |
| Zona | Borde punteado | border/default |
| Zona · hover / arrastrando | Borde | primary/accent |
| Zona · hover / arrastrando | Fondo | primary/faint |
| Icono | Fondo / glifo | primary/subtle |
| Link "Elegí" | Color | primary/accent |
| Texto | Color | text/muted |
| Archivo | Fondo | bg/subtle |
| Archivo | Tamaño | text/subtle |
| Deshabilitado | Fondo / texto | bg/subtle |
Instalación
npx shadcn@latest add https://polen.cdc.cool/r/file-upload.jsonPlayground
Configurá el componente y copiá el snippet: se genera del mismo estado que renderiza el preview.
o arrastralos acá
Hasta 10 MB por archivo.
size
Acepta
<FileUpload label="Documentación" hint="Hasta 10 MB por archivo." multiple maxSize={10 * 1024 * 1024}>o arrastralos acá</FileUpload>Props
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
accept | string | — | Como en <input accept>: ".pdf,image/*". |
multiple | boolean | false | Varios archivos. |
maxSize | number | — | Bytes por archivo. Los que lo superan se rechazan con mensaje. |
value / onChange | File[] · (files) => void | — | Controlado. |
children | ReactNode | "o arrastralo acá" | Texto después del link. |
size · label · hint · error · required · disabled · name | — | — | Como en Input. |
- El trabajo lo hace un
<input type="file">real: Enter o Espacio sobre el link abren el selector del sistema; el lector de pantalla lo anuncia como control de archivos con su label. - El foco se ve en toda la zona.
- Cada "Quitar" tiene nombre con el archivo.
- El arrastre es un atajo: todo funciona sin él.