File
A real <input type="file">, styled through ::file-selector-button rather than replaced.
Example (a11y tier)
Import
import File from 'abaabil/file' // structure onlyimport File from 'abaabil/file/styled' // + CSSimport File from 'abaabil/file/a11y' // + label/description/errorNot a button with a hidden input
The usual way to build this is a styled <button> with a visually hidden <input type="file"> behind it. That pattern has to reimplement three things the real control already does: associating the label, activating from the keyboard, and announcing which file was chosen. Most implementations manage one of the three, and the missing one is usually the announcement, which is invisible to the person who built it.
It is also unnecessary, because ::file-selector-button is a real pseudo-element: the browser's own button can be styled directly. That is what this component does.
accept is a filter, not an announcement
accept narrows what the file dialog shows. It is never read out, and it does not apply at all to a file dropped onto the control. So on its own it tells the user nothing about what you will take, and the first they learn of it is a rejection.
Say it in words as well, in description, which is wired through aria-describedby so it is read with the field rather than sitting near it. The a11y tier warns in development when accept is set without one.
Props
| Prop | Type | Default | Tier | Description |
|---|---|---|---|---|
className | string | — | all | Merged with the base class. |
label | string | — | a11y | Rendered as a real <label>. |
hideLabel | boolean | false | a11y | — |
description | string | — | a11y | Wired into aria-describedby. Say what you accept here. |
error | string | — | a11y | Sets aria-invalid and joins aria-describedby. |
| ...props | — | — | all | accept, multiple, onChange and the rest land on the input. |