FileInput

A file picker that can be styled.

import { ... } from '@corndev/ui'
No file chosen

Nothing chosen yet.

HTML

<span class="corn-file-input">
  <input type="file" id="spec" class="corn-file-input-control" />
  <label for="spec" class="corn-button corn-file-input-trigger" data-variant="button" data-level="secondary" data-size="md">Choose file</label>
  <span class="corn-file-input-name" data-empty>No file chosen</span>
</span>

Classes

Class
.corn-file-input
.corn-file-input-control
.corn-file-input-trigger
.corn-file-input-name

Attributes

AttributeValues
data-emptypresent or absent

Vanilla JS

Required for behaviour — adds keeps the filename display in step with the input and reports the chosen files.

import { createFileInput } from '@corndev/ui';
import '@corndev/ui/theme';

const el = document.querySelector('#target');
const instance = createFileInput(el, { /* options */ });

Options

OptionTypeDefaultDescription
placeholderstring'No file chosen'Shown before anything is picked.
onChange(files: FileList) => voidundefinedCalled with the chosen files.

React

FileInput — from @corndev/ui-react.

Props

PropTypeDefaultDescription
labelReact.ReactNode'Choose file'The trigger’s text.
placeholderstring'No file chosen'Shown before a choice.
multiplebooleanfalseAllow several files.
disabledbooleanfalse
acceptstringundefinedPassed to the input, e.g. .json,application/json.
onChange(files: FileList) => voidundefinedCalled with the chosen files.
classNamestringundefinedMerged with the Corn UI classes.

The native file input is close to unstylable, and its button text is set by the browser and the locale. This is the standard answer: keep the real input and let a <label> drive it.

import { FileInput } from '@corndev/ui-react';
import '@corndev/ui/theme';

<FileInput
  label="Choose spec"
  accept=".json,.yaml"
  onChange={files => load(files[0])}
/>

What it deliberately does not do

It does not replace the input with a button that forwards clicks. The real <input type="file"> is still there, because it is what opens the picker, what a form submits, and what a screen reader announces.

It is hidden visually only. display: none would take it out of the accessibility tree and off the keyboard — that is the usual way this component gets broken, and it is invisible until someone tries to reach it with Tab.

Since focus lands on that hidden input, the focus ring is drawn on the trigger beside it. Tab to it and you can see where you are.

Vanilla

Write the input, a label pointing at it, and somewhere to put the filename:

<span class="corn-file-input">
  <input type="file" id="spec" />
  <label for="spec" class="corn-button" data-level="secondary">Choose file</label>
  <span data-file-name></span>
</span>
import { createFileInput } from '@corndev/ui';

createFileInput(document.querySelector('.corn-file-input'), {
  placeholder: 'No spec chosen',
  onChange: files => load(files[0]),
});

Clicking the label opens the picker with no JavaScript at all — the controller only keeps the filename display in step.