<divelopers> – Telegram
<divelopers>
1.03K subscribers
21 photos
253 links
Рандомные мысли про HTML, CSS, доступность, пользовательские интерфейсы, производительность, браузеры и веб-стандарты.

Автор: @alexnozer
Download Telegram
Атрибуты supports и layer для <link>

У элемента <link rel="stylesheet"> можно указать атрибут media с выражением как в директиве @media. Браузер будет проверять медиа-выражение на соответствие текущему окружению, что повлияет на загрузку файла стилей.

<link
rel="stylesheet"
href="mobile.css"
media="(width < 600px)"
>


Если условие не выполняется, файл стилей загружается с низким приоритетом и асинхронно (не блокирует отрисовку), а сами стили не применяются. Эта механика лежит в основе доклада Вадима Макеева «условно адаптивно», о котором я делал пост.

В 496-м выпуске Веб-стандартов слушатель задал вопрос про директиву @import и каскадные слои (@layer). Хочется обернуть стили из внешнего файла в каскадный слой без изменения исходного кода этого файла.

Тут бы пригодился атрибут layer у элемента <link>, который помещал бы загруженные стили в указанный слой. Но такого атрибута на данный момент нет. Единственный способ — использовать директиву @import с layer() внутри элемента <style>:

<!-- атрибут layer не поддерживается
<link
rel="stylesheet"
href="utils.css"
layer="utils"
>
-->

<style>
@import url('utils.css') layer(utils);
</style>


Браузер загрузит utils.css и поместит его в слой с именем utils, что позволит управлять каскадом: повысить или понизить приоритет всех стилей из этого файла по сравнению со стилями из других слоёв.

Директива @import известна как плохое решение для производительности и многие рекомендуют её избегать. Дело в том, что вложенные @import приводят к последовательной загрузке стилей вместо параллельной.

Не смотря на то, что в файле utils.css могут отсутствовать директивы @import и загрузка должна быть как у <link rel="stylesheet">, всё ещё остаются вопросы к такому методу подключения внешних файлов стилей.

Другая полезная директива — @supports. В теории хотелось бы загружать некоторые стили только при наличии поддержки определённых CSS-функций. Это пригодилось бы для реализации прогрессивного улучшения:

<!-- атрибут supports не поддерживается
<link
rel="stylesheet"
href="scroll-animations.css"
supports="(animation-timeline: scroll())"
>
-->

<style>
@import url('scroll-animations.css') supports(animation-timeline: scroll());
</style>


В WHATWG есть issue, где обсуждается расширение элемента <link>, чтобы учесть директивы @layer и @supports. Пока решение по этому вопросу не принято, но есть разные идеи того, как это может выглядеть и работать.

#html #css
8👍5🔥2
Будущее фронтенда: хватит ли веб-платформы?

20-21 октября буду на конференции Frontendconf в качестве участника круглого стола на тему «Будущее фронтенда: хватит ли веб-платформы?» Обсудим разработку UI-библиотек, как в этом могут помочь современные возможности веб-платформы, конечно же затронем веб-компоненты и попытаемся заглянуть в будущее. Кто среди подписчиков будет на Frontendconf — приходите послушать нашу дискуссию.

#ui
🔥10👎1🌚1
Scoped Focusgroup

Дисклеймер: это обзор раннего предложения новых функций. Синтаксис может измениться в будущем или от функций могут отказаться.


Я слежу за развитием предложения focusgroup от OpenUI и жду реализации в браузерах, а опросы показывают интерес сообщества к этой функции. К моему сожалению, предложение перевели в статус неактивного.

Недавно было опубликовано новое предложение Scoped Focusgroup — следующую итерациею focusgroup. Некоторые идеи оригинального предложения отложили, а остальные немного переработали. Что изменилось:

- Поддержка CSS-свойств отложена на будущее;
- Поддержка навигации по двум осям отложена на будущее;
- Добавлена область действия для управление фокусом только там, где это предполагается исходя из ролей элементов.

В целом синтаксис и поведение атрибута focusgroup и его значений сохраняется (за исключением навигации по двум осям). Но добавляется набор новых значений для определения области действия, которые соответствуют ролям ARIA.

Новый синтаксис выглядит так:

<div focusgroup="<behavior> [inline|block] [wrap] [no-memory]">
<!-- ... -->
</div>

- <behavior> — роль элемента и поведение фокуса. Принимает значения toolbar, tablist, radiogroup, listbox, menu, menubar, в будущем grid;
- [inline|block] — направление навигации: inline — горизонтально, block — вертикально, зависит от направления письма;
- [wrap] — циклический перенос между крайними элементами. Если значение wrap указано, то перенос включён;
- [no-memory] — сброс последнего сфокусированного элемента при выходе за пределы виджета. Если значение no-memory указано, фокус сбрасывается.

<div
focusgroup="toolbar inline wrap"
aria-label="Форматирование текста"
>
<button type="button">
Жирный
</button>
<button type="button">
Курсив
</button>
<button type="button">
Подчёркнутый
</button>
</div>

В примере элемент получает роль toolbar (из значения <behavior>), кнопки удаляются из последовательности табуляции, добавляются обработчики клавиатуры для горизонтальной навигации с переносом между крайними кнопками.

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

#html #ui
👍6👾2
WCAG 2.2 — международный стандарт ISO/IEC

21 октября Международная организация по стандартизации (ISO/IEC JTC 1) утвердила WCAG 2.2 в качестве международного стандарта ISO/IEC 40500:2025. Это позволит большему числу стран официально принять WCAG 2.2.

