У свойства overflow долго было четыре рабочих значения: visible, hidden, scroll и auto. Все, кому нужно было просто отрезать вылезающее содержимое, по привычке писали overflow: hidden — и получали побочный эффект, о котором редко задумывались. Значение clip закрывает ровно эту дыру: оно обрезает контент и ничего больше не делает. Разберём, чем оно отличается от hidden, зачем понадобилось отдельное значение и почему именно им лечат сломанный position: sticky.

Зачем придумали отдельное значение

Проблема hidden в том, что он делает сразу две вещи. Во-первых, прячет всё, что выходит за границы блока. Во-вторых — и это неочевидно — превращает элемент в контейнер прокрутки. Скроллбара не видно, но технически блок становится прокручиваемым: к нему применимы scrollTop, scrollIntoView, фокус на скрытом элементе уводит вьюпорт блока, а сам блок создаёт новый контекст форматирования.

В большинстве случаев нужна была только первая половина — обрезать. Вторая половина приходила бесплатным и часто вредным довеском. Поэтому в модуле CSS Overflow появилось значение clip: оно обрезает по так называемому краю обрезки (overflow clip edge), но не создаёт контейнер прокрутки, не показывает скроллбар, запрещает любую прокрутку — включая программную — и не открывает новый контекст форматирования.

/* было: обрезали — и заодно случайно сделали блок прокручиваемым */
.card {
  overflow: hidden;
}

/* стало: чистое намерение «просто отрезать лишнее» */
.card {
  overflow: clip;
}

Если контекст форматирования всё-таки нужен (например, чтобы блок охватывал плавающих потомков), его добавляют явно через display: flow-root — а не как побочный эффект обрезки.

clip против hidden: построчное сравнение

Визуально результат одинаковый: вылезающее содержимое не видно, скроллбара нет, до спрятанного не добраться мышью. Различия — в поведении, которое не видно глазом.

Поведение overflow: hidden overflow: clip
Прячет вылезающее содержимое да да
Создаёт контейнер прокрутки да нет
Программная прокрутка (scrollTop, scrollIntoView) работает запрещена
Новый контекст форматирования создаёт не создаёт
Влияет на position: sticky у потомков да (часто ломает) нет
Поддерживает overflow-clip-margin нет да

Главный практический вывод из таблицы — строчка про контейнер прокрутки. Именно из неё растут и поломка залипания, и трюк с поосевой обрезкой, к которым мы сейчас перейдём.

overflow-clip-margin: рисуем чуть дальше края

По умолчанию clip режет ровно по padding-боксу. Но иногда нужно, чтобы что-то выходило за край на несколько пикселей — тень, обводка при фокусе, декоративный хвостик — и только потом обрезалось. Для этого есть парное свойство overflow-clip-margin: оно отодвигает край обрезки наружу на заданную длину.

.badge-host {
  overflow: clip;
  /* содержимое видно ещё на 1.5rem за краем, дальше — отрез */
  overflow-clip-margin: 1.5rem;
}

Свойство работает только вместе с overflow: clip. На hidden, auto или scroll оно просто игнорируется — ещё одна причина, по которой clip не сводится к косметическому синониму hidden. Отрицательные значения не допускаются: внутрь padding-бокса край обрезки не сдвинуть.

Почему clip чинит position: sticky

Это самый частый повод узнать про clip. Сценарий знаком многим: вешаешь на заголовок position: sticky с top: 0, он должен залипать при прокрутке страницы — а он упрямо уезжает вверх вместе с контентом. Чаще всего виноват какой-то родитель выше по дереву, которому ради обрезки декора или гашения горизонтального скролла прописали overflow: hidden.

Механика такая. Залипающий элемент привязывается не к окну, а к ближайшему предку-контейнеру прокрутки. Пока такого предка нет, контейнером считается само окно — и заголовок честно залипает к верху вьюпорта. Но как только промежуточный родитель получает overflow: hidden, он сам становится контейнером прокрутки. Теперь top: 0 отсчитывается уже от него, а раз этот родитель не прокручивается (его контент помещается целиком), залипать просто негде — элемент ведёт себя как обычный.

/* родитель ради обрезки декора стал контейнером прокрутки —
   и невольно сломал залипание заголовка внутри */
.section {
  overflow: hidden;   /* ← заменить на clip */
}

.section__heading {
  position: sticky;
  top: 0;
}

Замена одной строки на overflow: clip чинит всё: декор по-прежнему обрезается, но родитель больше не контейнер прокрутки, поэтому залипание снова отсчитывается от окна. Никаких JavaScript-костылей и переноса разметки не требуется.

.section {
  overflow: clip;     /* обрезка осталась, контейнер прокрутки — нет */
}

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

Где ещё пригодится overflow: clip

Починка залипания — не единственная причина. Несколько кейсов, где clip объективно лучше hidden.

Обрезать по одной оси, выпустить по другой

С hidden это невозможно: стоит задать overflow-x: hidden, как браузер принудительно поднимает вторую ось с visible до auto, и по вертикали появляется скролл. С clip пара overflow-x: clip и overflow-y: visible работает буквально: лишнее по горизонтали отрезается, а тултипы, бейджи и тени спокойно вылезают сверху и снизу.

.row {
  overflow-x: clip;     /* паразитный горизонтальный вылет — под нож */
  overflow-y: visible;  /* «NEW», тултипы, тени по вертикали — наружу */
}

Погасить случайный горизонтальный скролл страницы

Классическая боль: один широкий блок (таблица, ушедшая в минус секция, длинное слово) растягивает страницу, и снизу появляется паразитный горизонтальный скроллбар. Привычное лекарство — overflow-x: hidden на body — превращает body в контейнер прокрутки и попутно ломает любое залипание на странице. overflow-x: clip гасит вылет, не трогая прокрутку.

body {
  overflow-x: clip;   /* было overflow-x: hidden — ломало sticky */
}

Декор и анимации за краем карточки

Картинка с лёгким зумом по наведению, угол-ленточка «Sale», блик, уезжающий за границу — всё это надо обрезать по контуру карточки. Раньше для этого хватало hidden, но если внутри той же карточки есть залипающий элемент или нужно выпустить тень по одной оси — clip снимает конфликт. А если речь о фигурной, а не прямоугольной обрезке — это уже задача для свойства clip-path, которое режет по произвольному контуру.

Чуть меньше работы для браузера

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

Подводные камни

  • clip по одной оси рядом с auto/scroll по другой ведёт себя как hidden. Поосевой фокус работает только в паре clip + visible (или clip + clip). Если вторая ось — auto, scroll или hidden, то clip молча превращается в hidden и снова создаёт контейнер прокрутки.
  • Нет программной прокрутки. Если к блоку планируется scrollIntoView, scrollTo или прокрутка к сфокусированному потомку — clip не подойдёт, нужен hidden, auto или scroll.
  • overflow-clip-margin без clip не работает. На hidden/auto/scroll свойство просто игнорируется, отрицательные значения недопустимы.
Поддержка браузерами
chrome
Chrome
90
firefox
Firefox
81
edge
Edge
90
safari
Safari
16
opera
Opera
76

Итог

Правило простое. Нужно просто обрезать вылезающее, без прокрутки и без сюрпризов — берём overflow: clip. Нужен реально прокручиваемый блок (явный скроллбар или прокрутка из JavaScript) — остаётся hidden, auto или scroll. А когда сломалось залипание — первым делом ищем родителя с overflow: hidden и меняем его на clip. Одно значение, минус целый класс трудноуловимых багов.