Семантика SVG: role, aria-label, title, desc

SVG-графика по умолчанию ориентирована прежде всего на визуальное отображение. Экранные дикторы, вспомогательные технологии и поисковые системы воспринимают SVG иначе, чем обычный HTML-документ. Без дополнительной семантики интерактивная диаграмма может оказаться полностью недоступной: пользователь услышит лишь сообщение вроде «graphic» или «group».

В D3.js семантика SVG особенно важна, поскольку библиотека активно создаёт и изменяет DOM-структуру динамически. Правильное использование role, aria-label, title и desc позволяет превратить SVG из набора фигур в полноценный доступный интерфейс.


Роль семантики в SVG

SVG-элементы не обладают встроенной семаникой уровня HTML. Например:

<button>Сохранить</button>

Браузер и screen reader сразу понимают назначение кнопки.

SVG-элемент:

<circle cx="50" cy="50" r="40"></circle>

не сообщает ничего о своём смысле.

Для решения этой проблемы используются:

  • role
  • aria-* атрибуты
  • <title>
  • <desc>

Эти механизмы помогают:

  • озвучивать графики;
  • описывать диаграммы;
  • обозначать интерактивные элементы;
  • улучшать навигацию;
  • обеспечивать WCAG-совместимость.

Атрибут role

Назначение

Атрибут role сообщает вспомогательным технологиям, какую функцию выполняет SVG-элемент.

Пример:

<svg role="img"></svg>

Screen reader начинает воспринимать SVG как изображение.


Основные значения role

Role Назначение
img Графическое изображение
presentation Декоративный элемент
graphics-document Полноценный графический документ
graphics-symbol Графический символ
button Интерактивная кнопка
group Группа элементов

Использование role="img"

Наиболее распространённый вариант для диаграмм.

<svg role="img">

Такой SVG начинает восприниматься как единое изображение.

Пример в D3.js

const svg = d3.select("body")
    .append("svg")
    .attr("width", 600)
    .attr("height", 400)
    .attr("role", "img");

Использование role="presentation"

Применяется для декоративной графики, не несущей смысловой нагрузки.

<svg role="presentation"></svg>

Screen reader проигнорирует элемент.

Пример

d3.select("body")
    .append("svg")
    .attr("role", "presentation");

Подходит для:

  • фоновых паттернов;
  • декоративных иконок;
  • визуальных эффектов.

Использование role="button"

SVG-элемент может выступать интерактивным контролом.

<g role="button"></g>

Пример

const button = svg.append("g")
    .attr("role", "button")
    .attr("tabindex", 0);

Важно добавлять:

tabindex="0"

иначе клавиатурная навигация не будет работать.


Атрибут aria-label

Назначение

aria-label задаёт текстовое описание элемента.

Пример:

<svg aria-label="График продаж за 2025 год"></svg>

Screen reader озвучит:

«График продаж за 2025 год»


Использование aria-label в D3.js

const svg = d3.select("body")
    .append("svg")
    .attr("role", "img")
    .attr("aria-label", "Столбчатая диаграмма продаж");

Динамические aria-label

D3.js особенно удобен для генерации динамических описаний.

Пример

svg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("aria-label", d => `Продажи: ${d.value}`);

Каждый столбец получает индивидуальное описание.


aria-label для интерактивных элементов

Пример с кругами

svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("cx", d => d.x)
    .attr("cy", d => d.y)
    .attr("r", 8)
    .attr("role", "button")
    .attr("tabindex", 0)
    .attr("aria-label", d => `Точка данных ${d.name}`);

Атрибут aria-labelledby

Назначение

Позволяет связать SVG с существующим текстовым элементом.

Пример:

<svg aria-labelledby="chart-title"></svg>
<h2 id="chart-title">Диаграмма температуры</h2>

Использование с <title>

<svg aria-labelledby="title-id">
    <title id="title-id">Продажи компании</title>
</svg>

Это более семантичный вариант, чем прямой aria-label.


Элемент <title>

Назначение

<title> задаёт краткое название SVG.

Он:

  • используется screen reader;
  • отображается как tooltip;
  • участвует в доступности.

Структура <title>