Многие страны в той или иной степени используют WCAG разных версий как основу для законов об обеспечении доступности. Теперь они смогут использовать международный стандарт ISO/IEC 40500:2025.

#a11y
🎉6🌚1👾1
Quiet UI

Месяц назад Кори ЛаВиска, создатель библиотеки веб-компонентов Shoelace, объявил у себя в блоге о запуске проекта Quiet UI. Это библиотека из 80+ готовых UI-компонентов, которые выполнены в виде веб-компонентов.

Может возникнуть вопрос: зачем автор создал ещё одну библиотеку веб-компонентов, когда есть Shoelace и Web Awesome? Более того, в Quiet UI используются многие наработки из этих двух проектов.

Как отмечает автор, Shoelace и его наследник Web Awesome — это серьёзные проекты с большим сообществом, командой разработки и финансовой поддержкой. Quiet UI — пет-проект для тестирования идей и творчества.

Quiet UI не конкурирует с Shoelace и Web Awesome, а служит творческой площадкой и испытательным полигоном. Вскоре после релиза автор поменял лицензию на MIT и теперь библиотека доступна как ПО с открытым исходным кодом.

В Quiet UI используются некоторые новые возможности браузеров и есть компоненты, которых нет в Shoelace и Web Awesome. Не исключено что в будущем некоторые из таких компонентов будут адаптированы и перенесены.

В общем интересный проект, который можно использовать как альтернативу Shoelace и Web Awesome. Причём использовать можно с любым фреймворком или без них, так как в этом сила веб-компонентов.

UPD 11.11.2025. Автор в конечном итоге принял решение оставить проект для личного пользования. Поэтому теперь библиотека недоступна для использования и больше не предоставляется как проект с открытым исходным кодом. Все статьи о проекте в блоге удалены.


#ui
👍12🔥2🌚1👾1
Порядок в <head>

Как часто вы заглядываете в <head> ваших проектов и проверяете, что там происходит? А стоило бы периодически это делать, потому что содержимое и порядок элементов могут негативно влиять на производительность.

Начать стоит с валидности. Внутри <head> допустимы только определённые элементы:

- Один <noscript>
- Один <base>
- Ноль и более <link>
- Ноль и более <meta>
- Ноль и более <noscript>
- Ноль и более <style>
- Ноль и более <template>
- Ноль и более <nonoscript>

При обнаружении любого другого элемента парсер немедленно закрывает <head> и переносит оставшиеся элементы в <body>.

<head>
<meta charset="utf-8">
<noscript>Мой сайт</noscript>
<img src="pixel.png">
<link
rel="preload"
href="custom-font.woff2"
as="font"
type="font/woff2"
>
<link
rel="stylesheet"
href="styles.css"
>
<noscript
src="noscript.js"
defer
></noscript>
</head>
<body>
<!-- ... -->
</body>

<!-- парсер сделает так: -->

<head>
<meta charset="utf-8">
<noscript>Мой сайт</noscript>
</head>
<body>
<img src="pixel.png">
<link
rel="preload"
href="custom-font.woff2"
as="font"
type="font/woff2"
>
<link
rel="stylesheet"
href="styles.css"
>
<noscript
src="noscript.js"
defer
></noscript>
<!-- ... -->
</body>


У этого есть разные последствия, потому что от размещения элементов в <head> или <body> меняется место и приоритет в очереди загрузки ресурсов, статус блокировки парсера, нарушается работа скриптов, завязанных на <head>.

Недопустимые элементы в <head> часто появляются при интеграции различных «пикселей», аналитических фреймов и прочих сторонних вставок. Обычно они представлены элементами <img>, <iframe> и <div>.

Инструкции к таким вставками обычно предлагают вставить сниппет в начало <body>, но нередко они попадают в <head> вместе с элементами <noscript>. Отловить подобные ошибки помогает валидатор HTML.

Другой источник проблем — неверный порядок элементов в <head>. Простая перестановка элементов в нужном порядке может выиграть вплоть до нескольких секунд времени загрузки. Об этом есть отличный доклад Гарри Робертса.

Выявить проблемы с порядком элементов помогут два инструмента:

- ct.css — файл стилей, который делает элементы <head> видимыми и подсвечивает проблемные места;
- capo.js — сниппет, который анализирует содержимое <head> и выводит в консоль текущий и отсортированный порядок.

Отдельного внимания заслуживают менеджеры тегов, через которые на сайты неконтролируемо попадают разные маркетинговые скрипты и засоряют <head>. Подробнее об этом как-нибудь напишу пост.

#html #performance
👍16🔥42🌚1👾1
Релиз Web Awesome

В понедельник я рассказал о проекте Quiet UI, а тут спустя почти 4 месяца бета-теста произошёл официальный релиз другого проекта того же автора — Web Awesome. Это полноценный UI kit на базе веб-стандартов, «Bootstrap нового поколения».

Web Awesome — наследник библиотеки компонентов с открытым исходным кодом Shoelace, которую я часто упоминаю. Проведена работа по улучшению архитектуры и системы темизации, добавлены новые компоненты, утилиты, темы и раскладки.

Web Awesome распространяется по модели freemium. Все базовые компоненты, утилиты, раскладки и 3 темы доступны для использования бесплатно. Всё, что было доступно в Shoelace, бесплатно доступно в Web Awesome.

