Abusing Customizable Select
Патрик Броссет экспериментирует с новым стилизуемым <select> и некоторыми другими возможностями CSS. В статье он показывает 3 демо и описывает шаги и методы их реализации:
- Изогнутое меню выбора директории;
- Выбор карты из колоды в виде веера;
- Круговой выбор эмодзи.
Для каждого демо есть видео (на случай если браузер не поддерживает стилизуемый
#html #css #ui
Патрик Броссет экспериментирует с новым стилизуемым <select> и некоторыми другими возможностями CSS. В статье он показывает 3 демо и описывает шаги и методы их реализации:
- Изогнутое меню выбора директории;
- Выбор карты из колоды в виде веера;
- Круговой выбор эмодзи.
Для каждого демо есть видео (на случай если браузер не поддерживает стилизуемый
<select>), а также Codepen с полной реализацией. Все демо по своему интересные и демонстрируют возможности нового API.#html #css #ui
CSS-Tricks
Abusing Customizable Selects | CSS-Tricks
Let’s go over a few demos using the new customizable <select> feature that may be wild, but also give us a great chance to learn new things in CSS.
👍9❤2🔥1🌚1🤗1
Проблемы карточек, обёрнутых в ссылки
Карточки, обёрнутые в
Первая проблема заключается в доступности. Весь текстовый контент внутри ссылки будет использован для генерации имени для вспомогательных технологий. Для карточки из примера выше итоговым именем будет:
Слишком длинное и избыточное имя. Оно будет озвучиваться программами чтения с экрана, отображаться в списке ссылок и на дисплее Брайля, его нужно будет частично или полностью произнести при голосовом управлении интерфейсом.
Быстро исправить проблему можно путём добавления на ссылку атрибута
При использовании
Вторая проблема — дополнительные интерактивные элементы в карточке: тэги, кнопки галереи, переключатели параметров, кнопка добавления в корзину и так далее. Предположим, что в примере бренд — это ссылка на другую страницу:
Тогда получается интерактивный элемент внутри интерактивного элемента, что недопустимо по стандарту. Это приводит к неожиданному для пользователя поведению при нажатии и ухудшает опыт взаимодействия с карточкой.
Исправить первую и вторую проблемы, соблюдая при этом первое правило ARIA, можно перенеся ссылку внутрь заголовка и растянув её псевдо-элемент на всю карточку. Нажатие по псевдо-элементу делегирует нажатие на элемент.
Чтобы псевдо-элемент не перекрывал другие интерактивные элементы карточки, препятствуя взаимодействию с помощью мыши и касаний, их нужно приподнять на слой выше. Также побочный эффект — невозможность выделения текста.
Получается несколько более сложная реализация карточки за счёт псевдо-элемента и слоёв. Зато она более надёжная с точки зрения новых интерактивных элементов и удобная с точки зрения вспомогательных технологий.
Более подробно эту технику описывает Хейдон Пикеринг в онлайн книге Inclusive Components. Также тему проблем с карточками затрагивает Адриан Розелли в своей статье Block Links, Cards, Clickable Regions, Rows, Etc.
В будущем, надеюсь, решать эту задачу можно будет с помощью Link Area Delegation API, разрабатываемого в рамках OpenUI. Это механизм делегации кликов с других элементов на ссылку, навеянный проблемами с карточками.
#html #css #a11y
Карточки, обёрнутые в
<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…
2❤22👍2🌚1💯1🆒1
CSS Web Components
Будучи набором браузерных API, веб-компоненты можно использовать самыми разными способами. Некоторые из таких способов применения получили в сообществе собственные названия. Один из них — CSS Web Components.
Суть подхода в том, чтобы взять от Custom Elements API минимум возможностей: никаких Shadow DOM, классов, методов жизненного цикла и JS в целом. Только пользовательские HTML-элементы с атрибутами в разметке и стили.
Любой несуществующий в HTML элемент обрабатывается браузером как
Но
Кроме того, у пользовательских элементов можно задавать произвольные атрибуты без префикса
У пользовательских элементов по умолчанию встроенная роль
Всё это в совокупности можно использовать как альтернативу
Эти элементы стилизуются по имени, или классу, если так привычнее. Атрибуты используются для стилизации различных состояний. С обновлённой функцией
Примеров можно придумать много. Кто-то делится своими идеями и практическими примерами. А кто-то даже свою CSS-методологию построил на основе CSS Web Components. Всё это в целом выглядит непривычно, но интересно.
- You can make up HTML tags
- Replace Divs With Custom Elements For Superior Markup
- Custom Element Examples (Without Javascript)
- Responsive Columns: Build Amazing Layouts With Custom HTML Tags
- TAC: A new CSS methodology
- 3 Examples of the TAC Methodology In Action
Таким образом CSS Web Components — это подход к вёрстке, когда
#html #css
Будучи набором браузерных API, веб-компоненты можно использовать самыми разными способами. Некоторые из таких способов применения получили в сообществе собственные названия. Один из них — CSS Web Components.
Суть подхода в том, чтобы взять от Custom Elements API минимум возможностей: никаких Shadow DOM, классов, методов жизненного цикла и JS в целом. Только пользовательские HTML-элементы с атрибутами в разметке и стили.
Любой несуществующий в HTML элемент обрабатывается браузером как
HTMLUnknownElement. Он отображается, попадает в DOM со всеми дочерними узлами, доступен через DOM API, стилизуется с помощью CSS.Но
HTMLUnknownElement не валиден с точки зрения стандарта. Валидатор покажет ошибку и проигнорирует неизвестный элемент и всё его поддерево. В свою очередь пользовательские элементы — это валидные экземпляры HTMLElement.<!--
HTMLUnknownElement, не валиден
-->
<sidebarpanel>
<!-- ... -->
</sidebarpanel>
<!--
HTMLElement, валиден
-->
<sidebar-panel>
<!-- ... -->
</sidebar-panel>
Кроме того, у пользовательских элементов можно задавать произвольные атрибуты без префикса
data-*, если они не пересекаются со стандартными глобальными атрибутами, например id, class, translate, lang и так далее.У пользовательских элементов по умолчанию встроенная роль
generic, как у <div> или <span>. Поэтому они сами по себе ничего не значат и не ломают семантику. Если нужно, можно указать глобальные aria-* атрибуты и role.Всё это в совокупности можно использовать как альтернативу
<div> и <span> для более осмысленного именования контейнеров, обёрток и компонентов, для которых нет подходящих семантических HTML-элементов.<!--
Контейнер для ограничения
ширины контента и центровки
-->
<page-container width="lg">
<!-- ... -->
</page-container>
<!--
Контейнер для отображения
изображений в виде сетки
-->
<image-grid col="3" gap="sm">
<!-- ... -->
</image-grid>
<!--
Компонент бейджа
-->
<ui-badge variant="primary">
<!-- ... -->
</ui-badge>
Эти элементы стилизуются по имени, или классу, если так привычнее. Атрибуты используются для стилизации различных состояний. С обновлённой функцией
attr() из атрибутов можно извлекать значения и подставлять в свойства.Примеров можно придумать много. Кто-то делится своими идеями и практическими примерами. А кто-то даже свою CSS-методологию построил на основе CSS Web Components. Всё это в целом выглядит непривычно, но интересно.
- You can make up HTML tags
- Replace Divs With Custom Elements For Superior Markup
- Custom Element Examples (Without Javascript)
- Responsive Columns: Build Amazing Layouts With Custom HTML Tags
- TAC: A new CSS methodology
- 3 Examples of the TAC Methodology In Action
Таким образом CSS Web Components — это подход к вёрстке, когда
<div> и <span> заменяются на незарегистрированные (без определения в JS) пользовательские элементы с осмысленными названиями, атрибутами и соответствующими стилями.#html #css
Hawkticehurst
CSS Web Components for marketing sites
The truly No JavaScript web component.
👍6🔥6❤1🤩1🌚1🙈1
Диалог, форма, список выбора и нарушения WCAG
При аудите столкнулся с компонентом переключения страны/валюты в магазине. Он использует кнопку раскрытия диалога, в котором находится форма со списком выбора (listbox), внутри которого опции в виде кнопок отправки формы:
Достаточно маленький компонент, а нарушает сразу несколько критериев WCAG:
- 2.1.1 Keyboard — в списке выбора не реализована соответствующая механика клавиатурного взаимодействия. Фокус должен попадать на
- 2.5.3 Label in Name — имя кнопки раскрытия диалога не совпадает с видимой подписью. Видимая подпись — «United States (USD $)», а программно заданное с помощью атрибута
- 3.2.2 On Input — выбор опции из списка приводит к отправке формы и смене контекста. Из-за реализации опций на основе кнопок с атрибутом
- 3.3.2 Labels or Instructions — нет видимой подписи у кнопки раскрытия диалога и поля выбора. В первом случае подпись задана через
- 4.1.2 Name, Role, Value — у поля выбора нет ассоциированного имени. У
Правильнее было бы не делать внутри формы список выбора и оставить обычный список кнопок отправки формы. Такие кнопки явно дают понять, что ожидается смена контекста. Исправленная версия разметки:
Убран
На мой взгляд такая реализация работает лучше, не требует более сложного виджета списка выбора со своей механикой клавиатурного взаимодействия, более понятна пользователям и не нарушает упомянутые критерии WCAG.
#html #ui #a11y
При аудите столкнулся с компонентом переключения страны/валюты в магазине. Он использует кнопку раскрытия диалога, в котором находится форма со списком выбора (listbox), внутри которого опции в виде кнопок отправки формы:
<button
type="button"
aria-expanded="false"
aria-haspopup="dialog"
aria-controls="popover-id"
aria-label="Change country or currency"
>
United States (USD $)
</button>
<x-popover
role="dialog"
id="popover-id"
>
<form action="...">
<!-- скрытые поля -->
<x-listbox
role="listbox"
aria-activedescendant="option-us"
>
<!-- другие опции -->
<button
type="submit"
id="option-gb"
name="country_code"
value="GB"
role="option"
>
United Kingdom (GBP £)
</button>
<button
type="submit"
id="option-us"
name="country_code"
value="US"
role="option"
aria-selected="true"
>
United States (USD $)
</button>
<!-- другие опции -->
</x-listbox>
</form>
</x-popover>
Достаточно маленький компонент, а нарушает сразу несколько критериев WCAG:
- 2.1.1 Keyboard — в списке выбора не реализована соответствующая механика клавиатурного взаимодействия. Фокус должен попадать на
listbox, опции переключаться стрелками/Home/End с фокусом через aria-activedescendant;- 2.5.3 Label in Name — имя кнопки раскрытия диалога не совпадает с видимой подписью. Видимая подпись — «United States (USD $)», а программно заданное с помощью атрибута
aria-label имя — «Change country or currency»;- 3.2.2 On Input — выбор опции из списка приводит к отправке формы и смене контекста. Из-за реализации опций на основе кнопок с атрибутом
type="submit" при нажатии происходит отправка формы с перезагрузкой страницы;- 3.3.2 Labels or Instructions — нет видимой подписи у кнопки раскрытия диалога и поля выбора. В первом случае подпись задана через
aria-label, а во втором случае не задана ни видимым элементом, ни программно;- 4.1.2 Name, Role, Value — у поля выбора нет ассоциированного имени. У
listbox должно быть имя, которое задано одним из способов. В данном случае имя не задано ни одним из способов.Правильнее было бы не делать внутри формы список выбора и оставить обычный список кнопок отправки формы. Такие кнопки явно дают понять, что ожидается смена контекста. Исправленная версия разметки:
<p id="label-id">
Change country or currency
</p>
<button
type="button"
id="button-id"
aria-expanded="false"
aria-haspopup="dialog"
aria-controls="popover-id"
aria-labelledby="label-id button-id"
>
United States (USD $)
</button>
<x-popover
role="dialog"
id="popover-id"
>
<form action="...">
<!-- скрытые поля -->
<ul>
<!-- другие кнопки -->
<li>
<button
type="submit"
name="country_code"
value="GB"
>
United Kingdom (GBP £)
</button>
</li>
<li>
<button
type="submit"
name="country_code"
value="US"
aria-current="true"
autofocus
>
United States (USD $)
</button>
</li>
<!-- другие кнопки -->
</ul>
</form>
</x-popover>
Убран
listbox, кнопка раскрытия получила видимую подпись и составное имя с помощью aria-labelledby, добавлен обычный список, убрана семантика option и состояние aria-selected, вместо него теперь aria-current, добавлен autofocus для установки фокуса при открытии диалога.На мой взгляд такая реализация работает лучше, не требует более сложного виджета списка выбора со своей механикой клавиатурного взаимодействия, более понятна пользователям и не нарушает упомянутые критерии WCAG.
#html #ui #a11y
❤8👍5🤩2🌚1
Крис Койер про область видимости в CSS
Посмотрел запись доклада Криса Койера про область видимости в CSS с прошедшей в июне 2025 года конференции CSS Day. Другие доклады доступны в плейлисте на YouTube-канале конференции. Теперь пару мыслей о докладе.
Крис начал с того, что написание CSS, по сути, сводится к определению области видимости и заданию стилей для этой области. Селекторы — это область видимости. Вроде базовая мысль, но я с такой точки зрения об этом не думал.
Далее затрагиваются инструменты для автоматического ограничения области видимости через генерацию уникальных классов. Один из таких — CSS Modules. Интересно то, что это набор правил, а не конкретная реализация.
Прозвучала мысль, что если к синтаксису языка добавляется что-то нестандартное, то не стоит использовать расширение
Обычно CSS Modules импортируются с помощью псевдо-импортов, которые не работают в JS и обрабатываются сборщиком. Раз так, то расширение файла могло быть, например,
Вместо этого повсеместно используется конвенция
Далее упоминается подход CSS-in-JS, который помимо прочего обеспечивает область видимости через уникальные классы. По мнению Криса этот подход скорее CSS-in-React, поскольку тот не предлагает решения для стилей.
Это породило множество разных реализаций: styled-components, emotion, jss, и так далее. Они ничего особого не привнесли сообществу в глобальном смысле. Сегодня подход runtime CSS-in-JS пришёл в тупик и изжил себя.
Далее Tailwind, который обеспечивает область видимости иным способом. Не уникальными классами, а набором точечных классов, применяемых к конкретному элементу. Как и для Криса, это не для меня, но имеет место быть.
Отдельного говорится про хардкорные способы области видимости в веб-платформе:
В базовом виде
#css
Посмотрел запись доклада Криса Койера про область видимости в CSS с прошедшей в июне 2025 года конференции CSS Day. Другие доклады доступны в плейлисте на YouTube-канале конференции. Теперь пару мыслей о докладе.
Крис начал с того, что написание CSS, по сути, сводится к определению области видимости и заданию стилей для этой области. Селекторы — это область видимости. Вроде базовая мысль, но я с такой точки зрения об этом не думал.
Далее затрагиваются инструменты для автоматического ограничения области видимости через генерацию уникальных классов. Один из таких — CSS Modules. Интересно то, что это набор правил, а не конкретная реализация.
Прозвучала мысль, что если к синтаксису языка добавляется что-то нестандартное, то не стоит использовать расширение
.css и мимикрировать под обычный CSS. А CSS Modules добавляют нестандартные composes, :local и :global.Обычно CSS Modules импортируются с помощью псевдо-импортов, которые не работают в JS и обрабатываются сборщиком. Раз так, то расширение файла могло быть, например,
.mcss или .cssm, чтобы подчеркнуть отличие от CSS.Вместо этого повсеместно используется конвенция
.module.css в названии файла. Возможно, стандартное расширение .css выбрано ради совместимости с IDE, редакторами, песочницами и плагинами подсветки синтаксиса.Далее упоминается подход CSS-in-JS, который помимо прочего обеспечивает область видимости через уникальные классы. По мнению Криса этот подход скорее CSS-in-React, поскольку тот не предлагает решения для стилей.
Это породило множество разных реализаций: styled-components, emotion, jss, и так далее. Они ничего особого не привнесли сообществу в глобальном смысле. Сегодня подход runtime CSS-in-JS пришёл в тупик и изжил себя.
Далее Tailwind, который обеспечивает область видимости иным способом. Не уникальными классами, а набором точечных классов, применяемых к конкретному элементу. Как и для Криса, это не для меня, но имеет место быть.
Отдельного говорится про хардкорные способы области видимости в веб-платформе:
<iframe> (<object> сюда же) и Shadow DOM. Оба с нюансами и не очень удобные. В конце @scope — новый способ управления областью видимости.В базовом виде
@scope как-будто не имеет особого смысла. Того же эффекта можно добиться сейчас без него. Интересны более продвинутые техники, названные «Donut Scope», «DOM Blasters» и «Proximity». Это уже тема для отдельного поста.#css
YouTube
Scope in CSS - Chris Coyier - CSS Day 2025
I hate to be the one to tell you but writing CSS is half thinking about scope. You don’t always need them, but there are plenty of tools out there that help with scoping one way or another. They are worth considering as they help with a variety of problems…
👍10❤3🌚1😈1
«Donut Scope», «Proximity» и «DOM Blasters»
По горячим следам предыдущего поста про область видимости делаю продолжение. Речь пойдёт о директиве
В базовом виде
Стили
«Donut Scope» позволяет ограничить область видимости «сверху» и «снизу». Так можно создавать «дыры» внутри элементов, в которых селекторы не будут действовать. Полезно для ограничения глобальных стилей в сбросах.
«Proximity» — часть механизма каскада, которая вводит понятие близости элемента к области видимости. При прочих равных применяется тот селектор, который будет ближе к области видимости в DOM-дереве.
С обычной вложенностью ссылка внутри
«DOM Blasters» — так Крис Койер назвал применение
Ссылка внутри первого
Крис в своём докладе предложил смелую идею: а что, если все стили компонентов писать таким образом, используя
Крис провёл замеры на 10000 карточек со стилями в отдельном файле и в
Такой бенчмарк не отражает реальность. 10000 одинаковых карточек действительно хорошо сжимаются, а браузер может применять оптимизации. Нужно проверять на реальном проекте с разными стилями компонентов.
#css
По горячим следам предыдущего поста про область видимости делаю продолжение. Речь пойдёт о директиве
@scope в целом и подходах «Donut Scope», «Proximity» и «DOM Blasters» (названы так в докладе Криса Койера) в частности.@scope — новая директива CSS, доступная на данный момент во всех актуальных версиях основных браузеров (получила статус Baseline Newly Available в декабре 2025). Директива предназначена для управления областью видимости.В базовом виде
@scope создаёт область видимости, за пределы которой селекторы не действуют. Можно писать более простые селекторы и использовать селекторы типа без риска что-то задеть:@scope (.card) {
:scope {
...
}
.title {
...
}
button {
...
}
}Стили
.title и button применятся внутри .card и не повлияют на элементы вне .card. Селектор :scope позволяет стилизовать корневой элемент области видимости, в данном случае .card. В таком виде это не особо полезно и возможно без @scope:/* Вложенность даёт тот же эффект */
.card {
...
.title {
...
}
button {
...
}
}
«Donut Scope» позволяет ограничить область видимости «сверху» и «снизу». Так можно создавать «дыры» внутри элементов, в которых селекторы не будут действовать. Полезно для ограничения глобальных стилей в сбросах.
<style>
@scope (body) to (.content) {
a {
text-decoration: none;
}
}
</style>
<nav>
<a href="...">
Ссылка в меню без подчёркивания
</a>
...
<nav>
<div class="content">
...
<a href="...">
Ссылка в тексте с подчёркиванием
</a>
...
</div>
«Proximity» — часть механизма каскада, которая вводит понятие близости элемента к области видимости. При прочих равных применяется тот селектор, который будет ближе к области видимости в DOM-дереве.
<style>
@scope (.light-theme) {
a {
color: blue;
}
}
@scope (.dark-theme) {
a {
color: lightblue;
}
}
</style>
<div class="dark-theme">
<a href="...">Ссылка</a>
<div class="light-theme">
<a href="...">Ссылка</a>
</div>
</div>
С обычной вложенностью ссылка внутри
light-theme будет светло-синей, потому что применяется последнее по порядку правило. Со @scope ссылка внутри light-theme будет синей, потому что это правило ближе к области видимости light-theme.«DOM Blasters» — так Крис Койер назвал применение
@scope внутри <style> без указания селектора в скобках. Это режим, в котором ближайший родительский для <style> элемент используется как область видимости.<div>
<a href="...">Ссылка</a>
</div>
<div>
<a href="...">Ссылка</a>
<style>
@scope {
a {
color: red;
}
}
</style>
</div>
Ссылка внутри первого
<div> будет отображаться как обычно. Ссылка во втором <div> будет красной. Потому что второй <div> — ближайший родительский элемент для <style>, и в этом <style> используется @scope.Крис в своём докладе предложил смелую идею: а что, если все стили компонентов писать таким образом, используя
<style> и @scope? Пока непонятны преимущества и недостатки такого подхода в реальных условиях, но это идея уровня «что, если».Крис провёл замеры на 10000 карточек со стилями в отдельном файле и в
<style> для каждой карточки. По количеству памяти и времени отрисовки разницы не было. Увеличился размер HTML, но gzip хорошо справился.Такой бенчмарк не отражает реальность. 10000 одинаковых карточек действительно хорошо сжимаются, а браузер может применять оптимизации. Нужно проверять на реальном проекте с разными стилями компонентов.
#css
10🔥7😱3❤1🌚1
Роли presentation, none, generic и aria-hidden
В ARIA есть несколько сущностей, которые могут вызвать путаницу:
- роль
- роль
- роль
- состояние
Роль
Разработчики не должны задавать атрибут
Роль
В примере виджет
Нет смысла использовать
Для элементов с важной семантикой использование
Роль
Итого:
-
-
-
#a11y
В ARIA есть несколько сущностей, которые могут вызвать путаницу:
- роль
generic;- роль
none;- роль
presentation;- состояние
aria-hidden="true". Роль
generic обозначает общие элементы-контейнеры, которые сами по себе ничего не обозначают. В HTML эта роль встроена в такие элементы, как <div>, <span> и некоторые другие. Она не предназначена для использования в коде.Разработчики не должны задавать атрибут
role="generic" каким-либо элементам. Это внутренняя роль для браузера. Вспомогательные технологии не реагируют на generic элементы и работают с их контентом.Роль
presentation обозначает элемент, который выполняет чисто декоративную функцию и не несёт смысла. presentation похожа на generic и делает элемент безымянным контейнером без смысловой нагрузки.presentation можно задавать элементам в отличие от generic. В ряде случаев нужно сбросить семантику, но сохранить элемент с контентом в дереве доступности. Случаи, в основном, связаны со структурой виджетов.<ul role="menubar">
<li role="presentation">
<button
type="button"
role="menuitem"
>
...
</button>
</li>
<li role="presentation">
<button
type="button"
role="menuitem"
>
...
</button>
</li>
...
</ul>
В примере виджет
menubar создан на основе <ul> и <li>. Семантика list переопределена на menubar. Семантика <li> остаётся listitem. Внутри menubar допустимы только элементы menuitem, поэтому listitem сбрасывается.Нет смысла использовать
presentation на элементах, которые по умолчанию не несут смысла: <div>, <span>, <section> без имени, <img alt="">, <a> без href, пользовательских элементах и других со встроенной ролью generic.Для элементов с важной семантикой использование
presentation сбрасывает эту семантику. <h1 role="presentation"> потеряет семантику heading и будет как <span>. Сбрасывать стандартную семантику не рекомендуется.Роль
none — это синоним presentation, то есть они работают одинаково. none была добавлена в ARIA 1.1, чтобы уменьшить путаницу. К тому же none короче по количеству символов и выглядит более понятно.aria-hidden — это состояние, которое можно применять к любым элементам. Значение true исключает элемент и все его дочерние узлы из дерева доступности, как-будто их нет. При этом визуально все элементы сохраняются.presentation и none удаляют семантику, элемент по-прежнему остаётся в дереве доступности как незначимый и семантика всех дочерних элементов сохраняется. aria-hidden="true" полностью исключает элемент из дерева доступности.Итого:
-
generic — незначимый контейнер, системная роль для браузера, нельзя использовать в коде;-
presentation и none — незначимый контейнер, можно использовать в коде, но, скорее всего, не пригодится;-
aria-hidden="true" — элемент и его поддерево скрыто от вспомогательных технологий, но отображается визуально.#a11y
👍14❤4🌚1👀1
Резервное значение font-family
Гарри Робертс написал статью о том, что резервное значение свойства
Обычно базовый шрифт задаётся через
Все заголовки должны отображаться как Playfair Display. Это пользовательский шрифт, который должен загрузиться и обработаться браузером. Это происходит не мгновенно, при отрисовке страницы шрифт может быть ещё не готов.
В этом случае браузер будет использовать резервный шрифт, что зависит от свойства
Кажется, что резервным шрифтом должен быть
В большинстве случаев это шрифт Times New Roman. Чтобы избежать его появления, стоит указать резервные варианты, если шрифт переопределяется. А лучше один раз задать пользовательские свойства и использовать их.
#css #performance
Гарри Робертс написал статью о том, что резервное значение свойства
font-family работает не так, как нам кажется. Поэтому при загрузке страницы на какое-то время текст может отображаться шрифтом Times New Roman, что ухудшает CLS.Обычно базовый шрифт задаётся через
:root, html или body для наследования вглубь дерева. Для заголовков часто шрифт переопределяется на акцентный из шрифтовой пары. Шрифт применяется к заголовкам с использованием селекторов типа или класса: h1, .h1, .heading.:root {
font-family: Inter, system-ui, sans-serif;
}
h1, h2, h3, h4, h5, h6, .heading {
font-family: 'Playfair Display';
}Все заголовки должны отображаться как Playfair Display. Это пользовательский шрифт, который должен загрузиться и обработаться браузером. Это происходит не мгновенно, при отрисовке страницы шрифт может быть ещё не готов.
В этом случае браузер будет использовать резервный шрифт, что зависит от свойства
font-display. Чаще всего используется значение swap, при котором резервный шрифт заменяется пользовательским как только тот загрузится.Кажется, что резервным шрифтом должен быть
system-ui и sans-serif. Но это не так. Резервный шрифт берётся из font-family самого заголовка. Указан только Playfair Display, поэтому резервным будет системный шрифт для этого элемента.В большинстве случаев это шрифт Times New Roman. Чтобы избежать его появления, стоит указать резервные варианты, если шрифт переопределяется. А лучше один раз задать пользовательские свойства и использовать их.
:root {
--font-base: Inter, system-ui, sans-serif;
--font-headings: 'Playfair Display', serif;
font-family: var(--font-base);
}
h1, h2, h3, h4, h5, h6, .heading {
font-family: var(--font-headings);
}#css #performance
Csswizardry
font-family Doesn’t Fall Back the Way You Think – CSS Wizardry
A quick but important reminder that font-family declarations don’t inherit fallback stacks the way many developers assume.
🔥10❤6💯3🌚1😎1
Нужен ли visually-hidden?
Шаблон
Этот шаблон часто применяется для добавления «скрытых» заголовков, подписей к полям и инструкций, которые нужны для улучшения доступности, но не предусмотрены дизайном. Дэвид Бушелл рассказывает историю этого шаблона.
Несмотря на распространённость и удобство, многие специалисты по доступности сходятся во мнении: это хак — следствие проблем при проектировании интерфейсов. Наличие в кодовой базе
Дизайнеры выступают за минимализм, экономию пространства и компактность. В итоге некоторые разделы остаются без заголовков, поля ввода — без подписей и инструкций, текстовые подписи кнопок заменяются иконками.
Опытные разработчики понимают, что это не правильно и добавляют визуально скрытые заголовки, подписи и инструкции, чтобы исправить проблемы без изменения внешнего вида. Хотя правильнее было бы исправить это на уровне дизайна.
В идеальной ситуации те элементы, что скрыты с помощью
#a11y
Шаблон
visually-hidden хорошо знаком специалистам по доступности. Это класс со свойствами для визуального сокрытия элемента с сохранением его в дереве доступности. Во многих библиотеках есть компонент <VisuallyHidden>.Этот шаблон часто применяется для добавления «скрытых» заголовков, подписей к полям и инструкций, которые нужны для улучшения доступности, но не предусмотрены дизайном. Дэвид Бушелл рассказывает историю этого шаблона.
Несмотря на распространённость и удобство, многие специалисты по доступности сходятся во мнении: это хак — следствие проблем при проектировании интерфейсов. Наличие в кодовой базе
visually-hidden считается антипаттерном.Дизайнеры выступают за минимализм, экономию пространства и компактность. В итоге некоторые разделы остаются без заголовков, поля ввода — без подписей и инструкций, текстовые подписи кнопок заменяются иконками.
Опытные разработчики понимают, что это не правильно и добавляют визуально скрытые заголовки, подписи и инструкции, чтобы исправить проблемы без изменения внешнего вида. Хотя правильнее было бы исправить это на уровне дизайна.
В идеальной ситуации те элементы, что скрыты с помощью
visually-hidden, должны отображаться визуально и не должны быть скрыты. Они будут занимать место и загромождать интерфейс, но это уже вопрос удобства против эстетики.#a11y
dbushell.com
Everything you never wanted to know about visually-hidden
The one where I attempt to answer a question
👍7🤔3❤2🌚2👾1
Разница между aria-selected, aria-checked, aria-current и aria-pressed
В стандарте ARIA есть состояния, которые можно применять к элементам. Среди них есть несколько довольно похожих на первый взгляд:
-
-
-
-
-
-
-
Применяется у следующих элементов:
-
-
-
-
-
-
-
-
-
Применяется к следующим элементам:
-
-
-
-
-
-
-
У
-
-
-
-
-
-
-
Назначение
-
-
-
-
Таким образом все четыре атрибута хоть и обозначают некоторое текущее состояние элемента, но отличаются по смыслу и применимости. Краткая сводка:
-
-
-
-
#a11y
В стандарте ARIA есть состояния, которые можно применять к элементам. Среди них есть несколько довольно похожих на первый взгляд:
-
aria-selected;-
aria-checked;-
aria-current;-
aria-pressed.aria-selected обозначает состояние выбора элемента внутри виджетов с одиночным или множественным выбором. Принимает значения:-
true — элемент выбран;-
false — элемент не выбран;-
undefined — элемент не выбираемый.Применяется у следующих элементов:
-
option в listbox;-
treeitem в tree;-
gridcell или row в grid;-
gridcell или row в treegrid;-
tab в tablist.aria-checked обозначает состояние отметки флажка, радио-кнопки, переключателя, отмечаемых пунктов меню или группы, состоящей из отмечаемых элементов со смешанным состоянием. Принимает значения:-
true — элемент отмечен;-
false — элемент не отмечен;-
mixed — элемент в смешанном (indeterminate) состоянии и связан с группой других отмечаемых элементов;-
undefined — элемент не отмечаемый.Применяется к следующим элементам:
-
checkbox;-
radio (кроме mixed);-
switch (кроме mixed);-
option в listbox;-
menuitemcheckbox в menu или menubar;-
menuitemradio в menu или menubar (кроме mixed);-
treeitem в tree.У
option и treeitem можно использовать aria-selected и aria-checked. Подойдёт любой из них. Но рекомендуется для одиночного выбора использовать aria-selected, а в случае множественного выбора использовать aria-checked.aria-current обозначает текущий элемент в контейнере или наборе связанных элементов. Может быть установлен на любой элемент, но принимает в качестве значения один из предустановленных токенов:-
page — элемент визуально выделен как текущая страница из набора страниц, подходит для активной ссылки в меню или хлебных крошках;-
step — элемент визуально выделен как текущий шаг в многошаговом процессе, подходит для визардов и многошаговых форм;-
location — элемент визуально выделен как текущее положение на странице, экране или процессе;-
date — элемент визуально выделен как текущая дата в виджете календаря или другом наборе дат;-
time — элемент визуально выделен как текущее время в таблице времени или другом наборе времени;-
true — элемент визуально выделен как текущий в наборе связанных элементов без дополнительного контекста;-
false — элемент не выделен визуально как текущий в наборе связанных элементов.Назначение
aria-current — программно передать визуальное выделение элемента. При этом только один элемент в наборе может быть отмечен как текущий и это нельзя использовать как альтернативу aria-selected и aria-checked.aria-pressed обозначает состояние кнопки-переключателя и используется только на кнопках и элементах с ролью button. Требует реализации механизма зацикленной смены значений при нажатии. Принимает следующие значения:-
true — кнопка нажата;-
false — кнопка не нажата;-
mixed — кнопка зависит от смешанного состояния нескольких других кнопок;-
undefined — кнопка не работает как переключатель.Таким образом все четыре атрибута хоть и обозначают некоторое текущее состояние элемента, но отличаются по смыслу и применимости. Краткая сводка:
-
aria-selected — состояние выбора для option, treeitem, gridcell, row и tab, рекомендуется для одиночного выбора в случае с option и treeitem;-
aria-checked — состояние отметки для checkbox, radio, switch, option, treeitem, menuitemradio и menuitemcheckbox, рекомендуется для множественного выбора в случае с option и treeitem, может быть в смешанном состоянии mixed у checkbox, option, treeitem и menuitemcheckbox;-
aria-current — визуально выделенный элемент в наборе связанных страниц, шагов, локаций, дат, времени или других элементов, один на весь набор;-
aria-pressed — состояние кнопки-переключателя, применимо только для кнопок, может быть в смешанном состоянии mixed.#a11y
👍12❤3🔥2🌚1👨💻1
Настоящий mobile-first
Mobile-first многим известен как подход к вёрстке, при котором сначала реализуется версия для мобильных и расширяется до десктопа. Это противоположность подходу desktop-first, при котором вёрстка идёт от десктопа к мобильным.
Разница обычно сводится к организации медиа-запросов и использованию
Принципиальной разницы нет. В теории, движение от малых экранов к большим даст в итоге чуть меньше стилей, потому что контент по умолчанию mobile-first, адаптируется под ширину экрана и идёт в потоке сверху вниз.
Однако сам термин mobile-first говорит о том, что подход ориентирован в первую очередь на мобильные устройства. Суть не в том, в какую сторону переопределяются стили, а в том, чтобы учесть ограничения мобильных устройств.
Ограничения исходят из конструктивных особенностей. Мобильные устройства компактные и переносимые, питаются от аккумулятора, работают в беспроводных сетях, управляются касаниями и жестами, вмещают меньше контента на экране.
Размеры и питание от аккумулятора не позволяют использовать более мощное железо и массивные системы охлаждения. Поэтому мобильные устройства уступают десктопам по производительности, что повышает требования к оптимизации.
Точки Wi-Fi и мобильные сети менее стабильны, чем проводные соединения. Скорость приёма/передачи, потери пакетов, задержки и другие характеристики сети на мобильных устройствах, как правило, уступают десктопам.
Не смотря на безлимитные тарифы, всё ещё достаточно распространены тарифы с лимитированным трафиком и скоростью. Покрытие сети вне крупных городов ухудшается. Может быть включен режим экономии трафика или батареи.
Отличаются сценарии взаимодействия. Нет мыши и курсора, виртуальная клавиатура на треть экрана заменяет физическую, управление осуществляется жестами, касаниями и долгими нажатиями вместо кликов и наведения указателя мыши.
Настоящий mobile-first — это разработка сайтов и веб-приложений с учётом всех особенностей и ограничений мобильных устройств. Он затрагивает архитектуру проекта и выбор инструментов разработки, а не только направление медиа-запросов.
В хорошем mobile-first:
- Элементы управления и контент компактно и логично размещены на ограниченном по размеру экране, интерфейс не перегружен;
- Интерактивные элементы достаточно большие для взаимодействия касаниями и расположены так, что с ними удобно работать;
- Ресурсы оптимизированы для минимального размера и быстрой загрузки;
- Видео, анимации, сложные фоновые вычисления и сетевые операции ограничены, если мало заряда батареи или включен энергосберегающий режим;
- Упрощённая версия без лишних ресурсов загружается, если включен режим экономии трафика;
- Контент кэшируется на устройстве и доступен оффлайн при потере соединения;
- Контент адаптируется к изменению ориентации экрана, режиму разделения экрана и другим возможностям устройства;
- Сайт использует аппаратные возможности устройства (камеру, микрофон, Bluetooth, акселерометр и другие), если это уместно и помогает пользователю;
#css #ux #performance
Mobile-first многим известен как подход к вёрстке, при котором сначала реализуется версия для мобильных и расширяется до десктопа. Это противоположность подходу desktop-first, при котором вёрстка идёт от десктопа к мобильным.
Разница обычно сводится к организации медиа-запросов и использованию
min-width/max-width. Сначала применяются мобильные стили и переопределяются на более широких экранах, или десктопные переопределяются на более узких./* Mobile-first */
.product-grid {
--col: 1;
--gap: 12px;
display: grid;
grid-template-columns:
repeat(var(--col), minmax(0, 1fr))
;
gap: var(--gap);
}
@media (min-width: 600px) {
.product-grid {
--col: 2;
--gap: 18px;
}
}
@media (min-width: 900px) {
.product-grid {
--col: 3;
--gap: 24px;
}
}
@media (min-width: 1200px) {
.product-grid {
--col: 4;
}
}
/* Desktop-first */
.product-grid {
--col: 4;
--gap: 24px;
display: grid;
grid-template-columns:
repeat(var(--col), minmax(0, 1fr))
;
gap: var(--gap);
}
@media (max-width: 1200px) {
.product-grid {
--col: 3;
}
}
@media (max-width: 900px) {
.product-grid {
--col: 2;
--gap: 18px;
}
}
@media (max-width: 600px) {
.product-grid {
--col: 1;
--gap: 12px;
}
}
Принципиальной разницы нет. В теории, движение от малых экранов к большим даст в итоге чуть меньше стилей, потому что контент по умолчанию mobile-first, адаптируется под ширину экрана и идёт в потоке сверху вниз.
Однако сам термин mobile-first говорит о том, что подход ориентирован в первую очередь на мобильные устройства. Суть не в том, в какую сторону переопределяются стили, а в том, чтобы учесть ограничения мобильных устройств.
Ограничения исходят из конструктивных особенностей. Мобильные устройства компактные и переносимые, питаются от аккумулятора, работают в беспроводных сетях, управляются касаниями и жестами, вмещают меньше контента на экране.
Размеры и питание от аккумулятора не позволяют использовать более мощное железо и массивные системы охлаждения. Поэтому мобильные устройства уступают десктопам по производительности, что повышает требования к оптимизации.
Точки Wi-Fi и мобильные сети менее стабильны, чем проводные соединения. Скорость приёма/передачи, потери пакетов, задержки и другие характеристики сети на мобильных устройствах, как правило, уступают десктопам.
Не смотря на безлимитные тарифы, всё ещё достаточно распространены тарифы с лимитированным трафиком и скоростью. Покрытие сети вне крупных городов ухудшается. Может быть включен режим экономии трафика или батареи.
Отличаются сценарии взаимодействия. Нет мыши и курсора, виртуальная клавиатура на треть экрана заменяет физическую, управление осуществляется жестами, касаниями и долгими нажатиями вместо кликов и наведения указателя мыши.
Настоящий mobile-first — это разработка сайтов и веб-приложений с учётом всех особенностей и ограничений мобильных устройств. Он затрагивает архитектуру проекта и выбор инструментов разработки, а не только направление медиа-запросов.
В хорошем mobile-first:
- Элементы управления и контент компактно и логично размещены на ограниченном по размеру экране, интерфейс не перегружен;
- Интерактивные элементы достаточно большие для взаимодействия касаниями и расположены так, что с ними удобно работать;
- Ресурсы оптимизированы для минимального размера и быстрой загрузки;
- Видео, анимации, сложные фоновые вычисления и сетевые операции ограничены, если мало заряда батареи или включен энергосберегающий режим;
- Упрощённая версия без лишних ресурсов загружается, если включен режим экономии трафика;
- Контент кэшируется на устройстве и доступен оффлайн при потере соединения;
- Контент адаптируется к изменению ориентации экрана, режиму разделения экрана и другим возможностям устройства;
- Сайт использует аппаратные возможности устройства (камеру, микрофон, Bluetooth, акселерометр и другие), если это уместно и помогает пользователю;
#css #ux #performance
❤8👍3😈2🌚1
Страница на Claude Code и Figma MCP
Недавно посмотрел страницу «Coming Soon» одного проекта, которая создана с помощью Claude Code в сочетании с Figma MCP по отрисованному макету. Её создал человек без опыта разработки за 30 минут. На доработку ему понадобилось 3 промпта.
Сама страница — фоновое изображение, логотип, заголовок, текст, поле для email, кнопка отправки и ещё один логотип. То есть простая страница. С визуальной точки зрения всё неплохо: выглядит как надо, адаптивность есть, форма работает.
Я бы не писал об этом, если бы вновь не пришлось узреть мощь современных технологий. Страница создана на Next и Tailwind. Дело не в самих технологиях, а в том, что их использование не оправдано для такой простой страницы.
Агент выбрал в качестве стэка по умолчанию Next и Tailwind, потому что ему не было указано иное. Уж не знаю, почему они делают такой выбор, но это печально. По всей видимости, статистически самый частый вариант. Получается стрельба из пушки по воробьям.
Интересно то, что Pagespeed выдаёт 100 баллов по производительности и 97 по доступности. То есть всё в порядке, цифры зелёные не смотря на Next. Тут важна трактовка результатов. Было бы странно видеть на такой странице не 100.
Достаточно добавить разделы с формой, каруселью, раскрываемыми FAQ, шапку с выпадающим меню, эффекты и аналитику, и эти 100 баллов стремительно полетят вниз. Весь бюджет JS съеден обвязкой Next уже на самом старте.
На страницу загружается 8 JS-файлов общим размером 160кб (~540кб без сжатия) просто потому что так работает Next. Вся интерактивность на странице — отправка email с формы в сторонний сервис. Для этого не нужен Next.
При загрузке страницы виден прыжок шрифта. Подключается вариативный шрифт размером 1.9мб (4.9мб без сжатия) для отрисовки заголовка и блока текста с одним предложением. Обший размер всех ресурсов — 2.5мб (6мб без сжатия).
Для создания этой простейшей страницы развёрнута целая машинерия. Банальная замена текста — процесс с запросом к Claude, который ждёт сборку и загрузку на Vercel. А ведь можно было просто открыть файл и поправить текст.
С точки зрения использования ИИ это тоже расточительство. Сколько лишней работы, токенов и времени затрачено на генерацию конфигов, создание структуры проекта, установку модулей, создание компонентов и генерацию бойлерплейта.
Эта страница должна быть одним HTML-файлом со встроенными
Это пост не о том, что Claude плохой, а о том, что по умолчанию результат выходит посредственный. Нужна экспертиза и понимание, чтобы задавать правильный вектор: стэк, правила, ограничения и желаемый результат.
А ещё настораживает, что кодогенерацией занимаются специалисты без опыта в разработке и на выходе выдают такие вот «сайты». Наверно, всё же, стоит отдавать эту работу разработчикам, чтобы не вышла «мощь современных технологий».
#performance
Недавно посмотрел страницу «Coming Soon» одного проекта, которая создана с помощью Claude Code в сочетании с Figma MCP по отрисованному макету. Её создал человек без опыта разработки за 30 минут. На доработку ему понадобилось 3 промпта.
Сама страница — фоновое изображение, логотип, заголовок, текст, поле для email, кнопка отправки и ещё один логотип. То есть простая страница. С визуальной точки зрения всё неплохо: выглядит как надо, адаптивность есть, форма работает.
Я бы не писал об этом, если бы вновь не пришлось узреть мощь современных технологий. Страница создана на Next и Tailwind. Дело не в самих технологиях, а в том, что их использование не оправдано для такой простой страницы.
Агент выбрал в качестве стэка по умолчанию Next и Tailwind, потому что ему не было указано иное. Уж не знаю, почему они делают такой выбор, но это печально. По всей видимости, статистически самый частый вариант. Получается стрельба из пушки по воробьям.
Интересно то, что Pagespeed выдаёт 100 баллов по производительности и 97 по доступности. То есть всё в порядке, цифры зелёные не смотря на Next. Тут важна трактовка результатов. Было бы странно видеть на такой странице не 100.
Достаточно добавить разделы с формой, каруселью, раскрываемыми FAQ, шапку с выпадающим меню, эффекты и аналитику, и эти 100 баллов стремительно полетят вниз. Весь бюджет JS съеден обвязкой Next уже на самом старте.
На страницу загружается 8 JS-файлов общим размером 160кб (~540кб без сжатия) просто потому что так работает Next. Вся интерактивность на странице — отправка email с формы в сторонний сервис. Для этого не нужен Next.
При загрузке страницы виден прыжок шрифта. Подключается вариативный шрифт размером 1.9мб (4.9мб без сжатия) для отрисовки заголовка и блока текста с одним предложением. Обший размер всех ресурсов — 2.5мб (6мб без сжатия).
Для создания этой простейшей страницы развёрнута целая машинерия. Банальная замена текста — процесс с запросом к Claude, который ждёт сборку и загрузку на Vercel. А ведь можно было просто открыть файл и поправить текст.
С точки зрения использования ИИ это тоже расточительство. Сколько лишней работы, токенов и времени затрачено на генерацию конфигов, создание структуры проекта, установку модулей, создание компонентов и генерацию бойлерплейта.
Эта страница должна быть одним HTML-файлом со встроенными
<style> и <script>. Без Next, Tailwind, node_modules, Turbopack, 8 чанков JS, вариативного шрифта и так далее. Для отправки формы простой скрипт на чистом JS.Это пост не о том, что Claude плохой, а о том, что по умолчанию результат выходит посредственный. Нужна экспертиза и понимание, чтобы задавать правильный вектор: стэк, правила, ограничения и желаемый результат.
А ещё настораживает, что кодогенерацией занимаются специалисты без опыта в разработке и на выходе выдают такие вот «сайты». Наверно, всё же, стоит отдавать эту работу разработчикам, чтобы не вышла «мощь современных технологий».
#performance
🔥16❤7🤡4👍3🌚1😎1
Оптимизация размера DOM бесполезна?
Я писал про уменьшение размера DOM. На днях смотрел подкаст «Организованное Программирование» про SEO, там гость заявил, что считает совет Lighthouse об оптимизации размера DOM вредным и бесполезным. Так ли это на самом деле?
Загрузка любого сайта начинается с получения HTML-разметки. HTML — это текстовый формат. Больше текста = больше размер файла, который нужно передать по сети. Значит файл будет дольше загружаться, что логично.
Но есть парадокс под названием сжатие. 72% текстовых файлов передаются по сети в сжатом виде. Если вы входите в 28%, то настройте хотя-бы Gzip, а лучше Brotli или Zstd. Сервер сжимает файл при отправке, браузер разжимает при получении.
Более тяжёлый в несжатом виде файл может весить меньше более лёгкого файла, если сравнить их размеры в сжатом виде. Алгоритмы работают на повторяющихся фрагментах текста, поэтому на большом HTML они могут быть эффективнее.
Не всегда рост размера HTML приводит к росту размера файла, передаваемого в итоге по сети. Иногда даже наоборот. Выходит, что оптимизация размера HTML пустая трата времени и бесполезный совет? Отнюдь нет.
Помимо передачи по сети HTML служит основой для построения DOM. HTML-парсер проходится по разметке и создаёт для каждого HTML-элемента, атрибута, строки текста и комментария объект с множеством свойств и связей.
Эти объекты нужны для того, чтобы ими манипулировать через DOM API, что под капотом делают любые JS-фреймворки. Также нужно построить CSSOM — для каждого узла вычислить все CSS-свойства, чтобы знать размеры и положение.
CSSOM нужен для итоговых процессов отрисовки страницы: Render tree, Layout и Paint. Это чтобы первично отрисовать страницу. Далее она прокручивается, меню выпадает, слайды переключаются, кнопки меняют цвет при наведении и так далее.
Всё это приводит к повторным пересчётам. Многие взаимодействия со страницей вызывают пересчёт. Браузеры выполняют огромное количество операций в секунду. За годы существования все эти процессы отлично оптимизированы.
Чтобы страница ощущалась быстрой и плавной, все операции по пересчёту стилей и отрисовке должны укладываться в один кадр отрисовки, то есть ~16мс для экрана с частотой обновления 60 Гц. Желательно не впритык, а с некоторым запасом.
Чем больше DOM-узлов, тем больше всего браузеру нужно рассчитать и подготовить к отрисовке. Осложняют дело «тяжёлые» новинки, такие как селектор
JS-отрисовка добавляет дополнительные накладные расходы. Размеры бандлов растут из-за шаблонов в коде, потребление памяти и процессора растёт, потому что код шаблона нужно выполнить, а с VDOM ещё держать в памяти деревья.
С большим DOM можно выйти за пределы одного кадра отрисовки, тогда начнётся заметная потеря кадров, сопровождаемая «лагами» и «фризами». Особо заметно это на низко- и средне-бюджетных смартфонах, которыми пользуется большинство.
Столкнувшись с «фризами» пользователи уйдут к конкурентам, у которых такого нет. Поисковики эти сигналы считывают и запоминают, ухудшаются поведенческие факторы и сайт теряет позиции в выдаче поиска. Бизнес теряет пользователей.
Это может показаться притянутым за уши. Но проблема точно реальная и с ней борются. Глянуть сайт на предмет просадки кадров точно стоит (в DevTools меню с тремя точками → More tools → Rendering → флажок Frame Rendering States).
Во вкладке Performance можно обратить внимание, сколько времени занимает Parse HTML, Rendering и Painting. Во вкладке Memory можно сделать снэпшот и увидеть, сколько оперативной памяти выделено на разные объекты, в том числе узлы DOM.
Экономия на спичках. Возможно и так, учитывая сложность оптимизации HTML. Если дело дошло до этого, значит всё остальное уже хорошо оптимизировано. Тем не менее, я считаю, что верстать нужно с мыслью об экономии узлов.
Современный CSS с flex и grid, а также немного смекалки позволяют достигать результата с меньшим количеством разметки. Убрали лишние узлы, сжали картинки, взяли встроенное API вместо JS и уже немало сэкономили.
#html #performance
Я писал про уменьшение размера DOM. На днях смотрел подкаст «Организованное Программирование» про SEO, там гость заявил, что считает совет Lighthouse об оптимизации размера DOM вредным и бесполезным. Так ли это на самом деле?
Загрузка любого сайта начинается с получения HTML-разметки. HTML — это текстовый формат. Больше текста = больше размер файла, который нужно передать по сети. Значит файл будет дольше загружаться, что логично.
Но есть парадокс под названием сжатие. 72% текстовых файлов передаются по сети в сжатом виде. Если вы входите в 28%, то настройте хотя-бы Gzip, а лучше Brotli или Zstd. Сервер сжимает файл при отправке, браузер разжимает при получении.
Более тяжёлый в несжатом виде файл может весить меньше более лёгкого файла, если сравнить их размеры в сжатом виде. Алгоритмы работают на повторяющихся фрагментах текста, поэтому на большом HTML они могут быть эффективнее.
Не всегда рост размера HTML приводит к росту размера файла, передаваемого в итоге по сети. Иногда даже наоборот. Выходит, что оптимизация размера HTML пустая трата времени и бесполезный совет? Отнюдь нет.
Помимо передачи по сети HTML служит основой для построения DOM. HTML-парсер проходится по разметке и создаёт для каждого HTML-элемента, атрибута, строки текста и комментария объект с множеством свойств и связей.
Эти объекты нужны для того, чтобы ими манипулировать через DOM API, что под капотом делают любые JS-фреймворки. Также нужно построить CSSOM — для каждого узла вычислить все CSS-свойства, чтобы знать размеры и положение.
CSSOM нужен для итоговых процессов отрисовки страницы: Render tree, Layout и Paint. Это чтобы первично отрисовать страницу. Далее она прокручивается, меню выпадает, слайды переключаются, кнопки меняют цвет при наведении и так далее.
Всё это приводит к повторным пересчётам. Многие взаимодействия со страницей вызывают пересчёт. Браузеры выполняют огромное количество операций в секунду. За годы существования все эти процессы отлично оптимизированы.
Чтобы страница ощущалась быстрой и плавной, все операции по пересчёту стилей и отрисовке должны укладываться в один кадр отрисовки, то есть ~16мс для экрана с частотой обновления 60 Гц. Желательно не впритык, а с некоторым запасом.
Чем больше DOM-узлов, тем больше всего браузеру нужно рассчитать и подготовить к отрисовке. Осложняют дело «тяжёлые» новинки, такие как селектор
:has(), контейнерные и стилевые запросы, @scope, каскадные слои.JS-отрисовка добавляет дополнительные накладные расходы. Размеры бандлов растут из-за шаблонов в коде, потребление памяти и процессора растёт, потому что код шаблона нужно выполнить, а с VDOM ещё держать в памяти деревья.
С большим DOM можно выйти за пределы одного кадра отрисовки, тогда начнётся заметная потеря кадров, сопровождаемая «лагами» и «фризами». Особо заметно это на низко- и средне-бюджетных смартфонах, которыми пользуется большинство.
Столкнувшись с «фризами» пользователи уйдут к конкурентам, у которых такого нет. Поисковики эти сигналы считывают и запоминают, ухудшаются поведенческие факторы и сайт теряет позиции в выдаче поиска. Бизнес теряет пользователей.
Это может показаться притянутым за уши. Но проблема точно реальная и с ней борются. Глянуть сайт на предмет просадки кадров точно стоит (в DevTools меню с тремя точками → More tools → Rendering → флажок Frame Rendering States).
Во вкладке Performance можно обратить внимание, сколько времени занимает Parse HTML, Rendering и Painting. Во вкладке Memory можно сделать снэпшот и увидеть, сколько оперативной памяти выделено на разные объекты, в том числе узлы DOM.
Экономия на спичках. Возможно и так, учитывая сложность оптимизации HTML. Если дело дошло до этого, значит всё остальное уже хорошо оптимизировано. Тем не менее, я считаю, что верстать нужно с мыслью об экономии узлов.
Современный CSS с flex и grid, а также немного смекалки позволяют достигать результата с меньшим количеством разметки. Убрали лишние узлы, сжали картинки, взяли встроенное API вместо JS и уже немало сэкономили.
#html #performance
🔥11👍8❤2🌚1🤝1
Стилизуемый combobox и фильтруемый select
Браузеры совместно с OpenUI продолжают развивать
На конференции BlinkOn 21 контрибьютор движка Chromium поделился прогрессом в этом направлении. Я уже писал о том, как работают атрибуты size и multiple у стандартного
Первое — обёртки
Для состояния, когда не выбрана ни одна опция, а значит
Далее продемонстрирован пример создания виджета выпадающего списка опций с возможностью фильтрации через поле ввода. В качестве образца выступает виджет добавления тегов в GitHub.
Виджет собран из нескольких встроенных API. Кнопка с атрибутами
Прорабатывается вариант для простых случаев, когда есть только опции и поле для их фильтрации, без дополнительных элементов. В таком случае предлагается вкладывать
К фильтрации предусмотрено событие
Показана возможность стилизации уже существующей в браузерах связки
Некоторые из показанных возможностей тестируются и уже доступны в Chrome за флагом экспериментальных технологий. Разработчики собирают обратную связь, обсуждают спорные моменты и дорабатывают API.
#html #css #ui
Браузеры совместно с OpenUI продолжают развивать
<select>, добавляя новые функции и возможности стилизации. В Сhrome уже можно стилизовать стандартный <select> с одиночным выбором, на подходе вариант с множественным выбором и combobox.На конференции BlinkOn 21 контрибьютор движка Chromium поделился прогрессом в этом направлении. Я уже писал о том, как работают атрибуты size и multiple у стандартного
<select> и планах по расширению их функциональности.<select multiple size="1">
<button>
<selectedcontent>
<div>One</div>
<div>Two</div>
</selectedcontent>
<span>0 Selected</span>
</button>
<option selected>One</option>
<option selected>Two</option>
<!-- ... -->
</select>
<style>
selectedcontent:not(:empty) + span {
display: none;
}
</style>
Первое — обёртки
<div> для проецируемых опций в <selectedcontent>. Этот элемент был внедрён как часть стилизуемого <select> для проецирования значения выбранной опции. Теперь проецируются несколько опций и они оборачиваются в <div> для стилизации.Для состояния, когда не выбрана ни одна опция, а значит
<selectedcontent> пуст, предлагается использовать резервный элемент <span> с текстом. Он скрывается через комбинацию псевдо-классов :not(), :empty и комбинатор +.Далее продемонстрирован пример создания виджета выпадающего списка опций с возможностью фильтрации через поле ввода. В качестве образца выступает виджет добавления тегов в GitHub.
<button
commandfor="dialog"
command="showpopover"
>
<selectedcontent id="sc">
</selectedcontent>
</button>
<dialog popover id="dialog">
<input filter="select">
<select
size="4"
id="select"
selectedcontentelement="sc"
>
<!-- опции -->
</select>
<!-- можно добавить другие элементы -->
</dialog>
Виджет собран из нескольких встроенных API. Кнопка с атрибутами
command/commandfor управляет отображением диалога, который реализован с помощью <dialog popover>. Внутри <select>, который связан с <selectedcontent> для проекции выбранной опции.<input> связан с <select> через новый атрибут filter, который позволяет фильтровать опции. Предложенный вариант многословен, но хорош тем, что в <dialog> можно поместить другие элементы (текст, кнопки, иконки) помимо <input> и <select>.Прорабатывается вариант для простых случаев, когда есть только опции и поле для их фильтрации, без дополнительных элементов. В таком случае предлагается вкладывать
<input> внутрь <select> перед опциями. Для этого будет доработан парсер.<select>
<input>
<!-- опции -->
</select>
К фильтрации предусмотрено событие
beforefilter, которое можно перехватить, отменить стандартное поведение через preventDefault(). Это механизм для реализации подгрузки данных из внешнего источника и динамической генерации списка опций.Показана возможность стилизации уже существующей в браузерах связки
<input> и <datalist>. Как и в случае со стилизуемым <select>, API базируется на идее базового внешнего вида и стандартных стилей, задаваемых свойством appearance: base;.<input list="options">
<datalist id="options">
<!-- опции -->
</datalist>
<style>
input[list], datalist {
appearance: base;
}
</style>
Некоторые из показанных возможностей тестируются и уже доступны в Chrome за флагом экспериментальных технологий. Разработчики собирают обратную связь, обсуждают спорные моменты и дорабатывают API.
#html #css #ui
YouTube
Customizable combobox and filterable select [BlinkOn 21]
Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube.
👍6❤1🌚1🤗1
You don't know HTML: <fieldset>
Элемент
Подпись задаётся с помощью элемента
У элемента
Помимо встроенной роли, семантики и привязки имени у
Атрибут
У
Атрибут
У
Таким образом
#ydkhtml
Элемент
<fieldset> в HTML предназначен для группировки элементов формы с опциональной подписью. Явные элементы для группировки — флажки, радио-кнопки, переключатели и данные, которые разбиты на несколько полей, как адрес или телефон.Подпись задаётся с помощью элемента
<legend>. Если он есть, то должен быть первым прямым потоком <fieldset>. Иное размещение или оборачивание в контейнер не валидно и подпись не будет привязана к <fieldset>.У элемента
<fieldset> встроенная роль group. Наличие валидного <legend> задаёт имя для группы. Воспроизвести это можно с помощью ARIA, но зачем, если в платформе есть встроенный элемент с соответствующей семантикой:<div
role="group"
aria-labelledby="legend"
>
<div id="legend">Размер</div>
<!-- переключатели -->
</div>
<!-- встроенный аналог -->
<fieldset>
<legend>Размер</legend>
<!-- переключатели -->
</fieldset>
Помимо встроенной роли, семантики и привязки имени у
<fieldset> есть API. Атрибут disabled отключает вложенные элементы формы, поэтому не нужно вручную каждому проставлять disabled. Полезно для отключения части формы.Атрибут
name задаёт идентификатор группы для доступа к ней через Form API. Это удобно использовать в сочетании со свойством elements, которое хранит все элементы формы или группы. Через него можно получить конкретный элемент.<form name="variants">
<fieldset name="sizes">
<legend>Размер</legend>
<!-- переключатели -->
</fieldset>
<!-- ... -->
</form>
<script>
const variantsForm = document.forms['variants'];
// или document.forms.variants
// или document.forms.namedItem('variants')
const sizeGroup = variantsForm.elements['sizes'];
// или variantsForm.elements.sizes
// или variantsForm.elements.namedItem('sizes')
// отключение группы
sizeGroup.disabled = true;
// элементы группы
const elements = sizeGroup.elements;
</script>
У
<fieldset> можно вызывать методы Constraint Validation API и проверить на валидность только поля из группы, а не всей формы целиком. Также применимы псевдо-классы :valid/:invalid, :user-valid/:user-invalid.Атрибут
form позволяет привязать группу со всеми элементами к указанной форме. Это позволяет вынести группу за пределы формы, сохранив с ней связь. Сбор данных для отправки, валидация и методы работы с элементами будут доступны.У
<fieldset> есть некоторые особенности стилизации. По умолчанию у него блочный контекст форматирования, ширина подстраивается под контент, есть рамка и <legend> размещается поверх рамки. Но элемент полностью стилизуемый.Таким образом
<fieldset> в сочетании с <legend> полезен для группировки полей, передаёт вспомогательным технологиям нужную семантику и имя, обладает API для управления элементами (отключение, вынос из формы, получение, валидация).#ydkhtml
👍11🌚1🤝1
sizes="auto" для изображений
Для адаптивных изображений у элемента
В Firefox 150 и Chrome 124 добавили новое значение
Зачем указывать
Обнаружив изображение, браузер поместит его в очередь загрузки. В моменте нужно решить, какое из изображений ставить в очередь. К этому времени стили могут быть ещё не загружены или не обработаны. Поэтому браузер смотрит значение
С лениво-загружаемыми изображениями ситуация другая. Их загрузка отложена до тех пор, пока изображение не появится в области просмотра. Чтобы это выяснить, браузеру нужно обработать стили и знать размеры и положение на странице.
К моменту загрузки ленивого изображения, даже если оно находится на первом экране, браузер уже обработал стили и знает все размеры. Именно поэтому
Таким образом изображениям с ленивой загрузкой
Синтаксисом предусмотрено совмещение значения
Мэт Маркиз, бывший председатель группы адаптивных изображений (Responsive Image Community Group) написал статью «The end of responsive images», в которой рассказал более чем 10-летнюю историю появления
#html #css
Для адаптивных изображений у элемента
<img> есть два атрибута. srcset задаёт набор изображений с физической шириной или плотностью пикселей. sizes задаёт доступное пространство, которое браузер заполняет подходящим изображением.В Firefox 150 и Chrome 124 добавили новое значение
sizes="auto". Однако не стоит думать, что теперь указывать размеры в sizes не нужно. sizes="auto" работает в паре с loading="lazy". Без него указывать auto нет смысла, работать не будет.Зачем указывать
sizes, разве браузер не знает размеры области для отрисовки изображения? Дело в том, что не знает до загрузки и обработки всех стилей. Сканер предварительной загрузки анализирует HTML и ищет ресурсы.Обнаружив изображение, браузер поместит его в очередь загрузки. В моменте нужно решить, какое из изображений ставить в очередь. К этому времени стили могут быть ещё не загружены или не обработаны. Поэтому браузер смотрит значение
sizes.С лениво-загружаемыми изображениями ситуация другая. Их загрузка отложена до тех пор, пока изображение не появится в области просмотра. Чтобы это выяснить, браузеру нужно обработать стили и знать размеры и положение на странице.
К моменту загрузки ленивого изображения, даже если оно находится на первом экране, браузер уже обработал стили и знает все размеры. Именно поэтому
sizes="auto" будет работать, браузер возьмёт известные ему размеры области.Таким образом изображениям с ленивой загрузкой
loading="lazy" можно задать sizes="auto". Остальным всё ещё нужно указывать размеры. Также остаются браузеры, которые не понимают значение auto, для них тоже нужны размеры.Синтаксисом предусмотрено совмещение значения
auto с размерами. Если браузер не понимает auto или не может получить размеры из стилей, он откатится к размеру, который указан после auto через запятую: sizes="auto, (min-width: 600px) 50vw, 90vw".Мэт Маркиз, бывший председатель группы адаптивных изображений (Responsive Image Community Group) написал статью «The end of responsive images», в которой рассказал более чем 10-летнюю историю появления
sizes="auto".#html #css
Piccalilli
The end of responsive images
Mat Marquis has waited 14 years to write this article. The sizes attribute has been a necessary evil but now, with an auto value capability, it’s completely transformed authoring responsive images on the web.
👍4🔥3🌚1🤝1
ARIA APG: доверяй, но проверяй
Есть ресурс под названием ARIA Authoring Practices Guide, сокращённо APG. На него ссылаются как на источник доступных шаблонов элементов интерфейса. Это неплохой ресурс, но при обращении к нему стоит помнить о некоторых нюансах.
Ресурс выглядит официально, содержит логотип W3C и WAI, размещён на домене w3.org, упоминается на сайте инициативы по веб доступности. Внушает доверие и вызвает ложное ощущение официального стандарта от W3C.
На деле APG — не стандарт W3C, в отличие от ARIA, WCAG, ARIA in HTML и других. APG создан специальной группой APG Task Force из сообщества WAI, которое, в свою очередь, одно из многих сообществ в составе консорциума W3C.
Задача APG — показать, как применять стандарт ARIA для реализации элементов интерфейса. Шаблоны демонстрируют все роли, свойства и состояния в соответствии с правилами стандарта. Это своего рода витрина ARIA.
Если взглянуть на шаблон кнопки, то APG предлагает использовать
Тем временем первое правило ARIA гласит, что не стоит использовать ARIA, если есть встроенный элемент. В HTML кнопка есть —
Аналогично с шаблонами флажков, радио-кнопок, диалогов, раскрываемых блоков, ссылок, ориентиров, шкалы, слайдера, таблиц и так далее. Они показывают, как реализовать с помощью ARIA. Зачастую в этом нет необходимости.
Отдельного внимания заслуживает борьба Адриана Розелли с шаблоном навигации сайта, который долгое время предлагал использовать не подходящие роли меню. Разработчики внедряли этот шаблон на сайты и делали только хуже.
Всё сказанное выше не значит, что на APG не стоит смотреть. Это неплохой источник информации о проектировании доступных интерфейсов. Важно не воспринимать его как официальный источник истины со 100% правильными шаблонами.
Эрик Бэйли считает хорошими частями в APG:
- Названия шаблонов. Хорошо, когда вся команда говорит на одном языке;
- Описания шаблонов. Суть шаблона, для чего нужен, как работает и когда применять;
- Клавиатурная навигация. Клавиши и их сочетания, которые должны работать при взаимодействии;
Остальное вторично. Таблицы поддержки не отражают реальный уровень поддержки, примеры кода не согласованы, написаны в разных стилях, используют устаревшие подходы и не готовы к продакшену, часть шаблонов заменяется встроенным HTML.
Используйте APG как источник знаний о сути того или иного шаблона и навигации с помощью клавиатуры. По возможности используйте встроенные HTML-элементы, обращайтесь к официальному стандарту ARIA и всегда тестируйте.
#ui #a11y
Есть ресурс под названием ARIA Authoring Practices Guide, сокращённо APG. На него ссылаются как на источник доступных шаблонов элементов интерфейса. Это неплохой ресурс, но при обращении к нему стоит помнить о некоторых нюансах.
Ресурс выглядит официально, содержит логотип W3C и WAI, размещён на домене w3.org, упоминается на сайте инициативы по веб доступности. Внушает доверие и вызвает ложное ощущение официального стандарта от W3C.
На деле APG — не стандарт W3C, в отличие от ARIA, WCAG, ARIA in HTML и других. APG создан специальной группой APG Task Force из сообщества WAI, которое, в свою очередь, одно из многих сообществ в составе консорциума W3C.
Задача APG — показать, как применять стандарт ARIA для реализации элементов интерфейса. Шаблоны демонстрируют все роли, свойства и состояния в соответствии с правилами стандарта. Это своего рода витрина ARIA.
Если взглянуть на шаблон кнопки, то APG предлагает использовать
<div>, <a> и <span> с ролью button, tabindex="0" для фокуса и JS для обработки нажатия. APG свою задачу выполнил: показал, как использовать роль button.Тем временем первое правило ARIA гласит, что не стоит использовать ARIA, если есть встроенный элемент. В HTML кнопка есть —
<button>. На практике <div> с ролью кнопки, tabindex и обработчиком нажатий считается антипаттерном.Аналогично с шаблонами флажков, радио-кнопок, диалогов, раскрываемых блоков, ссылок, ориентиров, шкалы, слайдера, таблиц и так далее. Они показывают, как реализовать с помощью ARIA. Зачастую в этом нет необходимости.
Отдельного внимания заслуживает борьба Адриана Розелли с шаблоном навигации сайта, который долгое время предлагал использовать не подходящие роли меню. Разработчики внедряли этот шаблон на сайты и делали только хуже.
Всё сказанное выше не значит, что на APG не стоит смотреть. Это неплохой источник информации о проектировании доступных интерфейсов. Важно не воспринимать его как официальный источник истины со 100% правильными шаблонами.
Эрик Бэйли считает хорошими частями в APG:
- Названия шаблонов. Хорошо, когда вся команда говорит на одном языке;
- Описания шаблонов. Суть шаблона, для чего нужен, как работает и когда применять;
- Клавиатурная навигация. Клавиши и их сочетания, которые должны работать при взаимодействии;
Остальное вторично. Таблицы поддержки не отражают реальный уровень поддержки, примеры кода не согласованы, написаны в разных стилях, используют устаревшие подходы и не готовы к продакшену, часть шаблонов заменяется встроенным HTML.
Используйте APG как источник знаний о сути того или иного шаблона и навигации с помощью клавиатуры. По возможности используйте встроенные HTML-элементы, обращайтесь к официальному стандарту ARIA и всегда тестируйте.
#ui #a11y
1👍5❤4🔥3⚡1🌚1😎1
Переусложнённые радио-кнопки в Shadcn
Пол Геберт в блоге написал о чрезмерно переусложнённых радио-кнопках в Shadcn. Это библиотека готовых компонентов для React, которая построена на базе Radix UI, использует Tailwind и работает по принципу «copy-paste» кода в проект.
Предположим, что в проекте нужны радио-кнопки. В HTML это решается добавлением
Если залезать под капот импортируемого
Радио-кнопка в Shadcn — это кнопка с ARIA-атрибутами для имитации радио-кнопки, SVG-круг, скрытый
Применение
Задумка Shadcn и Radix, по всей видимости, в надёжности стилизации. С
#html #css #ui
Пол Геберт в блоге написал о чрезмерно переусложнённых радио-кнопках в Shadcn. Это библиотека готовых компонентов для React, которая построена на базе Radix UI, использует Tailwind и работает по принципу «copy-paste» кода в проект.
Предположим, что в проекте нужны радио-кнопки. В HTML это решается добавлением
<input type="radio">. В Shadcn для этого нужно 3 импорта, 45 строк кода, сторонняя библиотека иконок для кружка, 30 классов Tailwind.Если залезать под капот импортируемого
RadioGroupPrimitive, там ещё 215 строк кода и 7 импортов. Неужели для радио-кнопок нужно столько кода? Но это ладно. Какой результат получается в браузере при отрисовке этого всего?<button
id="debit"
class="классы Tailwind"
type="button"
role="radio"
aria-checked="true"
data-state="checked"
value="debit"
tabindex="0"
data-radix-collection-item
>
<span
class="классы Tailwind"
data-state="checked"
>
<svg
class="классы Tailwind"
xmlns="..."
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10">
</circle>
</svg>
</span>
</button>
<input
aria-hidden="true"
style="..."
tabindex="-1"
type="radio"
value="debit"
checked
name="credit-or-debit"
>
Радио-кнопка в Shadcn — это кнопка с ARIA-атрибутами для имитации радио-кнопки, SVG-круг, скрытый
<input type="radio">, который нужен для работы форм и классы Tailwind. Всё может быть гораздо проще, если использовать стандартный HTML:<input
type="radio"
name="credit-or-debit"
value="debit"
checked
>
Применение
appearance: none к флажкам и радио-кнопкам сбрасывает их стандартный внешний вид. После этого можно задавать размеры, фон, рамку, тень, закругление и так далее. Маркеры можно сделать при помощи псевдо-элементов.<input
type="radio"
name="credit-or-debit"
value="debit"
checked
>
<style>
input[type="radio"] {
--color: currentColor;
appearance: none;
display: inline-grid;
place-content: center;
width: 24px;
height: 24px;
border: 2px solid var(--color);
border-radius: 50%;
margin: 0;
&::before {
content: '';
width: 12px;
height: 12px;
border-radius: 50%;
background-color: transparent;
transition: background-color .3s;
}
&:checked::before {
background-color: var(--color);
}
@media (forced-colors: active) {
--color: ButtonText;
}
}
</style>
Задумка Shadcn и Radix, по всей видимости, в надёжности стилизации. С
<input> дела обстоят хуже, чем с <button>. Но всё же решение с appearance работает сегодня во всех актуальных браузерах. Тогда к чему весь этот оверинжиниринг?#html #css #ui
Paulmakeswebsites
The Incredible Overcomplexity of the Shadcn Radio Button
Radio buttons are built into web browsers. Why are we using a UI library that wraps another UI library that rebuilds radio buttons from scratch? Why does rendering a radio button require multiple dependencies and several kilobytes of JavaScript? How did we…
🔥11👍5❤3🌚3👨💻1
Маркетинговый сайт, веб-компоненты и nanotags
Павел Гринченко в блоге Злых Марсиан описал способ разработки маркетинговых сайтов с использованием веб-компонентов и разработанного для них инструмента под названием nanotags, в основе которого другое решение от Марсиан — nanostores.
Маркетинговый сайт — это не приложение с тяжёлой логикой и большим количеством интерактивности. Это статический контент с точечными интерактивными элементами. Такие сайты создаются с использованием генераторов статики, в частности Astro.
Остаётся вопрос с тем, как добавлять ту самую интерактивность. Павел считает API веб-компонентов, встроенные в браузеры, полезными для этой задачи. Но в сыром виде API низкоуровневые и многословные. Поэтому была создана библиотека nanotags.
Это лёгкая библиотека, которая добавляет декларативный способ создания пользовательских элементов, поиска узлов, объявления реактивных свойств на основе nanostores и обработки событий. Цель — скрыть шаблонный код под капот.
nanotags рассчитан на работу с готовой разметкой, генерируемой на сервере или при сборке. Шаблонизатор отсутствует, Shadow DOM не используется. Это отличает nanotags от популярной библиотеки для веб-компонентов — Lit.
В разметке пользовательский элемент
Затем подключается nanotags. Функция
В
Библиотека хорошо интегрирована с TypeScript, выводит типы, поддерживает Standard Schema валидаторы (Valibot, Zod, ArkType) для свойств, работает с данными в формате JSON, делает проверки в рантайме на основе схем.
Больше возможностей nanotags описано в документации. Это интересный взгляд на то, как работать с веб-компонентами. Миграция сэкономила 100кб JS, что хорошо и позволяет лучше вписываться в бюджеты производительности.
Также я обратил внимание, что библиотека разработана с уважением к стандарту HTML. Ссылки на элементы отмечаются атрибутом
Маленькая деталь, которая, тем не менее, отражает отношение автора к стандартам. Все синтаксические расширения выполнены в соответствии с правилами расширения HTML, без выдуманного синтаксиса с невалидной разметкой. За это от меня респект.
#js #tools
Павел Гринченко в блоге Злых Марсиан описал способ разработки маркетинговых сайтов с использованием веб-компонентов и разработанного для них инструмента под названием nanotags, в основе которого другое решение от Марсиан — nanostores.
Маркетинговый сайт — это не приложение с тяжёлой логикой и большим количеством интерактивности. Это статический контент с точечными интерактивными элементами. Такие сайты создаются с использованием генераторов статики, в частности Astro.
Остаётся вопрос с тем, как добавлять ту самую интерактивность. Павел считает API веб-компонентов, встроенные в браузеры, полезными для этой задачи. Но в сыром виде API низкоуровневые и многословные. Поэтому была создана библиотека nanotags.
Это лёгкая библиотека, которая добавляет декларативный способ создания пользовательских элементов, поиска узлов, объявления реактивных свойств на основе nanostores и обработки событий. Цель — скрыть шаблонный код под капот.
nanotags рассчитан на работу с готовой разметкой, генерируемой на сервере или при сборке. Шаблонизатор отсутствует, Shadow DOM не используется. Это отличает nanotags от популярной библиотеки для веб-компонентов — Lit.
<my-counter count="0">
<span data-ref="display">0</span>
<button data-ref="button">+1</button>
</my-counter>
<script>
import { define } from "nanotags"
define("my-counter")
.withProps(p => ({
count: p.number(0)
}))
.withRefs(r => ({
display: r.one("span"),
button: r.one("button")
}))
.setup(ctx => {
ctx.on(ctx.refs.button, "click", () => {
ctx.props.$count.set(ctx.props.$count.get() + 1)
})
ctx.effect(ctx.props.$count, val => {
ctx.refs.display.textContent = String(val)
})
})
</script>
В разметке пользовательский элемент
<my-counter>, внутри которого <span> и <button>. Разметка генерируется Astro на этапе сборки. Вместо Astro может быть любой генератор статики или серверное решение, которое отдаёт готовый HTML.Затем подключается nanotags. Функция
define() определяет пользовательский элемент. В withProps() указываются реактивные свойства, их тип и начальное значение. В withRefs() задаются ссылки на узлы, которые должны быть в разметке.В
setup() идёт установка обработчиков событий и эффектов, которые меняют значения реактивных свойств. Доступен объект контекста ctx, в котором хранятся свойства, ссылки на узлы и прочая информация о компоненте.Библиотека хорошо интегрирована с TypeScript, выводит типы, поддерживает Standard Schema валидаторы (Valibot, Zod, ArkType) для свойств, работает с данными в формате JSON, делает проверки в рантайме на основе схем.
Больше возможностей nanotags описано в документации. Это интересный взгляд на то, как работать с веб-компонентами. Миграция сэкономила 100кб JS, что хорошо и позволяет лучше вписываться в бюджеты производительности.
Также я обратил внимание, что библиотека разработана с уважением к стандарту HTML. Ссылки на элементы отмечаются атрибутом
data-ref, JSON-данные хранятся в блоках данных, пользовательские элементы названы в соответствии с правилами.Маленькая деталь, которая, тем не менее, отражает отношение автора к стандартам. Все синтаксические расширения выполнены в соответствии с правилами расширения HTML, без выдуманного синтаксиса с невалидной разметкой. За это от меня респект.
#js #tools
evilmartians.com
From React to native web with nanotags: a migration that saved 100 KB—Martian Chronicles, Evil Martians’ team blog
Most marketing sites ship a SPA framework just to toggle a sidebar. Here's how we migrated an Astro site from React and Ark UI to native Web Components: 100 KB less JavaScript, no functionality lost, and a tiny library called nanotags that makes Custom Elements…
🔥3🤔3⚡1❤1
Разработка сайтов, дружелюбных для агентов
ИИ агенты ворвались в нашу жизнь и стали ещё одними потребителями Интернета. В 525 выпуске Веб-стандартов обсуждали статью с web.dev о разработке сайтов, дружелюбных для агентов. Сейчас это набирает популярность.
Агенты взаимодействуют с сайтами одним из трёх способов:
- Анализ скриншота страницы с помощью машинного зрения;
- Анализ HTML-кода страницы и построенного на его основе DOM;
- Анализ дерева доступности, построенного из DOM и CSSOM.
Возможны комбинации этих способов, чтобы разрешить неоднозначные моменты: в разметке может быть кнопка «Добавить в корзину» как
В статье даются советы, которые помогут агентам лучше воспринимать сайт:
- Все действия чётко обозначены в интерфейсе;
- Структура сайта стабильная и консистентная;
- Нет прозрачных наложений поверх интерфейса;
- Структура контента передана с помощью семантического HTML или ARIA, если нет возможности использовать семантический HTML;
- Интерактивные элементы обозначены в CSS с помощью
- Поля формы и подписи к ним связаны через
- Размер интерактивных элементов как минимум 8×8px.
Глядя на эти пункты я понимаю: адаптация сайта для агентов — это просто создание нормального сайт для людей. Все советы были актуальны задолго до бума агентов. Это просто базовые советы как сделать хороший сайт.
Чётко идентифицируемые элементы, стабильная структура, семантический HTML, ARIA для нестандартных виджетов, связь полей с метками, размер элементов, курсор с пальцем и отсутствие наложений — это база при разработке сайтов.
Всё настолько плохо, что теперь это не база, а новая модная дисциплина и навык «адаптация сайта для агентов»? С другой стороны, если это подстегнёт внедрение хороших практик для улучшения сайтов, то почему бы и нет.
Тем, кто всегда стремился к качественным, семантичным, доступным и быстрым сайтам, выбирал готовый HTML и статику вместо SPA и сложных решений — повезло, их сайты уже со старта гораздо лучше адаптированы для агентов.
Стоит сказать, что в рамках Agentic Engine Optimization есть более специфические приёмы, как описывает Эдди Османи. Но они всё равно сводятся к предоставлению краткой выжимки контента, ссылкам и грамотной структуре.
Важно кратко и чётко донести смысл, контент должен быть структурирован с помощью списков и иерархии заголовков, должно быть содержание с кратким описанием и ссылками. То есть всё примерно так же, как и для людей.
#html #css #ui
ИИ агенты ворвались в нашу жизнь и стали ещё одними потребителями Интернета. В 525 выпуске Веб-стандартов обсуждали статью с web.dev о разработке сайтов, дружелюбных для агентов. Сейчас это набирает популярность.
Агенты взаимодействуют с сайтами одним из трёх способов:
- Анализ скриншота страницы с помощью машинного зрения;
- Анализ HTML-кода страницы и построенного на его основе DOM;
- Анализ дерева доступности, построенного из DOM и CSSOM.
Возможны комбинации этих способов, чтобы разрешить неоднозначные моменты: в разметке может быть кнопка «Добавить в корзину» как
<div>, но на скриншоте она будет выглядеть как кнопка, а JS добавит необходимое поведение (не делайте так).В статье даются советы, которые помогут агентам лучше воспринимать сайт:
- Все действия чётко обозначены в интерфейсе;
- Структура сайта стабильная и консистентная;
- Нет прозрачных наложений поверх интерфейса;
- Структура контента передана с помощью семантического HTML или ARIA, если нет возможности использовать семантический HTML;
- Интерактивные элементы обозначены в CSS с помощью
cursor: pointer;- Поля формы и подписи к ним связаны через
<label for="...">;- Размер интерактивных элементов как минимум 8×8px.
Глядя на эти пункты я понимаю: адаптация сайта для агентов — это просто создание нормального сайт для людей. Все советы были актуальны задолго до бума агентов. Это просто базовые советы как сделать хороший сайт.
Чётко идентифицируемые элементы, стабильная структура, семантический HTML, ARIA для нестандартных виджетов, связь полей с метками, размер элементов, курсор с пальцем и отсутствие наложений — это база при разработке сайтов.
Всё настолько плохо, что теперь это не база, а новая модная дисциплина и навык «адаптация сайта для агентов»? С другой стороны, если это подстегнёт внедрение хороших практик для улучшения сайтов, то почему бы и нет.
Тем, кто всегда стремился к качественным, семантичным, доступным и быстрым сайтам, выбирал готовый HTML и статику вместо SPA и сложных решений — повезло, их сайты уже со старта гораздо лучше адаптированы для агентов.
Стоит сказать, что в рамках Agentic Engine Optimization есть более специфические приёмы, как описывает Эдди Османи. Но они всё равно сводятся к предоставлению краткой выжимки контента, ссылкам и грамотной структуре.
Важно кратко и чётко донести смысл, контент должен быть структурирован с помощью списков и иерархии заголовков, должно быть содержание с кратким описанием и ссылками. То есть всё примерно так же, как и для людей.
#html #css #ui
❤7👍4😁1🤔1
15й Всемирный День Осведомлённости о Доступности
Сегодня, 21 мая 2026 — всемирный день осведомлённости о доступности (Global Accessibility Awareness Day — GAAD). Это отличный повод напомнить о важности доступности и поделиться материалами на эту тему.
Я регулярно пишу в канале разные посты на тему доступности в вебе и отмечаю эти посты тегом #a11y@alexnozer_dev. К сожалению, теги в канале появились не сразу, поэтому часть материалов о доступности не помечена.
В конце марта вышел ежегодный отчёт The WebAIM Million с результатами анализа доступности миллиона сайтов с помощью инструмента WAVE. Также данные о состоянии доступности можно найти в отчёте Web Almanac 2025 от HttpArchive.
По итогам WebAIM 2026 в топе проблем из года в год один и те же, только некоторые пункты меняются местами. В этом году список такой:
- Недостаточный уровень контрастности текста на 83.9% сайтов;
- Отсутствующий альтернативный текст у изображений на 53.1% сайтов;
- Отсутствующие подписи у полей ввода на 51% сайтов;
- Пустые ссылки на 46.3% сайтов;
- Пустые кнопки на 30.6% сайтов;
- Отсутствующий язык документа на 13.5% сайтов.
Это лишь автоматически обнаруживаемые проблемы. Часть проблем не может быть обнаружена автоматическими средствами анализа. По каждой проблеме из топа планирую сделать отдельный пост, их не сложно исправить.
Есть папки с телеграм-каналами о доступности, собранные Верой Шингарёвой:
- Accessibility-папка — каналы, которые пишут про доступность или около;
- a11y_person — блоги людей с инвалидностью и без, которые пишут про доступность, инклюзию свою жизнь;
- a11y_org — организации в сфере доступности и инклюзии.
У Стаса Мельникова есть интересная серия статей про HTML и CSS ошибки, которые влияют на доступность (вся серия доступна по тегу #html_css_a11y_story_melnik909). Стас вместе со своим знакомым Ильёй, незрячим человеком, делятся распространёнными ошибками и как их исправить.
#a11y
Сегодня, 21 мая 2026 — всемирный день осведомлённости о доступности (Global Accessibility Awareness Day — GAAD). Это отличный повод напомнить о важности доступности и поделиться материалами на эту тему.
Я регулярно пишу в канале разные посты на тему доступности в вебе и отмечаю эти посты тегом #a11y@alexnozer_dev. К сожалению, теги в канале появились не сразу, поэтому часть материалов о доступности не помечена.
В конце марта вышел ежегодный отчёт The WebAIM Million с результатами анализа доступности миллиона сайтов с помощью инструмента WAVE. Также данные о состоянии доступности можно найти в отчёте Web Almanac 2025 от HttpArchive.
По итогам WebAIM 2026 в топе проблем из года в год один и те же, только некоторые пункты меняются местами. В этом году список такой:
- Недостаточный уровень контрастности текста на 83.9% сайтов;
- Отсутствующий альтернативный текст у изображений на 53.1% сайтов;
- Отсутствующие подписи у полей ввода на 51% сайтов;
- Пустые ссылки на 46.3% сайтов;
- Пустые кнопки на 30.6% сайтов;
- Отсутствующий язык документа на 13.5% сайтов.
Это лишь автоматически обнаруживаемые проблемы. Часть проблем не может быть обнаружена автоматическими средствами анализа. По каждой проблеме из топа планирую сделать отдельный пост, их не сложно исправить.
Есть папки с телеграм-каналами о доступности, собранные Верой Шингарёвой:
- Accessibility-папка — каналы, которые пишут про доступность или около;
- a11y_person — блоги людей с инвалидностью и без, которые пишут про доступность, инклюзию свою жизнь;
- a11y_org — организации в сфере доступности и инклюзии.
У Стаса Мельникова есть интересная серия статей про HTML и CSS ошибки, которые влияют на доступность (вся серия доступна по тегу #html_css_a11y_story_melnik909). Стас вместе со своим знакомым Ильёй, незрячим человеком, делятся распространёнными ошибками и как их исправить.
#a11y
1🔥4❤1🥰1🎉1