FileField
A file picker that looks like it belongs to the page.
Why it exists
<input type="file"> appears in six files — Echo's audience import, Mint's payment attachments, Grove's page attachments, Harvest's importer, and two of Hana's forms.
Four of the six left it unstyled
file: pseudo-element chain nobody remembers. That chain is here once. The two that were styled had already drifted from each other.Styled, labelled, and it says what it wants
The hint is wired with aria-describedby, so “CSV, up to 5 MB” is announced when focus lands rather than discovered on rejection.
CSV with a header row, up to 5 MB.
In a form
It is a native input, so it works inside a server action's form with no client component anywhere.
Images and PDFs.
In both themes
light
dark
Guidance
Use it when
- Any upload. It is the native control, so it keeps the platform's picker on every device.
- hint to say the format and the ceiling before the file is chosen.
- accept to filter the picker's default view.
Reach for something else when
- Treating accept as validation. The user can switch the picker to “all files”, so the server still has to check — that is how an importer ends up parsing a JPEG.
- A drag-and-drop zone as the only way in. It is undiscoverable by keyboard and impossible on a phone; a dropzone is an addition to this control, never a replacement.
- Hiding the input behind a styled label without keeping it focusable. That is the usual way an upload becomes mouse-only.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| hint | string | — | What is acceptable, in words. Wired with aria-describedby. |
| accept | string | — | Native. Filters the picker — it is not a gate. |
| … | InputHTMLAttributes | — | Everything a native file input takes: name, required, multiple, disabled. |
Accessibility
The focus ring is on the input rather than the button inside it, because no browser lets anything focus a ::file-selector-button — styling the pseudo-element and leaving the ring off the input is how a file picker ends up with no visible focus at all. The id is derived from useId unless one is passed, so a Field wrapping it binds its label to the right control even with several on a page.