<svg>
    <title>График населения</title>
</svg>

Создание <title> в D3.js

svg.append("title")
    .text("График населения");

Особенности <title>

Важный порядок

<title> желательно размещать первым дочерним элементом SVG.

Правильно:

<svg>
    <title>...</title>
    <rect></rect>
</svg>

Tooltip браузера

При наведении курсора браузер показывает содержимое <title>.

Это поведение зависит от браузера.


Элемент <desc>

Назначение

<desc> содержит подробное описание SVG.

Если <title> — краткое имя, то <desc> — полноценное текстовое пояснение.


Пример <desc>

<svg>
    <title>Диаграмма продаж</title>
    <desc>
        Столбчатая диаграмма показывает рост продаж
        с января по июнь.
    </desc>
</svg>

Использование <desc> в D3.js

svg.append("desc")
    .text(`
        Линейный график отображает изменение
        температуры по месяцам.
    `);

Разница между title и desc

Элемент Назначение
title Краткое название
desc Подробное описание

Семаника сложных диаграмм

Полная структура доступного SVG

<svg
    role="img"
    aria-labelledby="chart-title chart-desc">

    <title id="chart-title">
        Продажи по кварталам
    </title>

    <desc id="chart-desc">
        Диаграмма показывает увеличение продаж
        во втором и третьем квартале.
    </desc>

</svg>

Реализация в D3.js

const svg = d3.select("body")
    .append("svg")
    .attr("width", 800)
    .attr("height", 400)
    .attr("role", "img")
    .attr("aria-labelledby", "chart-title chart-desc");

svg.append("title")
    .attr("id", "chart-title")
    .text("Продажи по кварталам");

svg.append("desc")
    .attr("id", "chart-desc")
    .text(`
        Столбчатая диаграмма показывает рост
        продаж во втором квартале.
    `);

Доступность отдельных элементов графика

Иногда требуется озвучивание каждой точки данных.

Пример

svg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", d => x(d.name))
    .attr("y", d => y(d.value))
    .attr("width", x.bandwidth())
    .attr("height", d => height - y(d.value))
    .attr("tabindex", 0)
    .attr("role", "img")
    .attr(
        "aria-label",
        d => `${d.name}: ${d.value} продаж`
    );

Использование tabindex

SVG-элементы не получают фокус автоматически.

Для клавиатурной навигации используется:

tabindex="0"

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

svg.selectAll("circle")
    .attr("tabindex", 0);

Теперь элементы можно выбирать через клавишу Tab.


Обработка клавиатуры

Интерактивный SVG обязан поддерживать клавиатуру.

Пример

svg.selectAll("circle")
    .on("keydown", function(event, d) {

        if (event.key === "Enter") {
            console.log(d);
        }

    });

aria-hidden

Назначение

Скрывает элемент от screen reader.

aria-hidden="true"

Пример

svg.append("path")
    .attr("aria-hidden", "true");

Подходит для:

  • декоративных линий;
  • теней;
  • визуальных украшений.

Семаника групп <g>

Группы SVG также могут быть доступны.

Пример

const group = svg.append("g")
    .attr("role", "group")
    .attr("aria-label", "Серия данных 2025");

Доступность легенды

Пример

legend
    .attr("role", "group")
    .attr("aria-label", "Легенда диаграммы");

Доступность осей

Оси часто перегружают screen reader.

Иногда их скрывают:

axisGroup.attr("aria-hidden", "true");

Практика для bar chart

Полный пример

const svg = d3.select("body")
    .append("svg")
    .attr("width", 700)
    .attr("height", 400)
    .attr("role", "img")
    .attr(
        "aria-labelledby",
        "sales-title sales-desc"
    );

svg.append("title")
    .attr("id", "sales-title")
    .text("Продажи по регионам");

svg.append("desc")
    .attr("id", "sales-desc")
    .text(`
        Столбчатая диаграмма показывает
        продажи в северном, южном
        и западном регионах.
    `);

svg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", d => x(d.region))
    .attr("y", d => y(d.sales))
    .attr("width", x.bandwidth())
    .attr("height", d => height - y(d.sales))
    .attr("tabindex", 0)
    .attr("role", "img")
    .attr(
        "aria-label",
        d => `Регион ${d.region}, продажи ${d.sales}`
    );

