Кнопка — элемент, который, кажется, разработчики научились оформлять ещё лет десять назад. Поправил отступы, цвет фона, ховер-состояние, и готово. На практике этого мало: на тач-устройстве кнопка дёргается при двойном тапе, на iOS выделяется текст, после клика в Chrome остаётся неаккуратный outline, у системного <input type="file"> вообще нельзя поменять стиль кнопки... — и каждая такая мелочь портит впечатление.

Соберём пять небольших CSS-настроек, которые закрывают эти раздражители и делают кнопку приятнее в реальной жизни — на телефоне, на ноуте с трекпадом и для пользователей, которые ходят по сайту с клавиатуры.

Тап без задержки и без зум-жеста

На тач-устройствах браузер по умолчанию ждёт около 300 мс после первого касания, проверяя, не последует ли второй тап для зума. Этот резерв ощущается как «залипание» кнопки: ткнули — и ничего не происходит, потом происходит. Свойство touch-action: manipulation разрешает браузеру обрабатывать только базовые жесты (скролл и pinch-зум) и сразу отдавать клик, без ожидания второго тапа.

.button {
  touch-action: manipulation;
}

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

Не выделяем текст по случайности

Когда пользователь быстро двойным тапом нажимает кнопку (например, нетерпеливо отправляет форму), мобильный Safari может посчитать второй тап жестом выделения и подсветить текст внутри. У обычного <button> такого поведения нет, но у кнопкоподобной ссылки (<a class="button">) или у кастомных компонентов на <div> — запросто.

.button {
  user-select: none;
}

Один нюанс: Safari исторически требовал префикс -webkit-user-select, и хотя современные версии понимают и стандартное свойство, для подстраховки удобно писать обе формы:

.button {
  -webkit-user-select: none;
  user-select: none;
}

Применять только к интерактивным элементам, не к контентным абзацам — иначе пользователь не сможет скопировать текст.

Стилизуем кнопку у <input type="file">

Системная кнопка «Выбрать файл» долгое время была одним из самых раздражающих элементов в вебе: внешний вид прибит к движку браузера, а заменить её обычно предлагали через костыль с label и спрятанным инпутом. Теперь есть прямой способ — псевдоэлемент ::file-selector-button, который позволяет применить любые свои стили к кнопке внутри файлового инпута.

.upload-input::file-selector-button {
  padding: 8px 16px;
  border: none;
  border-radius: 8px;
  background: #4f46e5;
  color: #fff;
  font-weight: 600;
  cursor: pointer;
  margin-right: 12px;
}

Удобный паттерн — написать одно правило сразу для обычной кнопки и для системной, чтобы не дублировать стили:

.button,
.upload-input::file-selector-button {
  /* единое оформление кнопки */
}
Поддержка браузерами
chrome
Chrome
89
firefox
Firefox
82
edge
Edge
89
safari
Safari
15.4
opera
Opera
75

Аккуратный фокус для клавиатурных пользователей

Самый частый запрос на ревью — «убери, пожалуйста, эту синюю обводку, которая появляется после клика». И самый частый ответ из стиля 2010-х — outline: none. Это плохо: outline — единственный визуальный сигнал для тех, кто навигирует с клавиатуры, и его потеря ломает доступность сайта.

Решение — псевдокласс :focus-visible. Он применяет стиль только тогда, когда браузер считает, что фокус «заслуживает» визуальной подсказки: при навигации с клавиатуры (Tab, стрелки), но не при обычном клике мышью или тапе. То есть outline появится у клавиатурного пользователя и не будет мозолить глаза тому, кто пришёл с трекпада.

.button:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}

Свойство outline-offset добавляет зазор между обводкой и границей элемента — полезно, когда у кнопки скруглённые углы или своя рамка, и обводка вплотную смотрится грязно.

Чтобы стало ещё яснее: разница между :focus и :focus-visible — не во внешнем виде, а в условии срабатывания. :focus ловит факт фокуса как такового, независимо от способа. :focus-visible опирается на эвристику движка «показывать ли подсказку этому конкретному пользователю». На практике достаточно использовать только :focus-visible — обнулять :focus отдельно не нужно.

Поддержка браузерами
chrome
Chrome
86
firefox
Firefox
85
edge
Edge
86
safari
Safari
15.4
opera
Opera
72

Логический размер вместо width

Привычное width: fit-content — нормально, пока сайт работает только в горизонтальном письме слева направо. Если завтра потребуется поддержка арабского, иврита или вертикальной восточноазиатской типографики, привязка к физической оси width создаст проблемы: при writing-mode: vertical-rl ширина становится высотой, и стили начинают вести себя неожиданно.

CSS-логические свойства решают это сразу:

.button {
  inline-size: fit-content;
}

inline-size — это размер вдоль оси текста, без привязки к экранной ширине. Для обычной горизонтальной верстки результат идентичен width, для вертикальной или RTL — правильно адаптируется автоматически. Подробный разбор самих ключевых слов fit-content, min-content и max-content — в отдельной статье про размеры по содержимому.

Всё вместе

Соберём пять приёмов в одну компактную декларацию, которую можно применять как базовый стиль для любых кнопок и кнопкоподобных ссылок на сайте:

.button,
.upload-input::file-selector-button {
  inline-size: fit-content;
  padding: 8px 16px;
  border: none;
  border-radius: 8px;
  background: #4f46e5;
  color: #fff;
  font-weight: 600;
  cursor: pointer;

  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}

.button:focus-visible,
.upload-input::file-selector-button:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}

Полный интерактивный пример с кнопкой и файловым инпутом:

Итог

Базовое оформление кнопки — это не только padding и фон. Пять небольших настроек — touch-action: manipulation, user-select: none, ::file-selector-button, :focus-visible с outline-offset и inline-size: fit-content — убирают мелкие, но регулярные раздражители и делают кнопку дружелюбнее на всех платформах и для всех способов навигации. Все они не требуют JS, не плохо ложатся на существующие стили и не ломают совместимость со старыми браузерами — стоят того, чтобы добавить их в базовый стиль кнопок один раз и больше не возвращаться.