Поле ввода с подсказкой внутри — одна из самых частых деталей формы и одновременно одна из самых неудобных. В макете подсказка нарисована приглушённым серым, а в браузере она то слишком светлая, чтобы её прочитать, то внезапно берёт цвет введённого текста. Причина простая: собственных стилей у подсказки нет — её рисует сам браузер, и каждый движок рисует по-своему.
Управляет этим один псевдоэлемент. Разберём, как он устроен, какие свойства внутри него действительно применяются, как поменять цвет и не испортить читаемость формы, и что делать с вендорными селекторами, которые до сих пор кочуют по чужим стилям.
Почему подсказка в каждом браузере выглядит по-своему
Атрибут placeholder у поля — это просто строка. Как её показать, решает браузер, и решения у браузеров разные. Chrome рисует подсказку своим серым (darkgray) и на цвет самого поля не смотрит. Firefox поступает иначе: берёт цвет введённого текста и приглушает его до 54 % непрозрачности.
.field {
color: #1a4d2e; /* цвет введённого текста */
}
/* подсказку никто не трогал:
Firefox нарисует её приглушённым зелёным,
Chrome — своим серым, цвет поля он проигнорирует */
На светлом фоне и с тёмным текстом разница почти не заметна. Стоит взять нестандартный цвет текста или посадить поле на цветную подложку — и форма в двух браузерах выглядит откровенно по-разному. Поэтому цвет подсказки почти всегда задают явно, а не полагаются на дефолт.

