# File field

The browser’s own file input, dressed as a key and a printed file name in a well. The input itself is the control, so it takes the focus, opens the chooser from the keyboard, accepts a file dropped on it and posts with its form, with or without a script. With a script, a chosen set can be listed under it, one ruled row to a file with its size and a key to take it out.

Kit part · Free · https://tactileui.dev/parts/file-field

- Element: `<input class="tui-file-field__input" type="file">`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Categories: File uploads, Forms, Inputs
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### At rest · nothing chosen

```html
<div class="tui-field"><label class="tui-label" for="ff82">Invoice</label><div class="tui-file-field"><input class="tui-file-field__input" id="ff82" name="ff82" type="file" tabindex="-1" aria-label="Invoice"></div></div>
```

### Focus

```html
<div class="tui-field"><label class="tui-label" for="ff83">Invoice</label><div class="tui-file-field"><input class="tui-file-field__input is-focus" id="ff83" name="ff83" type="file" tabindex="-1" aria-label="Invoice"></div></div>
```

### The key pressed

```html
<div class="tui-field"><label class="tui-label" for="ff84">Invoice</label><div class="tui-file-field"><input class="tui-file-field__input is-down" id="ff84" name="ff84" type="file" tabindex="-1" aria-label="Invoice"></div></div>
```

### Several · multiple

```html
<div class="tui-field"><label class="tui-label" for="ff85">Receipts</label><div class="tui-file-field"><input class="tui-file-field__input" id="ff85" name="ff85" type="file" multiple tabindex="-1" aria-label="Receipts"></div><span class="tui-hint">Any number of images or PDFs.</span></div>
```

### Listed · data-file-list

```html
<div class="tui-field"><span class="tui-label">Receipts</span><div class="tui-file-field"><input class="tui-file-field__input" type="file" multiple tabindex="-1" aria-label="Receipts"><ul class="tui-file-field__list" aria-label="Chosen files"><li><span class="tui-file-field__name">taxi-0412.pdf</span><span class="tui-file-field__size">84 KB</span><button class="tui-key tui-key--flat tui-key--sm" type="button" tabindex="-1" aria-label="Remove taxi-0412.pdf">Remove</button></li><li><span class="tui-file-field__name">lunch.jpg</span><span class="tui-file-field__size">1.2 MB</span><button class="tui-key tui-key--flat tui-key--sm" type="button" tabindex="-1" aria-label="Remove lunch.jpg">Remove</button></li></ul></div></div>
```

### Error

```html
<div class="tui-field" data-error><label class="tui-label" for="ff86">Invoice</label><div class="tui-file-field"><input class="tui-file-field__input" id="ff86" name="ff86" type="file" tabindex="-1" aria-label="Invoice"></div><span class="tui-hint">Choose the invoice to send.</span></div>
```

### Disabled

```html
<div class="tui-field"><label class="tui-label" for="ff87">Invoice</label><div class="tui-file-field"><input class="tui-file-field__input" id="ff87" name="ff87" type="file" disabled tabindex="-1" aria-label="Invoice"></div></div>
```

## Every state

- Rest
- Hover
- Pressing
- Focus

Hover, pressed and focus are forced with `.is-hover`, `.is-down` and `.is-focus`; checked and disabled are real.

## API

| Name | Kind | Values | What it does |
| - | - | - | - |
| input.tui-file-field__input | element | type="file", with a name and an id | The real input, drawn as the well with the browser’s own button as the key. Set accept, multiple, required and disabled on it as on any file input. |
| .tui-file-field | class | around the input | The wrapper. Needed only to list the chosen files; a lone input works without it. |
| data-file-list | attribute | on the wrapper | List the chosen files under the well, one ruled row to a file, with its size and a key that takes it out. The input’s files are rebuilt to match. |
| .tui-file-field__list · __name · __size | class | made by the script | The list, a name and a size. The list is empty, and not drawn, until a file is chosen. |
| tui-file-field-change | event | detail { files } | Bubbles from the wrapper after a file is taken out of the list. The input’s own change fires when files are chosen. |
| fileSize(bytes) · fileSummary(names) | function | pure | A size in the units people read (“84 KB”), and what was chosen in words (“No file chosen”, “3 files”). |
| Space · Enter | key | on the input | Open the browser’s file chooser. |

## Markup

