What is placeholder text?
Placeholder text is the faint hint you see inside an empty form field, such as "Enter your email" or "Search the site". As soon as you start typing, it disappears to make room for what you enter.
You add it with the placeholder attribute, which arrived with HTML5:
<input type="email" placeholder="e.g. jane@example.com">
Browsers show placeholder text in their own default grey, which doesn't always suit your design. In this post we'll look at how to style it with CSS, which properties you can change, and how to keep it readable for everyone.
Here's a field with the browser's default placeholder styling. Click in it and start typing to watch the hint disappear:
Styling it with ::placeholder
To style placeholder text, use the ::placeholder pseudo-element. A pseudo-element lets you style a specific part of an element, in this case just the hint text, rather than the whole field.
input::placeholder {
color: #6b7280;
opacity: 1;
}
Read that as "the placeholder text inside every input". Here it is in action:
Why opacity: 1?
Firefox used to lower the opacity of placeholder text by default, on top of whatever colour you set. That made your chosen colour look paler in Firefox than in other browsers. Adding opacity: 1 ensures the colour you pick is the colour everyone sees.
Text areas too
::placeholder works on <textarea> elements as well, the larger boxes used for messages and comments. To style both at once, list them together:
input::placeholder,
textarea::placeholder {
color: #6b7280;
font-style: italic;
opacity: 1;
}
Tip: to style one field only, give it a class and target that instead, such as .search-box::placeholder.
What you can change
You can do more than change the colour. This example makes the hint green, italic and uppercase, with a little extra letter spacing:
input {
text-align: center;
}
input::placeholder {
color: #15803d;
font-style: italic;
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 1;
}
Notice that text-align is set on the input itself, not on ::placeholder. That centres both the hint and whatever the visitor types, and it works the same in every browser.
Supported properties
::placeholder only accepts properties that affect text. Anything to do with size or layout, such as width, margin or padding, is ignored.
| Property | Example |
|---|---|
color | color: #6b7280; |
opacity | opacity: 1; |
font-style | font-style: italic; |
font-weight | font-weight: 600; |
font-size and font-family | font-size: 0.9em; |
text-transform | text-transform: uppercase; |
letter-spacing and word-spacing | letter-spacing: 0.05em; |
text-decoration | text-decoration: underline; |
text-shadow | text-shadow: 1px 1px 0 #fff; |
Tip: be careful with font-size. If the placeholder is much smaller or larger than the typed text, the field can look like it jumps when someone starts typing.
Keeping it readable
It's tempting to make placeholder text very pale so it looks subtle. The original version of this post used #ccc, a light grey. The trouble is that pale text is hard to read, especially on a phone in bright sunlight or for anyone with low vision.
Contrast is the difference in brightness between text and its background. The WCAG accessibility guidelines recommend a ratio of at least 4.5 to 1 for normal text. Compare these two:
The second field is still clearly lighter than typed text would be, so it reads as a hint, but it's far easier to see.
Correction: the original post's second example used the bright green #66FF99. On a white background its contrast is only about 1.3 to 1, which makes it nearly invisible. The darker #15803d used above is readable while staying green.
The :placeholder-shown pseudo-class
::placeholder (two colons) styles the hint text itself. There's also :placeholder-shown (one colon), a pseudo-class, which styles the whole field while its placeholder is visible. In other words, it matches an empty field.
input:placeholder-shown {
border-style: dashed;
background: #f8fafc;
}
Type something in the field below and watch the dashed border turn solid:
Floating labels
A popular use of :placeholder-shown is the floating label. The label sits inside the empty field like a placeholder, then shrinks and floats to the top once the field is in use, so it never disappears.
<div class="field">
<input type="email" id="email" placeholder=" ">
<label for="email">Email address</label>
</div>
The trick is placeholder=" ": a placeholder made of a single space. It's invisible, but it means :placeholder-shown can tell us whether the field is empty.
.field {
position: relative;
}
.field input {
padding: 22px 12px 6px;
}
.field label {
position: absolute;
left: 13px;
top: 14px;
color: #6b7280;
pointer-events: none;
transition: top 0.15s ease, font-size 0.15s ease;
}
.field input:focus + label,
.field input:not(:placeholder-shown) + label {
top: 5px;
font-size: 12px;
color: #1d4ed8;
}
pointer-events: none lets clicks pass through the label to the input underneath. The final rule floats the label up whenever the field has focus or isn't empty. The + means "the label straight after the input", which is why the label comes second in the HTML.
Placeholders are not labels
A placeholder can look like a neat replacement for a label, but it causes real problems when used that way:
- It disappears. Once someone starts typing, they can no longer see what the field was asking for.
- It's easy to miss. Pale hint text can look like the field is already filled in, so people skip it.
- It's unreliable for screen readers. Screen readers (software that reads pages aloud) don't all announce placeholders consistently.
Key rule: always give every field a proper <label>. Use the placeholder only for an extra hint, such as an example of the format you expect.
<label for="phone">Phone number</label>
<input type="tel" id="phone" placeholder="e.g. 0412 345 678">
The floating label pattern above is a good compromise: it looks like a placeholder but uses a real label that never disappears.
What happened to the vendor prefixes?
When this post was first written, every browser had its own name for the placeholder selector, so you had to write the same rule four times:
/* The old way: no longer needed */
input:-ms-input-placeholder { color: #ccc; }
input::-webkit-input-placeholder { color: #ccc; }
input:-moz-placeholder { color: #ccc; }
input::-moz-placeholder { color: #ccc; }
These are called vendor prefixes: temporary, browser-specific names used while a feature is still being agreed. Once the standard version was settled, browsers switched to it.
Today the single standard ::placeholder works in every modern browser: Chrome, Edge, Firefox, Safari and their mobile versions. You can safely delete the prefixed versions from old stylesheets.
Good to know: the original post also suggested using jQuery to add placeholders in older browsers. That's no longer necessary, because every browser in use today supports the placeholder attribute natively.
Wrapping up
Styling placeholder text now takes a single selector: ::placeholder. Keep the colour dark enough to read, add opacity: 1 for consistency, and set alignment on the input itself. Most importantly, use placeholders as helpful hints alongside proper labels, never instead of them.