File input
A native file input behind a label styled as a secondary button, optionally inside a drop zone, with a list of chosen files and per-file errors.
stable · light DOM · 4.0.0. Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
Choose local files. This component does not upload them; the host validates its own file policy.
<j3w1-file-input id="example-file-input-default"><div class="file-input">
<label class="file-input-label" for="example-file-input-default-fi-attach">Attachments</label>
<div class="file-input-root">
<input class="file-input-native" id="example-file-input-default-fi-attach" type="file" multiple="" aria-describedby="example-file-input-default-fi-attach-help example-file-input-default-fi-attach-message" name="example-file-input-default-fi-attach">
<label class="file-input-button" for="example-file-input-default-fi-attach">Choose files</label>
<span class="file-input-status" role="status">No file chosen</span>
<span class="file-input-drop-hint" aria-hidden="true">Release to add</span>
</div>
<div class="file-input-progress" aria-hidden="true"></div>
<p class="file-input-help" id="example-file-input-default-fi-attach-help">Choose local files. This component does not upload them; the host validates its own file policy.</p>
<p class="file-input-message" id="example-file-input-default-fi-attach-message">✕ Choose at least one file.</p>
</div></j3w1-file-input>drop-zone
Choose local files. This component does not upload them; the host validates its own file policy.
<j3w1-file-input id="example-file-input-drop-zone"><div class="file-input">
<label class="file-input-label" for="example-file-input-drop-zone-fi-zone">Screenshots</label>
<div class="file-input-root file-input-zone">
<input class="file-input-native" id="example-file-input-drop-zone-fi-zone" type="file" multiple="" accept="image/png,image/webp" aria-describedby="example-file-input-drop-zone-fi-zone-help example-file-input-drop-zone-fi-zone-message" name="example-file-input-drop-zone-fi-zone">
<label class="file-input-button" for="example-file-input-drop-zone-fi-zone">Choose files</label>
<span class="file-input-status" role="status">No file chosen</span>
<span class="file-input-drop-hint" aria-hidden="true">Release to add</span>
</div>
<div class="file-input-progress" aria-hidden="true"></div>
<p class="file-input-help" id="example-file-input-drop-zone-fi-zone-help">Choose local files. This component does not upload them; the host validates its own file policy.</p>
<p class="file-input-message" id="example-file-input-drop-zone-fi-zone-message">✕ Only PNG or WebP files.</p>
</div></j3w1-file-input>with-list
Choose local files. This component does not upload them; the host validates its own file policy.
<j3w1-file-input id="example-file-input-with-list"><div class="file-input">
<label class="file-input-label" for="example-file-input-with-list-fi-list">Attachments</label>
<div class="file-input-root">
<input class="file-input-native" id="example-file-input-with-list-fi-list" type="file" multiple="" aria-describedby="example-file-input-with-list-fi-list-help example-file-input-with-list-fi-list-message" name="example-file-input-with-list-fi-list">
<label class="file-input-button" for="example-file-input-with-list-fi-list">Add files</label>
<span class="file-input-status" role="status">No file chosen</span>
<span class="file-input-drop-hint" aria-hidden="true">Release to add</span>
</div>
<div class="file-input-progress" aria-hidden="true"></div>
<ul class="file-input-list" aria-label="Chosen files"></ul>
<p class="file-input-help" id="example-file-input-with-list-fi-list-help">Choose local files. This component does not upload them; the host validates its own file policy.</p>
<p class="file-input-message" id="example-file-input-with-list-fi-list-message">✕ One file could not be added.</p>
</div></j3w1-file-input>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/file-input.css';
import '@j3w1/ui/register/file-input';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy file-input --out ./vendor/j3w1/file-inputThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
files | File[] | Current native files; files are never uploaded by the component. Read only. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
clear | (): void | Clear the current native file selection. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | Native file selection changed; read files from the component. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
File input file-input
A native file input behind a label styled as a secondary button, optionally inside a drop zone, with a list of chosen files and per-file errors.
Report an issue with this component
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
button.bg | color.action.secondary.bgobserveduse | rgb(0 0 0 / 0%) | Secondary button has no fill. |
button.text | color.action.secondary.textapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
button.border | color.action.secondary.borderapproveduse | #986f72 | D-041 action.secondary.border: color.primitive.modern.control. |
button.bg-hover | color.action.secondary.hover-bgobserveduse | #630f0d | Secondary button hover. |
button.bg-pressed | color.action.secondary.pressed-bgobserveduse | #420f0c | Secondary button pressed. |
button.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
button.border-invalid | color.status.danger.borderobserveduse | #e53935 | Danger border, 2px on invalid inputs. |
button.text-disabled | color.text.disabledapproveduse | #a3676b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
button.border-disabled | color.border.disabledapproveduse | #482b2b | D-041 border.disabled: color.primitive.modern.divider. |
button.bg-disabled | color.interaction.disabled.bgapproveduse | #0d0707 | D-041 interaction.disabled.bg: color.primitive.modern.panel. |
zone.border | color.interaction.drop-targetobserveduse | #e53935 | Dashed drop-target border. |
zone.bg-drop-target | color.interaction.marqueeobserveduse | rgb(145 20 16 / 12%) | Drag marquee and ghost. |
zone.hint | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
label.text | color.text.brightapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
status.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
list.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
list.divider | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
list.size | color.text.subtleapproveduse | #bd787d | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
list.error | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
remove.text | color.action.tertiary.textapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
remove.bg-hover | color.action.tertiary.hover-bgobserveduse | #1c0a09 | Ghost and icon button hover. |
progress.track | color.interaction.pressed.bgobserveduse | #420f0c | Pressed fill. |
progress.stripe | color.action.primary.bgapproveduse | #7d1310 | Primary button fill; D-026 darkened it off color12, which stays fixed as heritage ANSI 12. |
help.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
message.text | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
State matrix
Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).
| State | Button | Drop zone | With chosen files |
|---|---|---|---|
default |
No file chosen
Up to five files, 10 MB each. |
or drop PNG or WebP files here
PNG or WebP, 4 MB each. |
3 files chosen
Up to five files, 10 MB each. |
hover | |||
focus-visible | |||
drop-target | |||
invalid | |||
disabled | |||
loading |
Purpose
Choosing one or more files from the host. The native input keeps the host chooser, its keyboard and its security model; the theme replaces only its rendering.
Anatomy
Parts as listed in the anatomy; help and message sit below the root.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | button: 1px color.action.secondary.border outline, text color.action.secondary.text; zone: 1px dashed color.interaction.drop-target | — |
| hover | button → color.action.secondary.hover-bg; border unchanged | cursor: pointer |
| focus-visible | 1px dashed color.interaction.focus.ring at −2px on the button (the input has focus) | the ring |
| drop-target | root fill color.interaction.marquee; 1px dashed color.interaction.drop-target border | dashed pattern; hint reads “Release to add” |
| invalid | button border 2px color.status.danger.border; message with ✕; a failed row shows its own ✕ | border 1 → 2px; glyphs; aria-invalid |
| disabled | button text color.text.disabled, border color.border.disabled, fill color.interaction.disabled.bg; zone dashes color.border.disabled; no hover | disabled; cursor: not-allowed |
| loading | static 45° stripes, color.action.primary.bg on color.interaction.pressed.bg, below the row; the button stays enabled | aria-busy; status reads “Uploading n files”; no animation |
Precedence: disabled > loading > invalid > drop-target > hover; focus-visible is always drawn.
Keyboard
As in the key table; closing the chooser returns focus to the input. Dropping is pointer-only and never the sole way to add a file.
Accessibility
Hide the input with sr-only, never display: none or visibility: hidden,
so it keeps focus and the chooser. Both labels name it (“Attachments”,
“Choose files”); aria-describedby lists help and message. The status and
list are announced through a polite live region after a choice; per-file
errors are in the row text. The drop zone has no role and no tabindex.
Text meets 4.5:1; borders 3:1.
Portability
The button is the secondary button of the actions family; the stripes are a repeating gradient or a hatched brush. Toolkits without drag-and-drop omit the zone and keep the button; without a translucent fill, composite the marquee over the canvas and record the value.
Non-examples
display: none on the input and a button that clicks it by script. A drop
zone as the only way to add files. A rounded, dotted or pulsing zone. A
spinner, or animated stripes, while uploading. Errors shown by a red file
name alone. Disabling by opacity.
Contrast report
18 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| file-input default text | default | text | color.action.secondary.text #ffa2a7 | color.surface.canvas #000000 | 10.98 | 4.5 | pass |
| file-input default border | default | ui | color.action.secondary.border #986f72 | color.surface.canvas #000000 | 4.85 | 3 | pass |
| file-input hover text | hover | text | color.action.secondary.text #ffa2a7 | color.action.secondary.hover-bg #630f0d | 6.84 | 4.5 | pass |
| file-input focus-visible text | focus-visible | text | color.action.secondary.text #ffa2a7 | color.surface.canvas #000000 | 10.98 | 4.5 | pass |
| file-input focus-visible border | focus-visible | ui | color.action.secondary.border #986f72 | color.surface.canvas #000000 | 4.85 | 3 | pass |
| file-input focus-visible outline | focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.canvas #000000 | 4.97 | 3 | pass |
| file-input drop-target text | drop-target | text | color.text.default #e99499 | color.interaction.marquee #110202 | 8.84 | 4.5 | pass |
| file-input drop-target border | drop-target | ui | color.interaction.drop-target #e53935 | color.interaction.marquee #110202 | 4.80 | 3 | pass |
| file-input invalid text | invalid | text | color.action.secondary.text #ffa2a7 | color.surface.canvas #000000 | 10.98 | 4.5 | pass |
| file-input invalid border | invalid | ui | color.status.danger.border #e53935 | color.surface.canvas #000000 | 4.97 | 3 | pass |
| hovered button boundary against the canvas outside it | hover | ui | color.action.secondary.border #986f72 | color.surface.canvas #000000 | 4.85 | 3 | pass |
| drop-zone dashed border at rest | — | ui | color.interaction.drop-target #e53935 | color.surface.canvas #000000 | 4.97 | 3 | pass |
| status, hint and help text on the canvas | — | text | color.text.muted #d4868b | color.surface.canvas #000000 | 7.58 | 4.5 | pass |
| file sizes on the canvas | — | text | color.text.subtle #bd787d | color.surface.canvas #000000 | 6.15 | 4.5 | pass |
| per-file error and validation message on the canvas | — | text | color.status.danger.text #f07178 | color.surface.canvas #000000 | 7.34 | 4.5 | pass |
| disabled button text (exempt; house floor 3:1) | disabled | ui | color.text.disabled #a3676b | color.interaction.disabled.bg #0d0707 | 4.48 | 3 | pass |
| disabled border (exempt) | disabled | ui | color.border.disabled #482b2b | color.interaction.disabled.bg #0d0707 | 1.58 | 1 | pass |
| progress stripes (decorative pattern) | loading | ui | color.action.primary.bg #7d1310 | color.interaction.pressed.bg #420f0c | 1.51 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab / Shift+Tab | Reaches the native input (drawn as the button), then each remove button in the list. |
| Enter / Space | Opens the host file chooser from the input. |
| Delete / Backspace | On a focused remove button, same as activating it. |
| Escape | Closes the host chooser (host behaviour). |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: button text-field progress list field
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-OVERFLOW FX-STATE-MATRIX