Платная часть распространяется по подписке и включает в себя pro-компоненты, визуальный конфигуратор тем, шаблоны готовых интерфейсов из компонентов, раскладок и утилит, Figma kit, дополнительные темы, CDN и техническую поддержку.

В честь релиза предлагается пожизненная скидка 20% на подписку Pro до 19 ноября.

#ui
4🔥2🌚1👾1
Toolbar Elements

Члены группы OpenUI продолжают работать над UI-примитивами и опубликовали черновик предложения Toolbar Elements. Они предлагают добавить в веб-платформу встроенное решение для создания панели инструментов.

Такие панели встречаются во многих веб-приложениях: Google Docs, Figma, VSCode, Notion, Photoshop Web и так далее. Панель инструментов группирует кнопки, списки выбора, переключатели и другие интерактивные элементы интерфейса.

Сейчас для создания панели инструментов нужно использовать ARIA-роль toolbar. В спецификации ARIA также пишется, что у элемента с ролью toolbar можно реализовать управление фокусом, что подразумевает навигацию стрелками.

Дисклеймер: это обзор раннего предложения новых функций. Синтаксис может измениться в будущем или от функций могут отказаться.


Предлагается новый элемент <toolbar>, в который будет встроена роль toolbar и навигация по интерактивным элементам с помощью стрелок. По умолчанию <toolbar> отображает элементы горизонтально.

<toolbar aria-label="Main">
<button
id="undoButton"
aria-label="Undo"
>
<img src="..." alt="">
</button>
<button
id="redoButton"
aria-label="Redo"
>
<img src="..." alt="">
</button>
<button
id="printButton"
aria-label="print"
>
<img src="..." alt="">
</button>
<input
id="zoomSelect"
list="zoom"
aria-label="zoom"
>
<!-- ... -->
</toolbar>

<datalist id="zoom">
<option>Fit</option>
<option>50%</option>
<!-- ... -->
</datalist>

<noscript>
undoButton.addEventListener("click",
() => undo();
);
// ...
</noscript>

Есть открытые вопросы о том, как должен стилизоваться <toolbar>, как должно работать изменение ориентации (для вертикальной панели инструментов) и нужно ли поддерживать внутри разделители (как <hr> внутри <select>).

В будущем предполагается расширить возможности <toolbar>, которые на данный момент находятся за пределами предложения. В конечном итоге в веб-платформе должен появиться встроенный аналог шаблона панели инструментов.

#html #ui
🔥7🌚4👾1
Negative border radius

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

Есть разные хаки с абсолютно позиционированными элементами-масками. В целом вёрстка таких блоков — задача непростая. Но ребята из HTML Academy выкатили решение — библиотеку nebo.css.

Nebo — сокращение от negative border radius, как часто называют подобные эффекты. Это CSS-файл на 100 с лишним строчек кода, который предоставляет несколько классов и пользовательские свойства для точечной настройки.

В этих 100 строках скрыта магия CSS: градиенты в сочетании с математикой и, конечно же, масками позволяют добиться нужного эффекта. Кому интересно, как это работает, предлагаю изучить исходный код библиотеки.

Для использования достаточно подключить nebo.css к проекту или просто скопировать код и вставить в файл стилей. Далее к элементу, у которого нужно сделать вырез, добавляется класс nebo и модификатор стороны, с которой нужен вырез:

<link rel="stylesheet" href="nebo.css">

<!-- br - сокращение bottom right -->
<div class="card nebo nebo--br">
<!-- контент карточки -->
</div>


Доступны модификаторы для четырёх сторон:

- .nebo--tl — top left
- .nebo--tr — top right
- .nebo--bl — bottom left
- .nebo--br — bottom right

Логические свойства не завезли, но это отличный повод сделать pull request. Также есть ограничение, что одновременно на элементе можно сделать только один вырез. Если нужно несколько вырезов, то придётся использовать обёртки.

Далее у элемента с вырезом можно точечно настраивать параметры с помощью пользовательских свойств:

- --nb-r — радиус всех трёх скруглений
- --nb-w — ширина выреза
- --nb-h — высота выреза

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

Подробнее о библиотеке рассказывает Александр Першин в видео Вогнутые углы за пару строк CSS из рубрики CSS Боль. Также есть интерактивная песочница, где пошагово демонстрируются возможности библиотеки.

#css

Alt изображения: четыре квадратных блока с фиолетовым градиентным фоном расположены по два в ряд. У каждого блока есть вырез со скруглёнными углами. У первого блока квадратный вырез в левом верхнем углу. У второго блока вытянутый прямоугольный вырез в правом верхнем углу. У третьего блока вытянутый прямоугольный вырез в левом нижнем углу. У четвёртого блока квадратный вырез в правом нижнем углу.
10🔥4❤‍🔥2👾1
История архитектуры веб-приложений

На сайте Plain Vanilla есть статья с обзором архитектуры веб-приложений: классический и современный серверный рендеринг, клиентский рендеринг, серверный рендеринг с гидратацией на клиенте. В конце статья приходит к подходу local-first, о чём в блоге есть отдельная статья. Хороший исторический экскурс, рекомендую к прочтению.

#architecture
👍103🔥3👾1
Critical CSS

Во многих руководствах по улучшению производительности рекомендуется реализовать технику критического CSS (Critical CSS). То есть выделить стили, важные для начальной области просмотра, и встроить их в <head>.

Остальные, некритические стили, загрузить асинхронно, чтобы они не блокировали отрисовку. Звучит заманчиво, но у меня эта техника оптимизации вызывает лишь вопросы и мысли о сложности реализации.

