The button on a file input is the gray Choose File control the browser draws for <input type="file">, and you style it with the ::file-selector-button pseudo-element. It is the standard, unprefixed selector that replaced the old ::-webkit-file-upload-button, and it is Baseline now, so it is safe to ship without a vendor prefix:
input[type="file"]::file-selector-button {
padding: 0.5rem 1rem;
border: 0;
border-radius: 6px;
background: #2563eb;
color: #fff;
font: inherit;
cursor: pointer;
}That is the whole job for the common case. The rest of this page is the detail: the font gotcha that trips everyone up, the hover and focus states, the one thing you genuinely cannot style, and the label pattern you reach for when ::file-selector-button alone is not enough.
Browser support
::file-selector-button has been available across all the major engines since April 2021 (it landed in Chromium 89, and Firefox had it before that under the standard name). It is Baseline "Widely available," so you no longer need the prefixed selector at all unless you are supporting a very old Safari. If you do still target ancient browsers, group both selectors but keep them in separate rules. A browser that does not understand one of them will throw out the whole selector list, so this is wrong:
/* Wrong: one unknown selector kills the entire rule. */
input::-webkit-file-upload-button,
input::file-selector-button {
background: #2563eb;
}Split them so each engine still gets the half it understands:
input::-webkit-file-upload-button { background: #2563eb; }
input::file-selector-button { background: #2563eb; }In 2026 you can almost certainly drop the first rule entirely.
The font does not inherit (the gotcha)
The button is a real, separate element with its own user-agent styles, so it does not automatically inherit the font, color, or sizing of the surrounding form. Style a clean button, reload, and the label text is still in the browser's default system font while everything around it is your font stack. The fix is to opt back into inheritance explicitly:
input[type="file"]::file-selector-button {
font: inherit;
color: inherit;
}font: inherit is the line people forget. Set it and the button picks up your typeface, weight, and size; leave it out and the button looks bolted on.
Hover and focus states
The pseudo-element takes the usual interaction states, so you stack them on the same selector. Pair :hover with :focus-visible so keyboard users get a clear ring without mouse users seeing one on click:
input[type="file"]::file-selector-button {
background: #2563eb;
transition: background 0.15s ease;
}
input[type="file"]::file-selector-button:hover {
background: #1d4ed8;
}
input[type="file"]:focus-visible::file-selector-button {
outline: 2px solid #1d4ed8;
outline-offset: 2px;
}Note where the :focus-visible sits: focus lands on the <input> itself, not the pseudo-element, so the focus state reads input:focus-visible::file-selector-button. See keep focus rings for keyboard users with :focus-visible for why :focus-visible beats a blanket :focus.
What you cannot style: the "No file chosen" text
Here is the real limitation. ::file-selector-button reaches the button and nothing else. The "No file chosen" label the browser renders next to the button (and the filename it swaps in after a selection) is not exposed to CSS at all. There is no pseudo-element for it, no property that targets it. You can recolor it indirectly by setting color on the input, but you cannot reposition it, restyle it, or replace its text.
If all you want is to get rid of that text, set the input's text color to transparent:
/* Hides "No file chosen", but also hides the selected filename. */
input[type="file"] {
color: transparent;
}The catch is that it also hides the filename after the user picks something, which is usually worse than the default. When you need real control over the whole control, including the filename display, you stop fighting the native widget and build your own.
The pattern for full control: hide the input, style a label
The widely-used approach is to visually hide the actual <input type="file"> and style a <label> that points at it. Clicking the label opens the file dialog because that is what a <label> does, so you keep the native behavior and accessibility while getting a fully styleable trigger.
<label class="te-file-upload">
<span class="te-file-label">Upload a file</span>
<input type="file" />
</label>.te-file-upload {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
background: #2563eb;
color: #fff;
font: inherit;
cursor: pointer;
}
/* Accessibly hide the native input, keep it focusable and clickable. */
.te-file-upload input[type="file"] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}Two rules to keep this honest. First, hide the input with the clip technique above, not display: none or visibility: hidden. Those remove it from the accessibility tree and can stop it being focusable, which breaks keyboard use. Second, this stays a genuine <input type="file">. Do not fake the upload with a <div> and a click handler; you lose drag-and-drop, the native dialog, form submission, and assistive-technology support. The label-plus-hidden-input pattern keeps every one of those.
Showing the chosen filename in your custom control is the one piece this needs JavaScript for: listen for the input's change event, read input.files[0].name, and write it into the label.
const input = document.querySelector(".te-file-upload input");
const labelText = document.querySelector(".te-file-label");
input.addEventListener("change", () => {
labelText.textContent = input.files.length ? input.files[0].name : "Upload a file";
});Which approach to reach for
If you only need a branded button and the default filename text is acceptable, use ::file-selector-button directly: it is one rule, no JavaScript, and fully native. If you need to control the filename display, lay the button and text out yourself, or match a design system precisely, use the hidden-input label pattern. There is no third option that restyles the native "No file chosen" text in place, so do not waste time looking for one.
While you are theming form controls, accent-color tints native checkboxes, radios, and range inputs with a single line, and the :has() selector lets you style a form group based on what the input contains.
FAQ
See also
- CSS accent-color: brand-style checkboxes, radios, and form controls: tint native form controls with one line, the companion to a styled file button.
- The CSS :has() parent selector: style a form group based on the state or contents of an input inside it.
- CSS :focus-visible: keep focus rings for keyboard users, hide them for mouse: the right way to add the focus state shown above.
Sources
Authoritative references this article was fact-checked against.
- ::file-selector-button (MDN Web Docs)developer.mozilla.org
- ::file-selector-button browser support (Can I use)caniuse.com
- input type=file (MDN Web Docs)developer.mozilla.org