```html
<div class="tui-field">
  <label class="tui-label" for="invoice">Invoice</label>
  <div class="tui-file-field">
    <input class="tui-file-field__input" id="invoice" name="invoice" type="file" accept=".pdf">
  </div>
  <span class="tui-hint">A PDF, up to 5 MB.</span>
</div>

<!-- Several, listed with their sizes -->
<div class="tui-file-field" data-file-list>
  <input class="tui-file-field__input" id="receipts" name="receipts" type="file" multiple>
</div>
```

```js
import { fileSize, fileSummary } from '/kit/kit.mjs';

document.querySelector('.tui-file-field').addEventListener('tui-file-field-change', (e) => {
  upload.queue = e.detail.files;              // after a file is taken out of the list
});

fileSize(84200);                              // '84 KB'
fileSummary(['a.pdf', 'b.pdf']);              // '2 files'
```

The demo on the page:

```html
<form class="tui-panel" style="width:min(100%,460px);display:grid;gap:16px;text-align:left" action="/parts/file-field" method="get" data-ff-form><div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Expenses · Northwind</span><span class="tui-status"><span class="tui-lamp" data-on></span>Open</span></div>
  <div class="tui-field"><label class="tui-label" for="ff-one">Invoice</label><div class="tui-file-field"><input class="tui-file-field__input" id="ff-one" name="ff-one" type="file" accept=".pdf" aria-label="Invoice"></div><span class="tui-hint">A PDF, up to 5 MB.</span></div>
  <div class="tui-field"><label class="tui-label" for="ff-many">Receipts</label><div class="tui-file-field" data-file-list><input class="tui-file-field__input" id="ff-many" name="ff-many" type="file" multiple aria-label="Receipts"></div><span class="tui-hint">Any number of images or PDFs.</span></div>
  <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap"><output class="tui-muted" style="font-size:var(--tui-t-small)" data-ff-out aria-live="polite">Nothing sent yet</output><button class="tui-key tui-key--primary" type="submit">Send</button></div></form>
```

## Usage notes

Press Choose file, or Tab to the field and press Space: the browser’s chooser opens, and what you pick is printed beside the key. The second field takes several and lists them with their sizes, each with a key that takes it out. Both post with the form.

## Accessibility

- It is the browser’s own file input, so the browser says what a screen reader says for it, “Invoice, button, no file chosen”, in the visitor’s language, and the label’s `for` names it.
- The focus is the input’s, a 2px ring round the whole well; nothing is laid over it, so a pointer, a keyboard, a switch device and a file dragged from the desktop all reach it.
- What was chosen is the browser’s printed name, and the list under it says the same in rows. A polite line says “Chosen: 3 files” and, after a removal, “Removed lunch.jpg. taxi-0412.pdf.”
- Each Remove key is named for its file, “Remove lunch.jpg”, and the focus lands on the next key (or the input) after one is pressed, never nowhere.
- On a phone the well is 48px tall, the key is 42px inside it and every Remove key is 44px square; the text is 16px so the page is not zoomed.

## How it’s built

The browser lets a page restyle exactly one part of a file input, the button, with `::file-selector-button`, and everything else about the input is its own. So the well is the kit’s field on the input itself and the key is that button drawn as a key: the same cap, skirt and pressed shadow, taking its press from `:active`. The list is the one thing the browser cannot do, and the removal is done by building a new set of files with a `DataTransfer` and handing it back to the input, so the form posts what is shown.

## Rules

A file input that looks like the rest of the form and still is the browser’s: real focus, real posting, real drag and drop, and a list of what you chose.

The full rules are in `file-field.rules.md` in the brain, which ships with the Kit.

## Related parts

- [Drop zone](https://tactileui.dev/parts/drop-zone): An in-tray for files. A machined lip stands round a ribbed floor; the floor rises a little when a file is dragged over it, and a lamp on the lip lights. What goes in lands in the tray as a sheet of paper and is listed as printed rows, a refusal says why, and the key inside the tray is a real file input.
- [Field](https://tactileui.dev/parts/field): Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
- [Input group](https://tactileui.dev/parts/input-group): One recess that holds a field and everything that belongs to it. Words that are part of the value but are not typed, such as https:// or a currency, are printed on the floor of the well behind an engraved line; a glyph sits at the start; small keys sit at the end to clear the text, copy it, or show a password.
- [Key](https://tactileui.dev/parts/key): Every button, and every link that acts like one: a cap on a skirt that drops below the plate when pressed.