Что считать критическими стилями? Какие размеры начальной области просмотра брать за основу, если их огромное множество? Выносить ли стили скрытых элементов, которые, тем не менее, находятся на начальном экране?

Как эффективно разделять стили на критические и некритические? Как вносить изменения, чтобы критическая часть обновлялась? Как лучше загружать некритическую часть? И ещё много вопросов, ответы на которые не до конца ясны.

Единственное применение, как мне видится, в одностраничных лэндингах. Там весь CSS можно встроить в <head> из-за небольшого объёма кода. Инструменты, такие как penthouse, как раз работают с одной страницей.

Но Critical CSS, как подход, не рационален для лэндингов и в целом. Вряд-ли CSS будет узким местом, а реализация техники может не дать хоть какого-то улучшения. Гарри Робертс в статье несколько раз акцентирует на этом внимание.

Если есть проблема с долгим FCP, то стоит убедиться, а в CSS ли дело. Чаще всего проблема не в нём. Но если проблема действительно из-за большого количества CSS, то стоит применить другие техники оптимизации.

Прежде всего, сократить объём CSS за счёт грамотной стратегии разделения по страницам, разделам или компонентам и загрузкой только там, где это нужно. Это можно использовать в сочетании с элементами <link> в <body>.

Ну и, само собой, стили нужно минифицировать, сжимать хотя-бы через gzip, а лучше через brotli или zstd, проверять поддерживаемые браузеры для предотвращения лишних префиксов и трансформации и кэшировать.

#css #performance
👍103💯2
Расширение <label>

Элемент <label> предназначен для добавления подписи к элементам управления. Есть два способа: вложить элемент управления внутрь <label> или связать <label> и элемент управления через атрибуты for и id.

Также есть 5 правил ARIA, первое из которых гласит: «используйте встроенные в HTML элементы и атрибуты для передачи нужной семантики вместо использования ARIA». <label> — как раз пример такого встроенного элемента.

<!-- вместо этого: -->
<span id="name-label">Имя</span>
<input
type="text"
name="name"
autocomplete="given-name"
autocapitalize="words"
aria-labelledby="name-label"
>

<!-- лучше сделать так: -->
<label for="name">Имя</label>
<input
type="text"
name="name"
autocomplete="given-name"
autocapitalize="words"
id="name"
>


Всё бы хорошо, но <label> работает только со стандартными элементами управления: <input>, <select>, <textarea>, <button>, <meter>, <progress>, <output> и пользовательскими элементами, которые ассоциированы с формой.

Это значит, что если по тем или иным причинам пришлось создать собственный элемент управления на основе ARIA, то использовать <label> для привязки подписи к нему не получится. Подпись придётся также добавлять с помощью ARIA.

<!-- это не сработает: 
<label for="listbox">Пол</label>
-->
<!-- поэтому делают так: -->
<span id="listbox-label">Пол</span>
<div
role="listbox"
tabindex="0"
id="listbox"
aria-labelledby="listbox-label"
>
<!-- опции -->
</div>


Также кажется логичным использование <label> для задания подписей не только к элементам управления, но и к контейнерам, у которых может быть указано имя. Сейчас для этого нужно использовать ARIA и следовать первому правилу не получается.

<!-- Примеры иллюстративные, 
не используйте их на реальных проектах! -->
<nav id="nav">
<!-- это не сработает -->
<label for="nav">Основная</label>
<!-- ссылки -->
</nav>

<section id="faq-section">
<h2>
<!-- это не сработает -->
<label for="faq-section">FAQ</label>
</h2>
<!-- список вопросов и ответов -->
</section>

<div role="group" id="socials">
<!-- это не сработает -->
<label for="socials">
Мы в социальных сетях
</label>
<!-- ссылки на социальные сети -->
</div>

<!-- это не сработает -->
<label for="formatting">
Форматирование текста
</label>
<div
role="toolbar"
id="formatting"
>
<!-- опции форматирования -->
</div>


Раз <label> уже существует в HTML и может задавать имя для элемента управления через атрибут for, кажется, было бы полезно расширить эту функциональность и на другие элементы, где это уместно на основе роли элемента.

Или нужен новый элемент, например <name> (<caption> и <noscript> уже заняты), а <label> пусть остаётся только для стандартных элементов управления? Аналогичные идеи в сообществе были по поводу элемента <denoscription>.

Что вы думаете по этому поводу? Проголосуйте в опросе ниже.

#html #a11y
1👾1
Кнопки для CSS-карусели

В 135й версии Chrome появились CSS-карусели. С помощью Scroll Snap и новых псевдо-элементов можно создать функциональные карусели (и не только) с прокруткой, стрелками и маркерами, используя лишь HTML и CSS вместо тяжёлых JS-библиотек.

Звучит классно, но разработчики Chrome поторопились и выкатили реализацию, к которой в сообществе возникли вопросы. В частности, критиковалась доступность и использование псевдо-элементов в CSS для создания кнопок.

Многие высказались в пользу использования обычных HTML-кнопок. Я в том числе оставлял фидбек и предлагал использовать активно развивающийся API Invoker Commands, который уже внедрён в Chrome 135 и Firefox 144.

В мае началось обсуждение в OpenUI о добавлении новых команд для прокрутки. Затем последовал небольшой эксплейнер с описанием API. И на днях команда Chromium взялась за создание прототипа. Поэтому есть шансы, что это появится.

