Visibility

Управление видимостью: display, visibility, overflow по осям, доступность для скринридеров и адаптивное скрытие по брейкпоинтам.

Опциональный модуль packages/utils/scss/_visibility.scss

Display-утилиты

КлассCSSНазначение
.gr-hiddendisplay: noneПолное скрытие элемента
.gr-blockdisplay: blockБлочный элемент
.gr-inlinedisplay: inlineСтрочный элемент
.gr-inline-blockdisplay: inline-blockСтрочно-блочный элемент

Изменение в 0.8.0. .gr-flex-display и .gr-grid-display из этого модуля удалены. Флекс- и грид-контейнер включает ядро: .gr-flex, .gr-inline-flex и .gr-grid — вместе с адаптивными вариантами .gr-flex-md, .gr-grid-lg. Имена расходились только из-за запрета пересечения классов между пакетами, а смысл у них был один: .gr-grid в ядре существовал с самого начала.

Примеры display
Виден всегда
Скрыт (display:none)
Виден всегда

Видимость без изъятия из потока

КлассCSSОтличие от .gr-hidden
.gr-invisiblevisibility: hiddenМесто под элемент сохраняется, вёрстка не сдвигается
.gr-visiblevisibility: visibleВернуть видимость

Только для скринридера

.gr-sr-only убирает элемент с экрана, но оставляет его в дереве доступности: текст озвучивается, хотя визуально его нет. Так подписывают иконки-кнопки и поясняют ссылки «Подробнее».

<button>
  <svg aria-hidden="true">…</svg>
  <span class="gr-sr-only">Закрыть диалог</span>
</button>

Генерируемый CSS:

.gr-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

Изменение в 0.7.0. Вместо устаревшего clip: rect(0, 0, 0, 0) используется clip-path: inset(50%) — результат тот же, свойство действующее. Послабление для clip убрано и из конфигурации stylelint.

Ссылка «к содержимому»

.gr-sr-only-focusable скрыт ровно до тех пор, пока его не выбрали с клавиатуры: мышью его не видно, по Tab он появляется. Это первая ссылка страницы, позволяющая пропустить навигацию.

<a class="gr-sr-only-focusable" href="#main">Перейти к содержимому</a>
Нажмите Tab, поставив курсор в этот блок
Перейти к содержимому

Обратная операция — .gr-not-sr-only: возвращает в поток элемент, скрытый таким образом на узких экранах.

Overflow-утилиты

Пять значений, каждое — по обеим осям сразу и по каждой отдельно. clip отличается от hidden тем, что запрещает прокрутку вообще, в том числе программную.

Обе осиПо горизонталиПо вертикали
.gr-overflow-auto.gr-overflow-x-auto.gr-overflow-y-auto
.gr-overflow-hidden.gr-overflow-x-hidden.gr-overflow-y-hidden
.gr-overflow-clip.gr-overflow-x-clip.gr-overflow-y-clip
.gr-overflow-scroll.gr-overflow-x-scroll.gr-overflow-y-scroll
.gr-overflow-visible.gr-overflow-x-visible.gr-overflow-y-visible
<!-- широкая таблица прокручивается внутри себя, а не тянет страницу вбок -->
<div class="gr-overflow-x-auto">
  <table>…</table>
</div>

Прокрутка внутри скруглённого блока

КлассCSS
.gr-scroll-clipclip-path: inset(0 round var(--gr-r, 0))

Полосу прокрутки движок рисует поверх собственного края элемента и по border-radius не обрезает: у скруглённого блока с overflow: auto она срезает углы, а если у блока есть рамка — съедает и её нижнюю сторону. Свойство overflow тут бессильно: оно обрезает содержимое, а не полосу самого элемента.

Приёма два, и выбор между ними — не вкусовой:

Что за блокПриём
Фон и скругление, рамки нет.gr-scroll-clip — один класс, лишний узел не нужен
Есть рамкаПрокрутку вложить: рамка и радиус снаружи, overflow внутри
Три блока: как есть, с обрезкой, с вложенной прокруткой

Полоса срезала углы этого блока

Здесь та же полоса обрезана по радиусу

А здесь прокрутка внутри, и рамка цела

<!-- без рамки: хватает одного класса -->
<div class="gr-radius-4 gr-overflow-x-auto gr-scroll-clip">…</div>

<!-- с рамкой: рамка и радиус снаружи, прокрутка внутри -->
<div class="gr-radius-4 gr-overflow-hidden gr-border">
  <p class="gr-overflow-x-auto gr-whitespace-nowrap">…</p>
</div>

Рамку .gr-scroll-clip не спасает. Полоса рисуется поверх нижней границы своего элемента, и обрезка углов этого не отменяет: скругление вернётся, а нижняя линия рамки так и останется закрытой. Свойствами самого блока это не лечится — рамке нужен отдельный узел снаружи прокрутки. Вложенность заодно обрезает полосу по радиусу сама, обычным overflow: hidden.

Радиус берётся из --gr-r — той самой переменной, которую публикуют утилиты со значением: .gr-radius-4, .gr-radius-full и остальные из каскада скруглений. У голого .gr-radius своего значения нет — он читает переменную, а не задаёт её, поэтому в паре с обрезкой нужен класс с числом. Без --gr-r утилита не делает ничего: фолбэк 0 оставляет прямые углы прямыми, вместо того чтобы скруглить блок, который скруглять не просили.

