File upload
A drop zone that is a real file input, so it works and posts before any script runs. Picked files show as a list.
client - size not measured yet
File upload
client - size not measured yetA drop zone that is a real file input, so it works and posts before any script runs. Picked files show as a list.
Try it
maxFiles
maxSize
invalid
visibleRows
The code
file-upload.tsx
import { FileUpload } from 'topclassui/file-upload'
<div className="grid gap-1.5">
<label htmlFor="file-upload-playground" className="text-sm font-medium text-body">Attachment</label>
<FileUpload id="file-upload-playground" name="attachment" accept=".png,.jpg,.jpeg,.pdf">
PNG, JPG or PDF
</FileUpload>
</div>maxSize is in bytes and checked in the browser only: check it again in the action.
Every state
2 statesDrop files hereBrowseA file is required
Drop files hereBrowseUploads are closed
Import from the package
topclassui/file-upload
import { FileUpload } from 'topclassui/file-upload'Install once:npm i topclassui@beta zod
Examples
- The children are the hint inside the zone, such as "PDF or PNG, up to 5 MB", and they describe the input.
maxFiles(1 to 100, default 1) alone decides whether the input takes several files at once; with 1 a new file replaces the old one.maxSizerefuses a larger file as too large; each refusal is read out, and a refused file is never sent.- An input cannot set its form's encType: a form with a string action needs multipart/form-data, or it sends only the file names.
- The list adds up across picks and drops and shows
visibleRowsrows before it scrolls.onValueChangehears the kept files after each change.
basic
Drop files hereBrowsePNG, JPG or PDF, up to 10 MB
basic.tsx
import { FileUpload } from 'topclassui/file-upload'
/** One file, named by its label. The hint inside the zone describes the input. */
export default function Demo() {
return (
<div className="grid gap-1.5">
<label htmlFor="file-upload-basic" className="text-sm font-medium text-body">Attachment</label>
<FileUpload id="file-upload-basic" name="attachment" accept=".png,.jpg,.jpeg,.pdf" maxSize={10 * 1024 * 1024}>
PNG, JPG or PDF, up to 10 MB
</FileUpload>
</div>
)
}
multiple
Drop files hereBrowsePDF or PNG, up to 5 files
multiple.tsx
import { FileUpload } from 'topclassui/file-upload'
/** The limits of the card's sample: up to five files, PDF or PNG, 5 MB each. maxFiles above 1 makes the input multiple. */
export default function Demo() {
return (
<div className="grid gap-1.5">
<label htmlFor="file-upload-multiple" className="text-sm font-medium text-body">Attachments</label>
<FileUpload id="file-upload-multiple" name="attachments" accept="application/pdf,image/png" maxFiles={5} maxSize={5 * 1024 * 1024}>
PDF or PNG, up to 5 files
</FileUpload>
</div>
)
}
in-field
Drop files hereBrowseDrop them here, 5 MB each
PDF or PNG, up to 5 files
in-field.tsx
import { Field } from 'topclassui/field'
import { FileUpload } from 'topclassui/file-upload'
/** The card's sample: a Field names the input, and its hint describes it after the zone's own. */
export default function Demo() {
return (
<Field name="attachments" label="Attachments" hint="PDF or PNG, up to 5 files">
{(control) => (
<FileUpload {...control} accept="application/pdf,image/png" maxFiles={5} maxSize={5 * 1024 * 1024}>
Drop them here, 5 MB each
</FileUpload>
)}
</Field>
)
}
form
form.tsx
import { Button } from 'topclassui/button'
import { FileUpload } from 'topclassui/file-upload'
/** A multipart form that posts with no script: the input sends one part per file. An input cannot set the form's
* encType, so the form writes it. Required: an empty input blocks the submit. */
export default function Demo() {
return (
<form method="post" encType="multipart/form-data" className="grid gap-3">
<div className="grid gap-1.5">
<label htmlFor="file-upload-form" className="text-sm font-medium text-body">Attachments</label>
<FileUpload id="file-upload-form" name="attachments" maxFiles={5} required>Up to 5 files</FileUpload>
</div>
<div><Button type="submit">Send</Button></div>
</form>
)
}
In Arabic, right to left
أفلت الملفات هناتصفّحPDF أو PNG، حتى 5 ملفات
rtl.tsx
import { TopClassProvider } from 'topclassui/provider'
import { FileUpload } from 'topclassui/file-upload'
/** Arabic, inside TopClassProvider as the root layout would mount it: the zone's words, the sizes and the reasons
* come from the Arabic labels, and each file name takes its own direction. */
export default function Demo() {
return (
<TopClassProvider direction="rtl" locale="ar">
<div dir="rtl" lang="ar" className="grid gap-1.5">
<label htmlFor="file-upload-rtl" className="text-sm font-medium text-body">المرفقات</label>
<FileUpload id="file-upload-rtl" name="attachments" accept="application/pdf,image/png" maxFiles={5} maxSize={5 * 1024 * 1024}>
PDF أو PNG، حتى 5 ملفات
</FileUpload>
</div>
</TopClassProvider>
)
}
Props
Keyboard and accessibility
- A name is required:
idfor a<label htmlFor>, oraria-label, oraria-labelledby. - Each file has a remove button named after it, such as "Remove brief.pdf"; after a remove the focus goes to the next file, else the previous one, else the input.
- A drag of files marks the zone with a solid primary line; under forced colours the line is Highlight.
invalidturns it to danger.
Right to left
The zone centres, the rows mirror, and each file name takes its own direction. Sizes are written in the reader's digits.
Server and client
A client component: it renders on the server and hydrates in the browser.
Files: index.tsx, client.tsx.
On the chip: client - size not measured yet.