Дисклеймер: это обзор раннего предложения новых функций. Синтаксис может измениться в будущем или от функций могут отказаться.


Что предлагается: 8 новых команд для прокрутки Scroll Snap контейнеров, на базе которых работает CSS-карусель. 4 команды для каждого направления плюс логические эквиваленты:

- page-up
- page-down
- page-left
- page-right
- page-block-start
- page-block-end
- page-inline-start
- page-inline-end

Выглядеть это будет вот так:

<style>
.carousel__track {
display: flex;
overflow-x: auto;
width: 300px;
scroll-snap-type: x mandatory;
}

.carousel__slide {
min-width: 300px;
scroll-snap-align: center;
}
</style>

<section
class="carousel"
aria-labelledby="related"
aria-roledenoscription="carousel"
>
<h2 id="related">Смотрите также</h2>
<div class="carousel__controls">
<button
type="button"
command="page-inline-start"
commandfor="track"
>
Предыдущий
</button>
<button
type="button"
command="page-inline-end"
commandfor="track"
>
Следующий
</button>
</div>
<div class="carousel__track" id="track">
<div
class="carousel__slide"
role="group"
aria-roledenoscription="slide"
aria-label="1 из 6"
>
<!-- Слайд 1 -->
</div>
<div
class="carousel__slide"
role="group"
aria-roledenoscription="slide"
aria-label="2 из 6"
>
<!-- Слайд 2 -->
</div>
<!-- остальные слайды -->
</div>
</section>

Кнопки привязываются к карусели через commandfor. Нажатие приводит к вызову системной команды на карусели «прокрутить одну страницу» в зависимости от направления. В конечном итоге это приводит к прокрутке слайдов с учётом точек привязки.

Это делает кнопки полностью настраиваемыми: можно добавить классы, атрибуты, вложить иконки, поместить в контейнер и разместить в нужном месте. Сама карусель избавляется от псевдо-элементов-кнопок, хотя их всё ещё можно будет добавить.

Для начала функциональность ограничена прокруткой одной страницы по нужной оси. Но уже обсуждаются расширения: отключение при достижении крайних элементов, автоматическая привязка без commandfor, «scroll to top» и количество страниц.

Использование обычных кнопок в сочетании с Invoker Commands — это именно то, как изначально должны были быть реализованы встроенные в платформу карусели на мой взгляд. Это понятнее, удобнее и доступнее кнопок, создаваемых в CSS.

#html #css
13💩1👾1
You don't know HTML: <time> и <data>

В HTML есть два редких элемента: <time> и <data>. Первый ещё можно встретить в разметке, где думают о семантике. Второй практически не встретить. Оба элемента объединяет возможность добавления машиночитаемых данных.

<time> предназначен для разметки дат, времени, временных зон и длительностей в формате ISO 8601. Это международный стандарт, который могут распознавать различные системы. Вот несколько примеров:

<!-- дата с годом -->
<time>2025-11-14</time>

<!-- месяц и день без года -->
<time>11-14</time>

<!-- время без секунд -->
<time>17:03</time>

<!-- дата и время с секундами -->
<time>2025-11-14T17:03</time>

<!-- временная зона -->
<time>+0300</time>

<!-- длительность -->
<time>2h 13m 42s</time>


Стандарт HTML говорит, что текст внутри <time> должен соответствовать формату (по сути ISO 8601). Если это не так, то дата/время/длительность в нужном формате должна быть указана в атрибуте datetime. Это позволяет парсить данные.

<!-- валидная строка по ISO 8601 -->
<time>2025-11-14 17:03</time>

<!-- невалидная строка по ISO 8601,
но валидная указана в атрибуте -->
<time datetime="2025-11-14 17:03">
Четверг, 14 ноября, 17:03
</time>


Атрибут позволяет сохранить машиночитаемые данные для поисковых роботов, ИИ, и других потребителей, а пользователям показывать человекочитаемые данные или относительные значения «вчера», «5 минут назад», «на прошлой неделе».

У <data> похожая задача: показать человекочитаемые данные и предоставить машиночитаемую альтернативу. Разница в том, что <data> не привязан ни к какому стандарту и формату, поэтому принимает произвольные данные.

Человекочитаемый контент указывается внутри элемента, а машиночитаемая альтернатива в атрибуте value.

<!-- даты допустимы, но лучше
использовать <time> -->
<p>
Дата публикации:
<data value="2025-11-14">
Четверг, 14 ноября 2025
</data>
</p>

<!-- идентификатор, модель, артикул,
SKU, или url handle товара -->
<h2>
<data value="GA-B419SWGL">
Холодильник LG GA-B419SQGL
</data>
</h2>

<!-- код валюты в ISO 4217
и неформатированная цена -->
<p>
Price:
<data value="USD">$</date>
<data value="119999">1.199,99</data>
</p>
<!-- Price: $1.199,99 -->


Есть ведь универсальные data-* атрибуты, которые можно использовать для хранения машиночитаемых данных у любых элементов? Это так, но они семантически нейтральные, в отличие от datetime у <time> и value у <data>.

Добавление data-* атрибута не значит, что это машиночитаемые данные, которые связаны с контентом. Это просто произвольные данные. <time datetime> и <data value> обладают конкретной семантикой.

Может показаться, что элементы помогут программам чтения с экрана правильно обработать данные. Предположение логичное, но ложное. <time> и <data> — элементы со встроенной ролью generic, поэтому работают как <span>.

Возможно, со временем поддержка семантических элементов уровня текста во вспомогательных технологиях улучшится. Тогда <time> и <data> будут предоставлять дополнительный контекст.

А пока у <time> и <data> есть только теоретическая польза. Также они могут быть полезны для считывания и обработки в JS, как альтернатива data-* атрибутам. Для ИИ и парсеров контент тоже будет чуть более понятным.

#ydkhtml
👍6👌54👾4
Улучшение семантики карточки

Наткнулся в LinkedIn на пост с рецептом пропорционально масштабируемой карточки на основе единиц cqi и математики. Но меня зацепило не это, а разметка карточки:

<div class="card">
<div class="card-inner">
<img
class="card-img"
alt=""
src="..."
>
<div class="card-content">
<div class="card-noscript">
Добро пожаловать!
</div>
<div class="card-desc">
Это масштабируемая карточка.
</div>
<a
class="card-button"
href="..."
target="_blank"
>
Подписаться
</a>
</div>
</div>
</div>


<div> в <div>-е сидит и <div>-ом погоняет. Понятно, что разметка иллюстративная, а суть поста в CSS-технике. Иллюстративная ведь, да? Я не смог пройти мимо и решил сделать из этого пост. Итак, чиним семантику карточки.

Элемент .card может быть размечен как <article>, если карточка представляет независимый контент, который можно вырвать из контекста без потери смысла. Альтернатива — <div role="group">.

Возможен вариант с <section>, если карточка — большой тематический раздел. Если карточка находится в <li>, то <div> остаётся как обёртка компонента или сам <li> берёт на себя эту функцию.

Элемент .card-inner кажется лишним. Возможно, это требуется для стилей. Но выглядит так, что можно использовать родительский .card, а .card-inner можно удалить. Чем меньше узлов, тем лучше.

С .card-img всё ок. Пустой alt уместен для декоративных изображений, иначе нужно описание. Но можно лучше: добавить адаптивность за счёт атрибутов srcset и sizes, хотя-бы с x-дескриптором.

Можно добавить width и height для предотвращения сдвигов макета. Если карточка находится за пределами начальной области просмотра, loading="lazy" отложит загрузку картинки, а fetchpriority и decoding дадут больше контроля.

Элемент .card-content так же выглядит лишним. Расположить изображение и остальной контент можно с помощью flex или grid у .card, без лишних обёрток. В таких мелочах раздувается размер DOM.

.card-noscript надо заменить на заголовок, noscript в названии класса намекает. Скорее всего, <h3>, карточка обычно находится в разделе с <h2>. Но возможен вариант с <h2>, всё зависит от структуры.

Судя по классу и короткому тексту без дополнительной разметки, элемент .card-desc принимает обычный текст. Тут больше подойдёт элемент <p>. <div> уместен для rich-контента из WYSIWYG.

У ссылки .card-button не хватает информации об открытии страницы в новой вкладке для удобства пользователей вспомогательных технологий. Дополнительная иконка со стрелкой обеспечит визуальную подсказку.

Картинку и заголовок можно поменять местами в DOM и инвертировать порядок через CSS. Чтобы при быстром переходе к заголовку в скринридерах картинка не затерялась. Это имеет смысл для контентных картинок с alt-текстом.

К <article> можно привязать заголовок с помощью aria-labelledby для дополнительного контекста. Можно попробовать aria-roledenoscription для уточнения роли элемента. Но такие моменты желательно тестировать.

<article
class="card"
aria-labelledby="heading"
>
<h3
class="card-noscript"
id="heading"
>
Добро пожаловать!
</h3>
<img
class="card-img"
alt=""
src="..."
srcset="... 1x, ... 2x"
sizes="..."
width="380"
height="380"
loading="lazy"
decoding="async"
>
<p class="card-desc">
Это масштабируемая карточка.
</p>
<a
class="card-button"
href="..."
target="_blank"
>
Подписаться
<noscript
role="img"
aria-label="(откроется в новой вкладке)"
>
<!-- ... -->
</noscript>
</a>
</article>


Лучше? Лучше! Конечно, важно учитывать контекст. Где-то <article> будет не уместен, у изображения должно быть описание, а заголовок должен быть другого уровня. Также это моя версия разметки, кто-то может не согласиться.

В <div> нет ничего плохого. Но когда почти всё — <div>, вот это плохо. Плохо для SEO, вспомогательных технологий, расширений, парсеров, ИИ, специальных браузеров и режимов отображения веб-контента.

(Отредактировано, чтобы влезло в один пост)

#html
17👍10👾4🌚1
Использование ARIA для ИИ

Месяц назад OpenAI запустила свой собственный браузер ChatGPT Atlas. Он создан на базе платформы Chromium со встроенным ChatGPT и агентом, который может ходить по сайтам и выполнять там задачи пользователей.

Это не какая-то выдающаяся новость в современном мире ИИ-хайпа. Подобные браузеры уже есть, а в существующих идёт активное внедрение ИИ. В конце статьи-анонса Atlas есть раздел What's next с одним спорным моментом:

Website owners can also add ARIA tags to improve how ChatGPT agent works for their websites in Atlas.


OpenAI предлагает разработчикам добавлять ARIA-теги для улучшения работы агента с сайтами. Опустим момент, что они употребили слово «теги» вместо «атрибуты», что может говорить о некомпетентности в вопросах ARIA.

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

С одной стороны, это простимулирует интерес разработчиков к стандарту ARIA и доступности в целом. «Давайте добавим ARIA для доступности и ИИ» может стать хорошим аргументом для бизнеса, чтобы выделить ресурсы на внедрение.