Вторая цена обрезки. clip-path режет всё, что выходит за коробку элемента, — внешнюю тень и контур фокуса тоже. Блоку с тенью нужна та же вложенность: тень на внешнем элементе, прокрутка на внутреннем. Прокручиваемой области с tabindex="0" контур фокуса возвращают отрицательным outline-offset.

Адаптивное скрытие

Классы .gr-hidden-{sm,md,lg,xl} скрывают элемент начиная с указанного брейкпоинта. Полезно для mobile-only / desktop-only элементов.

КлассПоведение
.gr-hidden-smСкрыт на экранах ≥ 640px
.gr-hidden-mdСкрыт на экранах ≥ 768px
.gr-hidden-lgСкрыт на экранах ≥ 1024px
.gr-hidden-xlСкрыт на экранах ≥ 1280px
Адаптивное скрытие (измените ширину окна)
Виден только на мобильных (<640px)
Виден всегда
Скрыт всегда
<div class="gr-hidden-sm">Виден только на мобильных</div>
<div class="gr-hidden-md">Скрыт на планшетах и шире</div>
<div class="gr-hidden-lg">Скрыт на десктопах</div>

Адаптивные display-классы

Кроме скрытия, доступны классы для принудительного изменения display на определённом брейкпоинте.

КлассПрименение
.gr-block-sm.gr-block-xldisplay: block от брейкпоинта
.gr-inline-sm.gr-inline-xldisplay: inline от брейкпоинта
.gr-inline-block-sm.gr-inline-block-xldisplay: inline-block от брейкпоинта
.gr-flex-sm.gr-grid-xldisplay: flex / grid от брейкпоинта — ядро, не этот модуль
<div class="gr-hidden gr-block-md">
  Скрыт на мобильных, виден от 768px
</div>

Примеры использования

Видимость — это всегда «что и для кого»: элемент прячется от узкого экрана, но не от широкого; от глаз, но не от скринридера; от экрана, но не от принтера. Рецепты ниже — четыре самых частых расклада.

Мобильное меню

<nav>
  <button class="gr-hidden-md">Меню (гамбургер)</button>
  <div class="gr-hidden gr-block-md">
    <ul role="list" class="gr-flex gr-gap">
      <li><a href="#">Home</a></li>
      <li><a href="#">About</a></li>
      <li><a href="#">Contact</a></li>
    </ul>
  </div>
</nav>

Пара «скрыть + показать» собирается из классов этого модуля. .gr-flex-md и .gr-grid-md живут в ядре, а слой утилит объявлен позже ядра — рядом с .gr-hidden они проигрывают, и элемент остаётся скрытым на любой ширине. Поэтому флекс-ряд, появляющийся от брейкпоинта, оборачивается: снаружи — .gr-hidden .gr-block-md, внутри — обычный .gr-flex .gr-gap.

Результат (измените ширину окна)
Logo
☰ Меню (mobile)

Второстепенное — только широкому экрану

Боковая колонка «Сейчас читают», декоративная иллюстрация, подробности в строке заказа — то, без чего мобильный экран обойдётся. Пара .gr-hidden .gr-block-lg (или .gr-flex-lg, если блок — флекс) прячет элемент через display: до 1024px он не занимает места вовсе, а не оставляет дыру, как оставила бы .gr-invisible. Основной контент при этом один для всех экранов — скрывается только дополнение.

<div class="gr-flex gr-gap">
  <article class="gr-flex-1 gr-min-w-0">Статья — видна всем</article>
  <aside class="gr-hidden gr-block-lg gr-w-1/4">Сейчас читают — только от 1024px</aside>
</div>
Колонка исчезает из потока до 1024px (сузьте окно)

Статья

Видна на любой ширине и занимает всё освободившееся место.

Кнопки-иконки с невидимой подписью

Панель действий из одних пиктограмм экономит место, но кнопка без текста нема для скринридера. Подпись кладётся внутрь кнопки с .gr-sr-only: глазами её нет, в дереве доступности — есть. Сами кнопки — .gr-btn .gr-btn-icon .gr-btn-ghost из ui, ряд собирает флекс с мелким зазором.

<div class="gr-flex gr-gap-sm">
  <button class="gr-btn gr-btn-icon gr-btn-ghost">✎<span class="gr-sr-only">Редактировать</span></button>
  <button class="gr-btn gr-btn-icon gr-btn-ghost">⧉<span class="gr-sr-only">Дублировать</span></button>
  <button class="gr-btn gr-btn-icon gr-btn-ghost gr-text-danger">✕<span class="gr-sr-only">Удалить</span></button>
</div>
Иконки — глазам, .gr-sr-only-подписи — скринридеру

Скрыть при печати

Страница заказа или статья на печати не нуждается в навигации, кнопках и баннерах. Печатных утилит вроде .gr-print-hidden в библиотеке нет намеренно — сценарий редкий и решается одним своим правилом поверх любого класса или контейнера. Тени при печати библиотека уже глушит сама — токены --gr-shadow-* обнуляются темой.

@media print {
  .no-print { display: none; }
}

<nav class="no-print">…</nav>
<button class="gr-btn gr-btn-primary no-print">Оплатить</button>