Стилизация placeholder при помощи CSS

Атрибут placeholder подсказывает, что ждут от поля ввода, пока оно пустое. Когда-то его стилизация требовала четырёх вендорных префиксов, а сегодня хватает одного селектора ::placeholder. Разберём, как оформлять подсказку правильно, какой приём с ней делает форму удобнее и почему ею нельзя заменять подпись поля.

Самое важное: плейсхолдер — не подпись

Подсказка исчезает при первом введённом символе. Человек, которого отвлекли на середине формы, уже не узнает, что он заполняет; скринридер в части браузеров её не зачитывает вовсе; автозаполнение браузера срабатывает хуже. Поэтому у поля всегда должна быть настоящая подпись <label>, а плейсхолдер берёт на себя только формат или пример.

ТАК НЕ НАДО: ПОДПИСЬ ЖИВЁТ ТОЛЬКО В ПЛЕЙСХОЛДЕРЕТелефон в формате +380…поле пустое — подсказку видно+380 67 502 45 84начали вводить — подсказка исчезлаи что это за поле, уже не узнатьТАК НАДО: ПОДПИСЬ ОТДЕЛЬНО, ПЛЕЙСХОЛДЕР ПОКАЗЫВАЕТ ФОРМАТТелефон+380 __ ___ __ __подпись на месте всегдаТелефон+380 67 502 45 84подпись читает и человек, и скринридер
Подпись только в плейсхолдере и правильный вариант с label
<!-- так не надо -->
<input type="tel" name="phone" placeholder="Телефон в формате +380…">

<!-- так надо -->
<label for="phone">Телефон</label>
<input type="tel" id="phone" name="phone"
       placeholder="+380 __ ___ __ __" autocomplete="tel">

Как изменить цвет и шрифт подсказки

Сегодня достаточно одного правила — ::placeholder поддерживают все актуальные браузеры:

input::placeholder,
textarea::placeholder {
    color: #8A8A8A;
    font-style: italic;
    opacity: 1; /* Firefox по умолчанию приглушает подсказку */
}

Строка с opacity не лишняя: Firefox исторически делает плейсхолдер полупрозрачным, и без неё заданный цвет выглядит бледнее, чем задумано.

Набор свойств, который реально применяется к подсказке, невелик: цвет, фон, всё семейство font-*, межбуквенный и межсловный интервал, text-decoration, text-transform, line-height, opacity и text-overflow. Отступы, рамки и позиционирование к ней не применяются — это оформление самого поля.

Старые префиксы и зачем о них помнить

В статьях пятилетней давности тот же эффект записывали так:

::-webkit-input-placeholder { color: #8A8A8A; }
::-moz-placeholder          { color: #8A8A8A; }
:-moz-placeholder           { color: #8A8A8A; }
:-ms-input-placeholder      { color: #8A8A8A; }

Причина была в том, что стандарта не существовало и каждый движок реализовал стилизацию по-своему: в IE и Firefox до 19-й версии плейсхолдер считался псевдоклассом, в WebKit и Blink — псевдоэлементом. Сегодня эти строки нужны только проектам с поддержкой очень старых браузеров. Важная деталь: селекторы нельзя объединять через запятую — браузер, не знающий хотя бы один из них, отбросит правило целиком.

Длинная подсказка в узком поле

На телефоне поля сужаются, и длинный текст обрезается на полуслове. Многоточие в конце выглядит аккуратнее:

input::placeholder {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}

Приём рабочий, но лечит следствие. Если подсказка не помещается — она слишком длинная: формат «+380 __ ___ __ __» читается лучше, чем предложение на всю ширину.

Прятать подсказку при фокусе

Браузеры ведут себя по-разному: одни убирают плейсхолдер при постановке курсора, другие держат до первого символа. Привести к единому поведению можно одной строкой:

input:focus::placeholder,
textarea:focus::placeholder {
    color: transparent;
}

Красивее — с плавным уходом в сторону:

input::placeholder {
    opacity: 1;
    transition: opacity .2s ease, transform .2s ease;
}

input:focus::placeholder {
    opacity: 0;
    transform: translateX(6px);
}

Состояния поля и :placeholder-shown

Отдельный псевдокласс :placeholder-shown срабатывает, пока поле пустое и подсказка видна. На нём строятся приёмы, которые раньше делали скриптом.

ТРИ СОСТОЯНИЯ ПОЛЯ И СЕЛЕКТОРЫ ДЛЯ НИХВаше имяпусто, подсказка видна::placeholder:placeholder-shownВаше имякурсор в поле:focus::placeholderздесь подсказку обычно гасятМаксиместь значение:not(:placeholder-shown)подпись поднимают наверхСелектор :placeholder-shown срабатывает, пока поле пустое, — на нём держится приём с плавающей подписьюи подсветка незаполненных полей без единой строки JavaScript.
Три состояния поля и соответствующие им селекторы

Плавающая подпись — подпись лежит поверх пустого поля и поднимается наверх, как только появился текст. Весь эффект на CSS:

<div class="field">
  <input type="text" id="name" placeholder=" ">
  <label for="name">Ваше имя</label>
</div>
.field { position: relative; }

.field label {
    position: absolute;
    left: 12px;
    top: 12px;
    color: #8A8A8A;
    pointer-events: none;
    transition: transform .15s ease, font-size .15s ease;
}

/* поле заполнено или в фокусе — подпись уезжает наверх */
.field input:not(:placeholder-shown) + label,
.field input:focus + label {
    transform: translateY(-22px);
    font-size: 12px;
}

Пробел в placeholder=" " обязателен: без атрибута псевдокласс :placeholder-shown не сработает вовсе.

Тот же селектор подсвечивает незаполненные обязательные поля, причём только после того, как посетитель их тронул:

input:required:not(:placeholder-shown):invalid {
    border-color: #C0392B;
}

Доступность и тёмная тема

  • Контраст. Светло-серая подсказка на белом фоне — классическая ошибка. WCAG требует не меньше 4,5:1 для обычного текста; подсказку делают приглушённой, но читаемой.
  • Не прячьте label визуально «чтобы было чисто». Если дизайн требует поля без видимой подписи, подпись всё равно нужна в разметке — хотя бы через aria-label.
  • Тёмная тема. Цвет подсказки задавайте токеном и переопределяйте в медиазапросе:
input::placeholder { color: #8A8A8A; }

@media (prefers-color-scheme: dark) {
    input::placeholder { color: #9B9B9B; }
}
  • Не кладите в плейсхолдер важное. Требования к паролю, формат даты, обязательность поля — это текст рядом с полем, а не исчезающая подсказка.

Коротко о главном

Сегодня для оформления подсказки хватает ::placeholder, а вендорные префиксы нужны лишь при поддержке очень старых браузеров. Для состояний поля есть :placeholder-shown: на нём делаются плавающая подпись и подсветка незаполненного — без JavaScript. Главное правило остаётся прежним: плейсхолдер показывает формат, подпись объясняет, что за поле, и одно другим не заменяется.

Если форма на вашем сайте работает, но посетители бросают её на середине, напишите нам — посмотрим, где именно теряются заявки.