При грамотной реализации глобально повысится уровень доступности сайтов. ARIA станет более популярным и появится новый тренд. Где тренд, там повышенная активность, в том числе авторов стандарта и специалистов по доступности.

С другой стороны, это может ухудшить состояние доступности. Разработчики, не знакомые с ARIA, пойдут внедрять «теги для ИИ» без понимания для чего это нужно и какое влияние оказывает. SEO-шник (AIO-шник) сказал, мы добавили.

Были уже времена alt-ов и пресных «SEO-текстов» со спамом «ключевиков» вместо реального описания изображений и полезного контента. С <meta name="keywords"> так наигрались, что поисковики перестали его учитывать.

Есть вопросы и к самой модели. Опыт генерации кода с помощью ИИ у меня и коллег показывает, что просьба делать доступно приводит к излишнему добавления ARIA, что противоречит первому правилу использования.

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

Сейчас доля Atlas на рынке крайне мала и вряд-ли в ближайшее время станет сколько-то большой. Поэтому вряд ли будет ажиотаж вокруг внедрение ARIA для улучшения ИИ в одном специфичном браузере с малой базой пользователей.

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

Не хотелось бы превращать ARIA в инструмент для маркетологов, тем самым извратив изначальную суть и назначение этого стандарта. В HTML хватает других способов расширения, которые могут быть использованы для Atlas.

#a11y
😢93🔥2👍1🌚1👾1
Новый элемент <amount>

В прошлый понедельник я писал про элементы <time> и <data>, основная суть которых — связать человекочитаемый контент в произвольной форме с машиночитаемым эквивалентом в строгой форме. У этих элементов есть ограничения.

<time> предназначен для разметки даты, времени и длительности и не может использоваться для других данных. <data> не привязан к типу данных, но в атрибуте value может быть записано только значение без единиц измерения.

Дисклеймер: это обзор раннего предложения новых функций. Синтаксис может измениться в будущем или от функций могут отказаться.


В Mozilla предлагают добавить новый элемент <amount> для разметки различных количественных значений в удобном для чтения представлении с учётом локализации. Дополнительно можно будет аннотировать данные.

Предлагается 3 атрибута:

- value — машиночитаемое значение, если контент не представлен в виде числового значения;
- unit — единица измерения в системе СИ, если значение представляет физическую величину;
- currency — код валюты по стандарту ISO 4217, если значение представляет цену.

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

Но если контент нельзя распознать как число или единица измерения представлена в формате, отличном от СИ, тогда нужно задействовать атрибуты и дать браузеру подсказки.

<!-- 42.2 валидное число -->
<amount>42.2</amount>

<!--
42,2 невалидное число из-за запятой
вместо точки, как принято в немецком языке
Атрибут value содержит валидное значение
-->
<amount value="42.2" lang="de">42,2</amount>

<!-- km валидная единица измерения в СИ -->
<amount>42.2km</amount>

<!--
Из-за запятой значение не валидно
Атрибут value содержит валидное число
Атрибут unit содержит валидную единицу измерения
-->
<amount value="42.2" unit="km" lang="de">
42,2km
</amount>

<!--
Цена отформатирована в удобном виде с $ и запятой
Атрибут currency содержит международный код валюты
Атрибут value содержит валидное числовое значение
-->
<amount currency="USD" value="64000">
$64,000
</amount>


Автор ссылается на проведённое в 2000-х годах исследование о форматах измерений физических величин от сообщества микроформатов. Оно привело к предложению формата hmeasure, которое в 2010-х годах преобразовано в h-measure.

На сайте The Web We Want, где разработчики могут оставлять свои пожелания новых функций, а другие могут голосовать за них, есть соответствующее пожелание и связанная дискуссия в github-репозитории.

Браузеры смогут использовать мета-данные элемента <amount>, чтобы предложить пользователям отображение значения в формате, принятом в том или ином языке и привычном для пользователя. Это включает разделители целых и дробных частей чисел.

Также затрагивается тема доступности <time> и <data>, которые не оказывают эффекта на вспомогательные технологии, о чём я тоже упоминал. По всей видимости, <amount> должен лучше интегрироваться с вспомогательными технологиями.

В целом предложение логичное и полезное. В eCommerce, где много цен c валютами, размерных сеток, габаритов, объёмов и разных количественных значений, элемент <amount> был бы крайне полезен.

#html
👍123🎉3🌚2
Запись круглого стола «Будущее фронтенда: хватит ли веб-платформы?»

Доступна запись круглого стола «Будущее фронтенда: хватит ли веб-платформы?», в котором я участвовал на конференции Frontendconf 2025. Обсуждали, насколько веб-платформа готова предоставить базовые UI-примитивы.

Я выступал сторонником веб-платформы, говорил об инициативах Interop, OpenUI, CSS Form Control Styling, Global Design System и других. В рамках этих инициатив идёт работа по улучшению стандартных HTML-элементов и добавлению новых.

Моя мысль в том, что скорость внедрения и объём функций увеличились, поэтому через несколько лет браузеры смогут предоставить всё необходимое для создания UI Kit. Я с энтузиазмом слежу за процессом и делюсь тут новостями.

Идея кажется амбициозной и утопической, но я настроен оптимистично, рано или поздно мы к этому придём. Лёгкие UI Kit на основе стандартных элементов, API и веб-компонентов заменят специфичные UI-компоненты фреймворков.