Псевдоэлемент ::placeholder: синтаксис и куда его вешать
Подсказка адресуется псевдоэлементом ::placeholder. Правило без селектора перед ним попадёт во все поля на странице сразу:
::placeholder {
color: #7a4ec6;
}
На практике так делать не рекомендуется: под глобальное правило попадут и поиск в шапке, и форма подписки, и поле комментария, у которых оформление обычно разное. Полезнее привязываться к конкретному полю или к классу:
.search-field::placeholder {
color: #7a4ec6;
font-style: italic;
}
Псевдоэлемент работает не только у <input>, но и у <textarea> — отдельного селектора для многострочного поля заводить не нужно.
Главный приём предсказуемости — задать color явно. Когда цвет указан, Firefox перестаёт приглушать подсказку и показывает ровно то, что написано. Если нужно, чтобы подсказка совпадала по цвету с введённым текстом, есть ключевое слово currentColor:
.field::placeholder {
color: currentColor; /* тот же цвет, что у текста в поле */
}
В чужих стилях рядом с цветом часто стоит opacity: 1. Это наследство: в старых сборках Firefox приглушение было отдельным объявлением прозрачности в стилях браузера, и без сброса цвет действительно не докрашивался. Современному Firefox достаточно явного color. Лишняя строка ничего не сломает, но и смысла в ней уже нет.
Что внутри ::placeholder действительно работает
Частая история: разработчик пишет внутри псевдоэлемента отступ или рамку, ничего не происходит, и дальше идут поиски опечатки. Опечатки там нет. Спецификация CSS Pseudo-Elements разрешает внутри ::placeholder только то подмножество свойств, которое применимо к псевдоэлементу ::first-line — то есть к оформлению строки текста, а не контейнера.
| Группа свойств | Применяется | Игнорируется |
| Цвет и прозрачность | color, opacity | — |
| Шрифт | font-family, font-size, font-weight, font-style, font-variant | — |
| Фон | background-color, background-image и остальные background-* | — |
| Набор текста | letter-spacing, word-spacing, line-height, text-transform, text-decoration, text-shadow, vertical-align | text-align (задаётся на самом поле) |
| Контейнер | — | padding, margin, border, width, height |
| Раскладка и эффекты | — | display, position, transform, content |
Отсюда практическое правило: всё, что двигает подсказку внутри поля, делается на самом поле, а не на псевдоэлементе. Отступ слева — это padding поля, и он одновременно сдвинет и введённый текст, что как раз и нужно.
/* так ничего не произойдёт */
.field::placeholder {
padding-inline-start: 12px;
}
/* так правильно */
.field {
padding-inline-start: 12px;
}
Контраст: ради чего подсказку вообще трогают
Дефолтный светло-серый почти никогда не проходит по контрасту. Требование WCAG (Web Content Accessibility Guidelines — международные рекомендации по доступности веб-контента) — коэффициент контрастности не ниже 4,5:1 для обычного текста и не ниже 3:1 для крупного. Крупным считается текст от 18,66 px жирного начертания или от 24 px обычного. Серая подсказка на белом фоне обычно даёт что-то в районе 2,5:1 — на солнце или на дешёвой матрице её не видно вовсе.
Но и обратная крайность работает против пользователя. Если подсказка по контрасту совпадает с введённым текстом, человек принимает её за уже заполненное значение: пропускает поле, отправляет форму и получает ошибку валидации. Поэтому цель — не «сделать потемнее», а попасть в узкий коридор: читаемо, но заметно отличается от реального ввода.
.field {
background: #ffffff;
color: #1b1f27; /* введённый текст, контраст ~16,5:1 */
}
.field::placeholder {
color: #5b6472; /* подсказка, контраст ~6:1 */
}
Разницу удобнее держать не только по яркости, но и по начертанию: курсив или чуть меньший размер отделяют подсказку от значения надёжнее, чем десять процентов серого. Как подбирать пару «текст — фон» по числам, разбирали в статье про выбор цвета шрифта по цвету фона.
Отдельно стоит помнить про режим высокой контрастности Windows (в CSS ему соответствует медиа-запрос forced-colors): система подменяет цвета своими, и подсказка начинает выглядеть ровно как введённый текст. Никакими стилями это не обходится — поэтому подсказка не должна быть единственным носителем смысла. Подпись поля живёт в <label>, а подсказка внутри поля — только дополнение к ней; подробный разбор аргументов — в заметке placeholder — это не label.
Светлая и тёмная тема одним правилом
Цвет подсказки — классический кандидат на дублирование в тёмной теме: то, что читается на белом, на тёмном фоне превращается в грязь. Раньше выход был один — продублировать правило в медиа-запросе:
.field::placeholder {
color: #5b6472;
}
@media (prefers-color-scheme: dark) {
.field::placeholder {
color: #9aa4b2;
}
}
Сейчас то же самое пишется одной строкой через функцию light-dark(). Она принимает два значения и сама выбирает нужное по текущей схеме — при условии, что схема объявлена свойством color-scheme:
:root {
color-scheme: light dark;
}
.field::placeholder {
color: light-dark(#5b6472, #9aa4b2);
}
Есть и второй подход, при котором тему вообще не приходится перечислять. Подсказка — это тот же текст поля, только разбавленный его фоном; функция color-mix() умеет смешивать два цвета в заданной пропорции, и результат сам поедет за темой:
.field {
--field-bg: light-dark(#ffffff, #12161c);
background: var(--field-bg);
color: light-dark(#1b1f27, #e6e9ef);
}
.field::placeholder {
/* 62 % цвета текста, остальное — фон поля */
color: color-mix(in oklab, currentColor 62%, var(--field-bg));
}
Смешивать лучше в oklab или oklch, а не в srgb: в этих пространствах середина между двумя цветами выглядит серединой и для глаза, без провалов в грязь. Почему так — разбирали в статье про OKLCH и предсказуемый цвет в CSS. Про сам переключатель тем и про prefers-color-scheme есть отдельный разбор.
Вендорные селекторы: наследство, которое ломает правило
В чужих стилях и в старых ответах на форумах цвет подсказки почти всегда задаётся набором из четырёх-пяти селекторов. Вот что это было:
| Селектор | Кому он был нужен |
| ::-webkit-input-placeholder | Chrome и Opera до перехода на стандартный псевдоэлемент, Safari до 10.1 |
| :-moz-placeholder | Firefox 4–18, ещё в виде псевдокласса с одним двоеточием |
| ::-moz-placeholder | Firefox 19–50, уже псевдоэлемент, но всё ещё с префиксом |
| :-ms-input-placeholder | Internet Explorer 10–11 и первый Edge на движке EdgeHTML |
Сегодня писать их в новом коде незачем: все живые браузеры понимают стандартный ::placeholder, а Internet Explorer снят с поддержки Microsoft в июне 2022 года. Но знать их полезно — они встречаются в легаси, и с ними связана ловушка, на которой правило перестаёт работать целиком.
Список селекторов в CSS работает по принципу «всё или ничего»: если браузер не понимает хотя бы один селектор в перечислении через запятую, он выбрасывает всё правило. А вендорный селектор чужого движка как раз и является для него непонятным.
/* правило не сработает НИ В ОДНОМ браузере:
каждый споткнётся о чужой префикс и отбросит весь блок */
::placeholder,
::-webkit-input-placeholder,
:-ms-input-placeholder {
color: #7a4ec6;
}
/* каждому селектору — своё правило */
::placeholder {
color: #7a4ec6;
}
::-webkit-input-placeholder {
color: #7a4ec6;
}
Обойти это «прощающими» списками вроде :is() не получится: псевдоэлементы внутрь них помещать нельзя. Так что вариант только один — отдельное правило на каждый селектор.
Псевдокласс :placeholder-shown: когда одного цвета мало
У ::placeholder есть парный псевдокласс — :placeholder-shown. Он вешается не на подсказку, а на само поле и совпадает ровно до тех пор, пока подсказка видна, то есть пока в поле ничего не ввели. Разница важная: псевдоэлемент красит текст подсказки, псевдокласс позволяет реагировать на факт пустого поля — и красить хоть рамку, хоть соседние элементы.
Самый практичный сценарий — показывать кнопку очистки только тогда, когда очищать есть что. Никакого JavaScript для этого не нужно:
.search__clear {
visibility: hidden;
}
/* поле НЕ показывает подсказку — значит, в нём что-то есть */
.search__input:not(:placeholder-shown) ~ .search__clear {
visibility: visible;
}
На этом же псевдоклассе строится приём с подписью, которая изображает подсказку в пустом поле и уезжает наверх, когда человек начал печатать. Важный нюанс: поле при этом должно иметь непустой placeholder — обычно туда кладут один пробел, иначе псевдокласс не сработает. И это не отменяет настоящий <label> в разметке: подпись должна оставаться подписью для скринридера, а не превращаться в декорацию. Как собрать такую форму целиком — в разборе HTML для формы логина и регистрации, а сам псевдокласс вместе с остальными состояниями полей разобран в обзоре современных псевдоклассов CSS.
Частые ошибки
- Правило ::placeholder без селектора перед ним. Под него попадают все поля сайта разом, включая поиск в шапке и форму комментария с другим оформлением.
- Подгонка цвета по макету без проверки контраста. Дизайнер видит подсказку на калиброванном мониторе в тёмной комнате, пользователь — на телефоне под солнцем.
- Слишком тёмная подсказка. Её принимают за уже введённое значение и пропускают поле.
- Перечисление вендорных селекторов через запятую. Правило не сработает нигде — каждому селектору нужен свой блок.
- Попытка задать padding, border или transform внутри псевдоэлемента. Геометрия настраивается на самом поле.
- Подсказка вместо подписи. Она исчезает при вводе, обрезается по ширине поля и по-разному читается вспомогательными технологиями.
Итог
Для цвета подсказки достаточно одного псевдоэлемента и одного явного объявления color — всё остальное в старых рецептах либо устарело вместе с браузерами, либо мешает. Внутри ::placeholder работают только «текстовые» свойства: цвет, прозрачность, шрифт, фон и набор; геометрия настраивается на поле.
Реальная работа тут не в синтаксисе, а в подборе цвета. Подсказка должна уверенно проходить по контрасту и при этом заметно отличаться от введённого значения — у этого коридора узкие стенки с обеих сторон. Для тёмной темы дублировать правило больше не нужно: light-dark() выбирает значение по схеме, а color-mix() позволяет вывести цвет подсказки из цвета текста и фона поля, чтобы он ехал за темой сам.
Комментарии (0)