This is episode #21 in a series examining modern CSS solutions to problems I've been solving over the last 14+ years of being a frontend developer.
We're going to create custom form input and textarea styles that have a near-identical appearance across the top browsers. We'll specifically style the input types of
file, and style the
Read on to learn how to:
- reset input styles
hslfor theming of input states
- ensure all states meet contrast requirements
- retain a perceivable
:focusstate for Windows High Contrast mode
Now available: my egghead video course Accessible Cross-Browser CSS Form Styling. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.
This is the fourth installment in the Modern CSS form field mini-series. Check out episodes 18-20 to learn how to style other common form field types including radio buttons, checkboxes, and selects.
Common Issues with Native Input Styles#
There is a bit more parity between text input styles than we saw with radios, checkboxes, and selects, but inconsistencies nonetheless.
Here's a screenshot of the unstyled inputs we're going to address today across (from left) Chrome, Safari, and Firefox.
We will be looking to unify the initial appearance across browsers and common field types.
datefield is unique in that Chrome and Firefox provide formatting and a popup calendar to select from, while Safari offers no comparable functionality. We cannot create this in CSS either, so our goal here is to get as far as we can with creating a similar initial appearance. Check out the caniuse for date/time inputs.
We're covering a lot of field types, so check the CodePen for the full list. But here is the essential HTML for a text input and a textarea.
Hey there! Register for my CSS workshop in October with Smashing Conferences: Level-Up With Modern CSS
<label for="text-input">Text Input</label>
<input class="input" id="text-input" type="text" />
<textarea class="input" id="textarea"></textarea>
To allow simplifying our styles and preparing to work with the cascade, we've only added one CSS class -
input - which is placed directly on the text input and textarea.
The label is not part of our styling exercise, but its included as a general requirement, notably with the
for attribute having the value of the
id on the input.
Create CSS Variables for Theming#
For the tutorial, we're going to try a bit different technique for theming by using
We'll set a grey for the border, and then break down a blue color to be used in our
:focus state into its hsl values, including:
h for "hue",
s for "saturation", and
l for "lightness".
Each of the tutorials for our form fields has incorporated a bit different method for theming, which can all be extracted and used beyond just forms!
As per all user interface elements, the input border needs to have at least 3:1 contrast against it's surroundings.
:focus state needs to have 3:1 contrast against the unfocused state if it involves something like changing the border color or, according to the WCAG 2.2 draft, a thickness greater than or equal to
The draft for WCAG 2.2 makes some slight adjustments to
:focus requirements, and I encourage you to review them.
As is included in all my tutorials as a modern best practice, we add the following reset first:
As seen in the initial state of the fields across browsers, some standout differences were in border type, background color, and font properties.
font-family do not inherit from the document like typography elements do, so we need to explicitly set them as part of our reset.
Also of note, an input's
font-size should compute to at least 16px to avoid zooming being triggered upon interaction in mobile Safari. We can typically assume
16px, but we'll explicitly set it as a fallback and then use the newer CSS function
max to set
16px as the minimum in case it's smaller than
1em (h/t to Dan Burzo for this idea).
font-size: max(16px, 1em);
padding: 0.25em 0.5em;
border: 2px solid var(--input-border);
We set our
border to use the theme variable, and also created a slightly rounded corner.
After this update, we're already looking pretty good:
It may be difficult to notice in that screenshot, but another difference is the height of each field. Here's a comparison of the text input to the file input to better see this difference:
Let's address this with the following which we are applying to our
.input class as long as it is not placed on a
line-height: 1 since when it's not a
textarea it's impossible for an input to be multiline. We also set our height in
rem due to considerations of specifically the file input type. If you know you will not be using a file input type, you could use
em here instead for flexibility in creating various sized inputs.
But, critically, we've lost differentiation between editable and
disabled input types. We also want to define
readonly with more of a hint that it's also un-editable, but still interactive. And we have a bit more work to do to smooth over the file input type. And, we want to create our themed
Would you like CSS tips in your inbox? Join my newsletter for article updates, CSS tips, and front-end resources!
File Input CSS#
Let's take another look at just our file input across Chrome, Safari, and Firefox:
We cannot style the button created by the browser, or change the prompt text, but the reset we provided so far did do a bit of work to allow our custom font to be used.
We'll make one more adjustment to downsize the font just a bit as when viewed with other field types the inherited button seems quite large, and
font-size is our only remaining option to address it. From doing that, we need to adjust the top padding since we set our padding up to be based on
If you were expecting a fancier solution, there are plenty of folx who have covered those. My goal here was to provide you a baseline that you can then build from.
readonly CSS Style#
While not in use often, the
readonly attribute prevents additional user input, although the value can be selected, and it is still discoverable by assistive tech.
Let's add some styles to enable more of a hint that this field is essentially a placeholder for a previously entered value.
To do this, we'll target any
.input that also has the
[readonly] attriute. Attribute selectors are a very handy method with wide application, and definitely worth adding to (or updating your awareness of) in your CSS toolbox.
In addition to swapping for a
dotted border, we've also assigned it the
not-allowed cursor and enforced a medium-grey text color.
As seen in the following gif, the user cannot interact with the field except to highlight/copy the value.
Disabled Input and Textarea Style#
readonly, we'll use an attribute selector to update the style for disabled fields. We are attaching it to the
.input class so it applies on textareas as well as our other input types.
We'll make use of our CSS variable to update the border color to a muted grey, and the field background to a very light grey. We'll also again apply the
not-allowed cursor as just an extra hint that the field is not interactive.
And here is the result for both a text input and a textarea:
disabledfields are not necessarily discoverable by assistive tech since they are not focusable. They also are not required to meet even the typical 3:1 contrast threshold for user interface elements, but we've kept with user expectations by setting them to shades of grey.
textarea is really close, but there's one property I want to mention since it's unique to the inherent behavior of textareas.
That property is
resize, which allows you to specify which direction the
textarea can be resized, or if it even can at all.
While you definitely should allow the
textarea to retain the resize function under general circumstances, you can limit it to just vertical resizing to prevent layout breakage from a user dragging it really wide, for example.
We'll apply this property by scoping our
.input class to when it's applied on a
Try it out in the final CodePen demo!
:focus State Styles#
Ok, we've completed the initial styles for our inputs and the textarea, but we need to handle for a very important state:
We're going to go for a combo effect that changes the border color to a value that meets 3:1 contrast against the unfocused state, but also adds a
box-shadow for a bit of extra highlighting.
And here's why we defined our theme color of the focus state in hsl: it means we can create a variant of the border color by updating just the lightness value.
First, we define the border color by constructing the full hsl value from the individual CSS variable values:
border-color: hsl(var(--input-focus-h), var(--input-focus-s), var(--input-focus-l));
Then, we add in the
box-shadow which will only use blur to create essentially a double-border effect.
calc() is acceptable to use inside
hsla, so we use it to lighten the original value by 40%, and also allow just a bit of alpha transparency:
/* ...existing styles */
box-shadow: 0 0 0 3px hsla(var(--input-focus-h), var(--input-focus-s), calc(var(--input-focus-l) +
Note that we've now added a new context for our contrast, which is the
:focus border vs. the
box-shadow, so ensure the computed difference for your chosen colors is at least 3:1 if using this method.
Optionally, jump back up to the
.input rule and add a
transition to animate the
/* ...existing styles */
transition: 180ms box-shadow ease-in-out;
Finally, we don't want to forget Windows High Contrast mode which will not see the
box-shadow or be able to detect the border color change. So, we include a transparent outline for those users:
outline: 3px solid transparent;
We also use this technique in the episode covering button styles.
Here's a gif demo of focusing into the text input:
And here's the appearance for the
readonly field, since it has a different
In the CodePen HTML, there is a comment with an example of using an inline style to define an updated visual such as for an error state. Again, keep in mind that we are lightening the provided
--input-focus-l value by 40%, and the focused border color must be at least 3:1 contrast against the unfocused color, so consider that when you alter the CSS variable values.
Input Mode and Autocomplete#
There are two aditional attributes that can help improve the user experience, particularly on mobile, in addition to using the correct input type (ex: email).
The first is defining the
inputmode, which provides an altered keyboard or keypad that better matches the expected data. Read up on available
inputmode values on MDN >
autocomplete which has far more options than
off. For example, I always appreciate that on iPhone when Google sends me a confirmation code by text the keyboard "just knows" what that value is. Turns out, that's thanks to
Check out the full list of
autocomplete values that allow you to hint at the value expected and really boost the user experience of your forms for users that make use of auto-filling values.
First, here's a final look at our solution across (from left) Chrome, Safari, and Firefox. The file input still sticks out a bit when viewed side by side, but in the flow of a form on an individual browser it's definitely acceptable.
Here is the solution with all the field types we covered represented.