File picker
The richer sibling of .re-file: a styled drop /
browse area built by visually hiding the native <input type="file"> (which
stays the form value) and driving a custom surface. Click-to-pick works with
zero JavaScript — the <label> opens the OS picker. The optional
enhanceFilePicker behavior (or the
<re-file-picker> element) layers
on the filename readout, drag-and-drop, clear, and validation.
Reach for .re-file when you just want the native button styled; reach for this
when you want an icon, a drop zone, or a custom prompt.
<div class="re-file-picker" data-re-file-picker>
<label class="re-file-picker__field">
<input
type="file"
class="re-file-picker__input"
name="docs"
aria-label="Upload documents"
/>
<span class="re-file-picker__ui">
<svg
class="re-file-picker__icon"
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
<span class="re-file-picker__prompt">Choose a file or drag it here</span>
</span>
</label>
<p class="re-file-picker__list" hidden></p>
<button
class="re-file-picker__clear"
data-re-file-clear
hidden
type="button"
aria-label="Clear selection"
>
Clear
</button>
<span class="re-file-picker__status re-sr-only" role="status" aria-live="polite"></span>
</div> <div class="stack">
<div class="re-file-picker" data-size="sm">
<label class="re-file-picker__field">
<input type="file" class="re-file-picker__input" aria-label="Small picker" />
<span class="re-file-picker__ui">
<span class="re-file-picker__prompt">Choose a file</span>
</span>
</label>
</div>
<div class="re-file-picker" data-size="lg">
<label class="re-file-picker__field">
<input type="file" class="re-file-picker__input" aria-label="Large picker" />
<span class="re-file-picker__ui">
<span class="re-file-picker__prompt">Choose a file or drag it here</span>
</span>
</label>
</div>
</div> States
Section titled “States”<div class="stack">
<div class="re-file-picker">
<label class="re-file-picker__field">
<input
type="file"
class="re-file-picker__input"
aria-label="Invalid picker"
aria-invalid="true"
/>
<span class="re-file-picker__ui">
<span class="re-file-picker__prompt">Choose a file</span>
</span>
</label>
</div>
<div class="re-file-picker">
<label class="re-file-picker__field">
<input
type="file"
class="re-file-picker__input"
aria-label="Disabled picker"
disabled
/>
<span class="re-file-picker__ui">
<span class="re-file-picker__prompt">Choose a file</span>
</span>
</label>
</div>
</div> Validation
Section titled “Validation”enhanceFilePicker enforces accept, data-re-file-max-files, and
data-re-file-max-size on dropped files (the OS picker already filters by
accept); a rejected set sets aria-invalid and emits a re-error event with
{ reason, rejected, accepted }. Client checks are a convenience — the server is
the only trust boundary.
<div
class="re-file-picker"
data-re-file-picker
data-re-file-max-files="1"
data-re-file-max-size="1000000"
>
<label class="re-file-picker__field">
<input
type="file"
class="re-file-picker__input"
name="image"
accept="image/*"
multiple
aria-label="Upload one image"
/>
<span class="re-file-picker__ui">
<span class="re-file-picker__prompt">One image, up to 1 MB</span>
</span>
</label>
<p class="re-file-picker__list" hidden></p>
<button
class="re-file-picker__clear"
data-re-file-clear
hidden
type="button"
aria-label="Clear selection"
>
Clear
</button>
<span class="re-file-picker__status re-sr-only" role="status" aria-live="polite"></span>
</div> Accessibility
Section titled “Accessibility”- Keyboard — fully native. Tab moves focus to the (visually hidden but focusable) input; Space / Enter opens the OS picker. Drag-and-drop is a pointer-only enhancement (WCAG 2.5.7) — the picker is always the keyboard path, so nothing is keyboard-inaccessible.
- Focus — the input is clipped offscreen, so the visible
:focus-visiblering is drawn on__uivia:focus-within(and re-established as aHighlightoutline under forced colors, where the box-shadow ring is stripped). The input’s own ring is suppressed so it can’t ghost. - Name vs. instruction — give the input an accessible name with
aria-label(or an associated.re-fieldlabel) that says what to upload (“Resume”); the visible__promptis the instruction (“Choose a file or drag it here”), not the name. - Selection announced — without the behavior the picker still works, but the chosen file isn’t echoed (the native text is on the hidden input).
enhanceFilePickerwrites the filenames into__list(visible) and announces a summary through the sr-onlyrole="status"__statusregion — so the selection reaches assistive tech. - Clear — the
__clearbutton is a real<button>placed outside the<label>(inside, every click would reopen the picker); it’s authoredhiddenand only revealed by the behavior, with anaria-label. - Notes —
[data-dragover]and[data-has-files]use distinct system colors under forced colors so the two states stay distinguishable; hover uses--re-color-bg-mutedso it survives dark mode. See the accessibility guide.