Речь не о полной замене. Фреймворки останутся и будут управлять данными, отображением и логикой интерфейса. Заменится слой примитивов пользовательского интерфейса, из которых всё строится: выпадающие списки, вкладки, диалоги, карусели и так далее.

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

#ui
8👍1👾1
Доступны ли фильтры из радио-кнопок?

Представьте интерфейс: сетка с карточками статей блога, над которой горизонтальный список тегов. Нажатие по тегу меняет текущий состав статей в сетке. Всё происходит на одной странице без перезагрузки.

Теги реализованы как радио-кнопки, потому что логика подразумевает один активный тег. Если бы активных тегов могло быть несколько, вместо радио-кнопок были бы флажки. Разметка для описанного интерфейса выглядела бы так:

<form>
<fieldset>
<legend>
Теги
</legend>
<label for="tag-1">
<input
type="radio"
name="tag"
value="tag-1"
>
Тег 1
</label>
<label for="tag-2">
<input
type="radio"
name="tag"
value="tag-2"
>
Тег 2
</label>
<!-- остальные теги -->
</fieldset>
</form>
<ul>
<!-- список статей -->
</ul>


Изменение списка происходит динамически при выборе тега с помощью JS. Это довольно распространённый подход. Возникает вопрос: доступен ли такой интерфейс и поведение? А именно изменение списка при выборе радио-кнопки тега.

Критерий 3.2.2 On Input гласит: изменение настроек любого компонента пользовательского интерфейса не должно автоматически приводить к изменению контекста, если пользователь заранее не предупреждён об этом.

Радио-кнопка по определению считается компонентом пользовательского интерфейса. Настройки — это различные свойства и состояния, которые меняются при взаимодействии. У радио-кнопки это состояние «отмечено»/«не отмечено».

Изменение контекста — это существенное изменение в пользовательском агенте (браузере), области просмотра, фокусе или содержимом страницы, которое меняет её смысл. Это может дезориентировать пользователей, если они этого не ожидают.

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

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

Как это исправить? WCAG предлагает два способа:

- Добавить кнопку отправки формы;
- Добавить предупреждение перед компонентом.

Отправка формы — ожидаемое действие, которое изменяет контекст. Добавление кнопки отправки решает проблему. При этом не обязательно делать традиционную отправку с перезагрузкой страницы, по-прежнему можно менять список через JS.

Это надёжно и понятно для пользователей: выбор опции и подтверждение выбора. Есть преимущество не только для доступности. Случайный выбор не того тега не приведёт к выполнению JS-логики изменения списка статей и потенциальному сетевому запросу.

Но кнопка отправки — это дополнительный элемент и действие, которое пользователю нужно совершить. Это менее удобно с точки зрения UX. Убрать дополнительный шаг можно путём замены радио-кнопок на несколько кнопок отправки.

<fieldset>
<legend>
Теги
</legend>
<button
type="submit"
name="tag"
value="tag-1"
>
Тег 1
</button>
<button
type="submit"
name="tag"
value="tag-2"
>
Тег 2
</button>
<!-- остальные теги -->
</fieldset>


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

<fieldset>
<legend>
Теги
</legend>
<p>
При выборе тега список статей
динамически изменяется.
</p>
<!-- теги -->
</fieldset>



Можно добавить переключатель, который активирует режим изменения списка статей при выборе радио-кнопки. Это одновременно будет работать как предупреждение и как способ отключить нежелательное поведение.


Всё это релевантно и для реализаций с группой кнопок-переключателей (с состоянием «нажата»/«не нажата») вместо радио-кнопок или флажков. Помимо фильтра тегов статей. проблема актуальна для товарных фильтров в интернет-магазинах.

#a11y
14👍3👾3
Переосмысление контейнера v2

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

Я предложил решение, которое основано на динамическом вычислении полей на основе функции calc() и единиц vw. Решение рабочее, но не очень интуитивное из-за формулы и расчёта --vw для решения проблемы с полосой прокрутки.

Для понимания цели рекомендую ознакомиться с соответствующими постами. А я поделюсь альтернативным решением, которое мне пришло в голову. Оно даёт то же результат, но реализовано с помощью grid и более понятное на мой взгляд.

<style>
.container {
--container-max-width: 1440px;
--container-min-field: 16px;
display: grid;
grid-template-columns:
minmax(var(--container-min-field), 1fr)
minmax(0, var(--container-max-width))
minmax(var(--container-min-field), 1fr)
;
}

.container > * {
grid-column: 2 / 3;
}

.section {
background-color: var(--section-bg-color);
}
</style>
<section class="section container">
<!-- контент -->
</section>


В контейнере создаётся сетка из трёх колонок. Центральная колонка предназначена для контента. Её максимальная ширина ограничена значением 1440px в функции minmax(). Эта колонка будет растягиваться и сжиматься в зависимости от ширины экрана.

Крайние колонки выполняют роль полей. Их минимальная ширина составляет 16px. Когда центральная колонка достигнет максимальной ширины, крайние колонки будут равномерно растягиваться на доступное пространство.

Правило с селектором .container > * нужно, чтобы любые дочерние элементы контейнера попадали в центральную колонку для контента. Потому что по умолчанию элементы последовательно распределяются по доступным колонкам.

Минус такого решения в том, что внутри контейнера нельзя будет определить другую сетку, например классическую 12-колоночную для контента, так как сетка уже определена. Понадобится отдельный вложенный элемент для сетки.

#css
👍6🤔31