Более доступная разметка с display:contents

Перевод статьи More accessible markup with display: contents с сайта hiddedevries.nl для css-live.ru, автор — Хидде де Врис

CSS-гриды (CSS Grid Layout) позволяют превратить элемент в грид (сетку) и располагать по нему непосредственных потомков («детей») этого элемента. С учетом этого бывает соблазн использовать более «плоскую», однородную разметку, но чем менее разметка осмысленна, тем хуже обычно ее доступность. C display:contents можно размещать в гриде «внуков», благодаря чему у нас могут быть и доступная разметка, и красивая верстка. Давайте разберемся в этом подробнее!

Ниже я более подробно поясню, что я имею в виду под «детьми» и «внуками», а затем покажу, как можно улучшить дело с помощью display:contents. Примечание: в браузерах это пока работает неправильно, подробности ниже.

Грид работает для непосредственных «детей»

В грид-раскладке, когда на выбранном элементе определен грид, только ближайшие потомки этого элемента становятся грид-элементами и размещаются по гриду. Чтобы освежить это знание для тех, кто не очень знаком с синтаксисом, давайте рассмотрим пример и напишем рецепт. С таким HTML:

Пример раскладки 1

<div class="container"> <h1 class="item">Пенне с томатным соусом</h1> <p class="item">Для этого простого рецепта нужно немного ингредиентов, но вкус будет восхитительным.</p> <div class="item ingredients"> <h2>Вам понадобятся</h2> <ul> <li>консервированные помидоры</li> <li>лук</li> <li>чеснок</li> </ul> </div>
</div>

CSS может быть таким:

.container { display: grid; /* элемент будет грид-контейнером */ grid-template-columns: repeat( 4, 1fr ); /* у грида будет 4 колонки */
}
.item:nth-child(1) { grid-columns: 1 / 2; /* Поместим элемент между грид-линиями 1 и 2 */
} .item:nth-child(2) { grid-columns: 2 / 4; /* Поместим элемент между грид-линиями 2 и 4 */
} .item:nth-child(3) { grid-columns: 4 / 5; /* Поместим элемент между линиями 4 и 5 */
}

Я назвал классы .container и .item, поскольку это ключевые понятия грид-раскладки: есть грид-контейнеры и грид-элементы. Разумеется, можно использовать любую систему именования, какую требует ваш проект.

Причина, по которой мы можем разместить эти элементы в гриде — то, что они непосредственные дочерние элементы («дети») грид-контейнера. Но посмотрите, что будет, если мы решим добавить список спонсоров, например, так:

Пример раскладки 2

Можно было бы добавить в нашу разметку список:

<div class="container"> <h1 class="item">Пенне с томатным соусом</h1> <p class="item">Для этого простого рецепта нужно немного ингредиентов, но вкус будет восхитительным.</p> <div class="item ingredients"> <h2>Вам понадобятся</h2> <ul> <li>консервированные помидоры</li> <li>лук</li> <li>чеснок</li> </ul> </div> <ul class="item sponsors"> <li>Супермаркет 1</li> <li>Супермаркет 2</li> </ul>
</div>

Но мы не сможем расположить по гриду каждого спонсора. Это из-за того, что непосредственный потомок элемента-контейнера, а значит, и грид-элемент, у нас только <ul>. А элементы <li> в нем — уже нет: они не ближайшие потомки нашего грид-контейнера и не участвуют в его играх с сеткой. Но что если мы во что бы то ни стало хотим выровнять спонсоров по нашей сетке?

Более плоская разметка

Один очевидный способ включить спонсоров в грид-раскладку — убрать <ul> и использовать для каждого спонсора по <div>-у. Но тем самым мы «сплющим» нашу разметку в плоскую структуру. Так мы вместе с водой выплескиваем и ребенка.

От элемента <ul> (неупорядоченного списка) достаточно пользы:

  • он остается списком вне контекста нашей страницы, например, в «режиме чтения» Safari он будет показан как список
  • он будет показан как список при печати с выключенными стилями
  • он будет списком для пользователей скринридеров (скринридеры умеют сообщать информацию наподобие «список, 3 пункта»).

Сделав разметку более плоской, мы лишимся этих преимуществ.

display: contents спешит на помощь

C display: contents можно сохранить и нашу разметку, и наше размещение по гриду. Это свойство заставляет элемент делать вид, будто его нет. Он не генерирует CSS-бокса (прямоугольной области в визуальной структуре страницы), так что фоны, рамки и другие свойства для этой области для него больше не работают. Но все эти свойства работают для потомков этого элемента. Спецификация гласит, что элемент ведет себя «как если бы его заменили … его содержимым». Если это звучит странно, рекомендую подробное объяснение Ире Адеринокун (а также нашу статью тех времен, когда это свойство только-только появилось — прим. перев.).

Пример раскладки 3

Фактически, для наших целей в рамках статьи, display: contents делает с элементом следующее: элемент перестает участвовать в грид-раскладке, а его содержимое начинает в ней участвовать. Это позволяет нам привязать к гриду самих спонсоров, а не список, в котором они содержатся.

В спецификации есть несколько занятных граничных случаев, что бывает, если использовать это свойство для элементов вроде img и video.

Проблемы с доступностью у теперешних реализаций display: contents в браузерах

Для людей, пользующихся вспомогательными технологиями (англ. assistive technologies, сокращенно AT), браузеры раскрывают специальные свойства доступности, включая роли элементов на странице (атрибут role). Тем самым их AT знают, где что на странице. У многих элементов изначально есть встроенное значение role, например, у списков — роль list (список).

Здесь-то теперешние браузеры с поддержкой display: contents и сплоховали. Они воспринимают display: contents не как что-то чисто для раскладки, а пытаются додумывать смысл по нему. Это проблема и баг, исходя из примечания в спецификации о влиянии display на раскладку:

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

(выделено мной)

Для нашего примера со списком спонсоров это значит, что элемент будет виден не как список, а как что-то другое (тестовый пример в CodePen).

Ниже я добавил мои результаты теста по браузерам. В каждом из них наш <ul> получает правильное значение role без display: contents, но стоит задать это свойство, как он свою роль теряет.

Firefox 61

Значение role у списка оказывается text leaf, т.е. «конечная текстовая нода» (баг Firefox). Добавлено: это уже пофиксили, исправление выйдет в Firefox 62 в августе 2018-го

Chrome 66

Список показывается как «нода доступности не раскрыта, элемент не отображается» (баг Chromium).

Safari

Список показывается как «Нет информации о доступности» (баг Safari, баг Webkit).

Элемент <ul> — только пример. Это происходит с любым элементом, которому задают display: contents и у которого до этого было значение role.

Если вам небезразлична доступность при использовании display: contents, не стесняйтесь ставить под этими багами свои «+1», добавлять в комментариях примеры из жизни и т.д. Надеюсь, браузеры повысят их приоритет.

С этой проблемой отчасти связано то, что значения display в CSS влияют на семантику таблиц, как объясняет Адриан Розелли; см. также объяснение Стива Фолкнера о том, кто за что отвечает в связке браузер/скринридер.

Заключение

Благодаря display: contents можно размещать по гриду «внуков» грид-контейнера. Это дает возможность делать разметку семантичнее, что здорово для доступности. Чем осмысленнее разметка, тем больше подробностей могут предоставить пользователю вспомогательные технологии. Но есть загвоздка: ни один из браузеров, поддерживающих display: contents сегодня, не сохраняет изначальной роли элемента с этим свойством в дереве доступности.

Я считаю, что доступные роли не должны исчезать при задании display: contents, поскольку это во многом противоречит самой его цели. Я завел баги про это для Firefox, Chromium и Safari. Очень надеюсь, что мы сможем использовать display: contents, не затрагивая доступных ролей элементов, так что можно будет верстать великолепные макеты с отличной доступностью. Надеюсь, продолжение следует!

P.S. Это тоже может быть интересно: