TGViewer
<divelopers> <divelopers> @alexnozer_dev · 1.18K subscribers
Post #352 857
Проблемы карточек, обёрнутых в ссылки

Карточки, обёрнутые в <a>, достаточно распространённый шаблон в Интернете. Дизайнеры хотят, чтобы нажатие в любое место карточки вызывало переход на соответствующую страницу. Самый простой способ — обернуть всё в <a>.

<a href="...">
<article class="card">
<img
src="..."
class="card__image"
alt="Чёрные беспроводные наушники Sony WH‑1000XM5, вид сбоку."
>
<h2 class="card__title">
Беспроводные наушники Sony WH‑1000XM5 (Чёрные)
</h2>
<p class="card__vendor">
Sony
</p>
<p class="card__price">
299 $
</p>
</article>
</a>


Первая проблема заключается в доступности. Весь текстовый контент внутри ссылки будет использован для генерации имени для вспомогательных технологий. Для карточки из примера выше итоговым именем будет:

Чёрные беспроводные наушники Sony WH‑1000XM5, вид сбоку. Беспроводные наушники Sony WH‑1000XM5 (Чёрные) Sony 299 $


Слишком длинное и избыточное имя. Оно будет озвучиваться программами чтения с экрана, отображаться в списке ссылок и на дисплее Брайля, его нужно будет частично или полностью произнести при голосовом управлении интерфейсом.

Быстро исправить проблему можно путём добавления на ссылку атрибута aria-label или aria-labelledby с привязкой к заголовку. Оба способа переопределяют имя, генерируемое из тестового контента. Всё содержимое не будет зачитываться.

<a 
href="..."
aria-label="Беспроводные наушники Sony WH‑1000XM5 (Чёрные)"
>
<!-- ... -->
</a>

<a
href="..."
aria-labelledby="heading"
>
<!-- ... -->
<h2
id="heading"
class="card__title"
>
Беспроводные наушники Sony WH‑1000XM5 (Чёрные)
</h2>
<!-- ... -->
</a>


При использовании aria-label важно, чтобы текст в атрибуте совпадал с видимыми текстом заголовка согласно критерию 2.5.3 Label in Name. С aria-labelledby это гарантируется за счёт связи с заголовком.

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

<a href="...">
<!-- ... -->
<a
href="..."
class="card__vendor"
>
Sony
</a>
<!-- ... -->
</a>


Тогда получается интерактивный элемент внутри интерактивного элемента, что недопустимо по стандарту. Это приводит к неожиданному для пользователя поведению при нажатии и ухудшает опыт взаимодействия с карточкой.

Исправить первую и вторую проблемы, соблюдая при этом первое правило ARIA, можно перенеся ссылку внутрь заголовка и растянув её псевдо-элемент на всю карточку. Нажатие по псевдо-элементу делегирует нажатие на элемент.

<article class="card">
<!-- ... -->
<h2 class="card__title">
<a
href="..."
clasd="card__link"
>
Беспроводные наушники Sony WH‑1000XM5 (Чёрные)
</a>
</h2>
<a
href="..."
class="card__vendor"
>
Sony
</a>
<!-- ... -->
</article>


.card {
position: relative;
}

.card__link::before {
content: '';
position: absolute;
inset: 0;
z-index: 1;
}

.card__vendor {
position: relative;
z-index: 2;
}


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

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

Более подробно эту технику описывает Хейдон Пикеринг в онлайн книге Inclusive Components. Также тему проблем с карточками затрагивает Адриан Розелли в своей статье Block Links, Cards, Clickable Regions, Rows, Etc.

В будущем, надеюсь, решать эту задачу можно будет с помощью Link Area Delegation API, разрабатываемого в рамках OpenUI. Это механизм делегации кликов с других элементов на ссылку, навеянный проблемами с карточками.

#html #css #a11y
Inclusive Components Cards Some of the components I've explored here have specific standardized requirements in order to work as expected. Tab interfaces, for example, have a prescribed structure and a set of interaction behaviors as mandated by the WAI-ARIA specification. It's at…
  • ❤ 22
  • 👍 2
  • 🌚 1
  • 💯 1
  • 🆒 1
More from @alexnozer_dev
  1. Sep 21, 2026Реализация пользовательских атрибутов Под конец прошлого года я рассказывал об API пользов…
  2. Sep 14, 2026Подклассы Event вместо CustomEvent Многие библиотеки предоставляют систему событий в качес…
  3. Sep 11, 2026Эволюция стилей в темах Shopify Адам Ватан на днях поделился новостью, что Shopify выкупил…
  4. Sep 9, 2026Persistent Widgets Многие сайты не должны быть SPA. Но иногда эта архитектура продиктована…
  5. Sep 7, 2026Processing Instructions и маркеры В DOM всё представлено в виде узлов (Node) разных типов.…
  6. Sep 1, 2026@scope и потоковая передача HTML Ноам Розенталь поделился интересной техникой, в которой с…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →