::file-selector-button to style the native button. For custom text, visually hide the input, style its label and show selected filenames with a change listener.Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.
Create free accountWhy the native file input is hard to style
A file input is a browser-controlled picker, not a normal text box and button. Its button can be styled with a pseudo-element, but the browser controls the default button wording and the accompanying "No file chosen" text. See the file input reference for the underlying HTML.
Style the button with CSS
Use the standard selector in current browsers. Put the older WebKit rule separately first so an unsupported selector cannot invalidate the standard rule.
input[type=file]::-webkit-file-upload-button {
border: 0; padding: .5rem 1rem; background: #e0e7ff; cursor: pointer;
}
input[type=file]::file-selector-button {
border: 0; border-radius: .25rem; padding: .5rem 1rem;
margin-right: 1rem; background: #e0e7ff; cursor: pointer;
}
input[type=file]::file-selector-button:hover { background: #c7d2fe; }
input[type=file]::-webkit-file-upload-button:hover { background: #c7d2fe; }Change the button text with a label
For complete control over the text, visually hide the input and style its associated label. The input stays in the accessibility tree and tab order. Its focus indicator appears on the adjacent label; clicking the label opens the picker.
<input class="file-input" type="file" id="upload" name="files" multiple>
<label class="upload-button" for="upload">Select files</label>
<p id="file-status" aria-live="polite">No files selected</p>
<style>
.file-input {
position: absolute; width: 1px; height: 1px; padding: 0;
margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0);
clip-path: inset(50%); white-space: nowrap; border: 0;
}
.upload-button { display: inline-block; padding: .5rem 1rem; background: #e0e7ff; cursor: pointer; }
.file-input:focus-visible + label { outline: 3px solid #4338ca; outline-offset: 3px; }
</style>Show selected file names and sizes
Put this code in a <script> after the markup above. It handles one or multiple files and uses textContent so filenames are not interpreted as HTML.
const input = document.querySelector('#upload');
const status = document.querySelector('#file-status');
input.addEventListener('change', () => {
const files = Array.from(input.files);
const names = files.map(file =>
file.name + ' (' + (file.size / 1024).toFixed(1) + ' KiB)'
);
status.textContent = names.length ? names.join(', ') : 'No files selected';
});Add a drag-and-drop zone
Wrap the input, label and status above in <div id="drop-zone">. Keep the label so keyboard and touch users can open the picker. A drop replaces the current selection and dispatches change to update the status.
<style>#drop-zone { padding: 1rem; border: 2px dashed #888; }
#drop-zone.highlight { border-color: #4338ca; background: #eef2ff; }</style>
<script>
const zone = document.querySelector('#drop-zone');
zone.addEventListener('dragover', e => {
e.preventDefault();
zone.classList.add('highlight');
});
zone.addEventListener('dragleave', e => {
if (!zone.contains(e.relatedTarget)) zone.classList.remove('highlight');
});
zone.addEventListener('drop', e => {
e.preventDefault();
zone.classList.remove('highlight');
if (!e.dataTransfer.files.length) return;
input.files = e.dataTransfer.files;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
</script>Use multiple for multiple dropped files. The accept attribute guides the picker but does not validate dropped files; check type and size on the server too. See file size validation.
Preview an image
This independent example previews the first selected image. Revoke the old object URL when the selection changes or the page is left.
<label for="image">Choose an image</label>
<input id="image" type="file" accept="image/*">
<img id="preview" alt="Selected image preview" hidden>
<script>
const imageInput = document.querySelector('#image');
const preview = document.querySelector('#preview');
let previewUrl = null;
imageInput.addEventListener('change', () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
previewUrl = null;
preview.hidden = true;
preview.removeAttribute('src');
const file = imageInput.files[0];
if (file && file.type.startsWith('image/')) {
previewUrl = URL.createObjectURL(file);
preview.src = previewUrl;
preview.hidden = false;
}
});
window.addEventListener('pagehide', () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
});
</script>Tailwind file button classes
Tailwind's file: variant targets the native button:
<input type="file" name="attachment"
class="file:mr-4 file:rounded file:border-0 file:bg-indigo-50 file:px-4 file:py-2 hover:file:bg-indigo-100">A complete upload form
Replace the access key with your own splitforms key. The POST and multipart encoding send the file contents along with the other fields.
<form action="https://splitforms.com/api/submit" method="POST" enctype="multipart/form-data">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<label for="email">Email</label>
<input id="email" type="email" name="email" required>
<label for="attachment">Attachment</label>
<input id="attachment" type="file" name="attachment" required>
<button type="submit">Send</button>
</form>File storage requires a paid plan with Storage connected; on Free, text fields are saved but files are dropped. The limit is five files of 5 MB each per submission. For setup and server-side checks, see how to add file uploads to a contact form.
FAQ
Can I change the No file chosen text?
The native text is controlled by the browser and cannot be changed with CSS. Visually hide the input, use a label as the button, and show your own status text updated from input.files.
Why does display: none break keyboard access?
An input with display: none is removed from keyboard navigation and the accessibility tree. Visually hide it instead, keep it focusable, and show a focus indicator on its label.
How do I reset a file input?
Set input.value to an empty string or call form.reset(). Also clear any custom filename text and revoke any preview object URL; resetting does not fire a change event.
Can I set a file input value?
You can clear value with an empty string, but cannot set it to a local file path. Users must choose files or drop them; a FileList from a drop can be assigned to input.files.