Семаника SVG-иконок

Декоративная иконка

<svg aria-hidden="true"></svg>

Значимая иконка

<svg
    role="img"
    aria-label="Настройки">
</svg>

Ошибки доступности SVG

Отсутствие role

Плохо:

<svg></svg>

Хорошо:

<svg role="img"></svg>

Отсутствие описания

Плохо:

<svg role="img"></svg>

Хорошо:

<svg
    role="img"
    aria-label="Диаграмма доходов">
</svg>

Избыточная озвучка

Плохо:

svg.selectAll("*")
    .attr("aria-label", "...");

Screen reader начинает читать сотни элементов.


Производительность и доступность

Большие SVG могут содержать тысячи DOM-узлов.

Избыточные ARIA-атрибуты:

  • ухудшают производительность;
  • перегружают accessibility tree;
  • затрудняют навигацию.

Поэтому:

  • важные элементы описываются;
  • второстепенные скрываются через aria-hidden.

Проверка доступности SVG

Инструменты

Инструмент Назначение
Lighthouse Аудит accessibility
axe DevTools Проверка ARIA
NVDA Screen reader
VoiceOver Screen reader macOS
WAVE Анализ доступности

Lighthouse и SVG

Lighthouse проверяет:

  • наличие accessible name;
  • корректность ARIA;
  • keyboard navigation;
  • контрастность.

Accessibility tree

Браузер строит дерево доступности отдельно от DOM.

Неправильная семаника SVG приводит к:

  • пропуску элементов;
  • неверному чтению;
  • хаотичной навигации.

Рекомендации для D3.js-проектов

Для диаграмм

Использовать:

  • role="img"
  • <title>
  • <desc>
  • aria-labelledby

Для интерактивности

Добавлять:

  • tabindex="0"
  • keyboard events
  • aria-label

Для декоративных элементов

Использовать:

aria-hidden="true"

или:

role="presentation"

Рекомендуемая структура доступного SVG

<svg
    role="img"
    aria-labelledby="chart-title chart-desc">

    <title id="chart-title">
        Название диаграммы
    </title>

    <desc id="chart-desc">
        Подробное описание диаграммы
    </desc>

    <g role="group">

    </g>

</svg>

Интеграция accessibility в D3.js

D3.js предоставляет полный контроль над DOM, поэтому accessibility реализуется напрямую через .attr().

Типичный шаблон

selection.attr("role", "img")
selection.attr("aria-label", "Описание")
selection.attr("tabindex", 0)

Поддержка WCAG

SVG с корректной семаникой помогает соответствовать:

  • WCAG 2.1
  • Section 508
  • EN 301 549

Особенно важны критерии:

WCAG Описание
1.1.1 Текстовые альтернативы
2.1.1 Доступность с клавиатуры
4.1.2 Name, Role, Value

Семаническая архитектура SVG

Хорошая архитектура SVG обычно строится по уровням:

Уровень Семаника
SVG-контейнер role, title, desc
Группы role="group"
Интерактивные элементы button, tabindex, aria-label
Декоративные элементы aria-hidden

Пример полностью доступного SVG в D3.js

const svg = d3.select("#chart")
    .append("svg")
    .attr("width", 900)
    .attr("height", 500)
    .attr("role", "img")
    .attr(
        "aria-labelledby",
        "revenue-title revenue-desc"
    );

svg.append("title")
    .attr("id", "revenue-title")
    .text("Доход компании по месяцам");

svg.append("desc")
    .attr("id", "revenue-desc")
    .text(`
        Линейная диаграмма показывает
        рост дохода с января по декабрь.
    `);

const bars = svg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", d => x(d.month))
    .attr("y", d => y(d.value))
    .attr("width", x.bandwidth())
    .attr("height", d => height - y(d.value))
    .attr("tabindex", 0)
    .attr("role", "img")
    .attr(
        "aria-label",
        d => `${d.month}, доход ${d.value}`
    );

bars.on("keydown", (event, d) => {

    if (event.key === "Enter") {
        console.log(d);
    }

});