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

Автор: @alexnozer
Download Telegram
Старые технологии и производительность

В октябре прошлого года Уэс Босс записал видео о сайте частной американской компании McMasters-Carr, которая занимается поставками оборудования, инструментов, запчастей и сырья для технического обслуживания.

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

Команда разработчиков хорошо оптимизировала сайт. Разметка генерируется на сервере. При наведении на ссылки страницы предварительно загружаются. Используется Service Wokrer для кэширования и CDN для эффективной доставки.

Применяется техника критического CSS, который встраивается прямо на страницу. JS разделяется по страницам и загружается только там, где нужно. Подход App Shell позволяет перерисовывать только основную часть страницы.

Ещё интереснее стек: ASP.NET, YUI (Yahoo UI Library) и jQuery, то есть устаревшие по современным меркам технологии. И это не мешает сайту работать быстро и эффективно. Всё дело в том, как используются эти технологии.

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

#performance
👍105👎1
Идеальных контрольных точек не существует

С момента первых свёрстанных макетов меня беспокоит тема контрольных точек (брейкпоинтов) для медиа-запросов. Какие значения использовать? По какому принципу их выбирать? Сколько их должно быть?

Наиболее популярна система контрольных точек Bootstrap: 576-768-922-1200-1400. Я придерживаюсь вариации системы 600-900-1200-1800 из статьи «The 100% correct way to do CSS breakpoints» Дэвида Гилбертсона, она показалась мне более логичной.

Предустановленная тема Dawn в магазинах на платформе Shopify использует систему 749/750-989/990 и двумя дополнительными контрольными точками: 650 и 1200. В интернете есть статьи и видео, где люди делятся собственными подходами.

Так какую же систему выбрать? В Set Studio пришли к выводу: идеальных контрольных точек не существует. В рамках эксперимента было собрано более 120000 источников, из которых получилось более 2300 уникальных размеров экранов.

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

К этому добавляется огромное разнообразие моделей мониторов, ноутбуков, планшетов, смартфонов и часов. Плюс разные режимы отображения: разделённый экран, предпросмотр ссылок, увеличенный масштаб, упрощённый режим, Picture-in-Picture.

Этот факт подводит к мысли об отказе от контрольных точек для медиа-запросов. Пусть @media используется по прямому назначению — определению медиа-функций prefers-*, print, pointer, forced-colors, orientation, color-gamut и других.

А вместо контрольных точек применить «алгоритмическую вёрстку» — описание в CSS некоторых правил (алгоритмов), по которым браузер сам решает, как размещать и отображать элементы. Подробнее об этом расскажу в отдельном посте.

#css
116👍6🔥3
Алгоритмическая вёрстка

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

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

Хейдон Пикеринг в своём видео Making Future Interfaces: Algorithmic Layouts демонстрирует суть на примере макета с боковой колонкой. Позже идея получила развитие в книге Every Layout за авторством Хейдона и Энди Бэлла.

Предположим, что нужно создать сетку с карточками товаров. На широких экранах карточки должны отображаться в несколько колонок. Чем у́же экран, тем меньше колонок. Классический подход — использовать grid и медиа-запросы:

.product-grid {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
}

@media (width > 600px) {
.product-grid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (width > 900px) {
.product-grid {
grid-template-columns: repeat(3, 1fr);
}
}

@media (width > 1400px) {
.product-grid {
grid-template-columns: repeat(4, 1fr);
}
}


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

.product-grid {
--columns: if(
media(width > 1400px): 4;
media(width > 900px): 3;
media(width > 600px): 2;
);
display: grid;
grid-template-columns: repeat(var(--columns, 1), 1fr);
gap: 30px;
}


Вместо изменения количества колонок в контрольных точках можно задать алгоритм построения сетки с автоматическими колонками из элементов, ширина которых ограничена минимальным значением:

.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 30px;
}


Браузер получает алгоритм: «автоматически создавай колонку, если в неё помещается элемент шириной от 200px до одной доли доступного пространства, остальные элементы переноси на новую строку».

Отступы, поля, размеры шрифта, закругления тоже можно описать в виде алгоритма c использованием функций calc(), min(), max(), clamp(). На этом, в частности, основана «гибкая типографика» (fluid typography):

.noscript {
font-size: clamp(2rem, 4vw + 1rem, 3rem);
}


Алгоритм для браузера: «рассчитай размер шрифта на основе текущей ширины области просмотра и размера шрифта корневого элемента, но не менее 2rem и не более 3rem». Размер шрифта будет меняться в зависимости от ширины экрана.

Больше примеров алгоритмической вёрстки:

- 1-Line Layouts
- SmolCSS
- Container Query Solutions with CSS Grid and Flexbox
- Modern Fluid Typography Using CSS Clamp
- Utopia Fluid Responsive Design
- Every Layout

#css
🔥236🗿5👍31🤔1🤡1
Obs.js

Гарри Робертс поделился коротким видео, в котором рассказал о разработке библиотеки с открытым исходным кодом Obs.js. Слоган библиотеки — контекстно-зависимая производительность для всех.

Context-aware web performance for everyone


Obs.js подключается к странице и использует различные API для определения текущих возможностей устройства: качество соединения, сетевую задержку, состояние батареи, заряжается ли устройство, доступную память и CPU.

В результате объект window.obs заполняется соответствующей информацией, а на <html> добавляются классы вида .has-bandwidth-high, .has-battery-charging, .has-cpu-high, .has-latency-medium и другие.

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

Подход Obs.js похож на некогда популярную библиотеку Modernizr, но направлен на определение функций производительности. Есть демо-страница, на которой можно проверить своё устройство.

#js #performance
👍13🔥52💩1
День знаний

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

- Learn web development от MDN. Структурированный набор обучающих материалов по основам веб-разработки от команды MDN. Не так давно MDN получил обновлённый дизайн с улучшенной типографикой, блоками кода, иконками, поиском и навигацией;
- Learn web development от web.dev. Набор небольших курсов по HTML, CSS, JS, производительности, приватности, доступности, изображениям, дизайну, формам, PWA и тестированию от web.dev и команды деврелов Chrome;
- Дока. Дружелюбный справочник по веб-разработке, создаваемый силами сообщества;

Игровой формат:
- CSS Diner. Игра, где с помощью CSS-селекторов необходимо выбрать нужные элементы на виртуальном столе;
- Flexbox Froggy. Игра, в которой с помощью Flexbox нужно помочь лягушкам добраться до своих лилий;
- Grid Garden. Игра, где нужно выращивать морковку, используя свойства CSS Grid.
- Anchoreum. Игра, обучающая работе с новым Anchor Positioning API, где нужно правильно разместить якоря.

Также напомню про подборку ссылок от Алекса Рассела на различные блоги, статьи и видео о веб-платформе, а также небольшую подборку материалов по доступности, которую я делал на 14й Всемирный День Информирования о Доступности.

#html #css #js
👍116
You don’t know HTML: <label>

Элемент <label> представляет подпись для элементов управления, которые относятся к категории labelable element. Подпись идентифицирует элемент, кратко описывает назначение и используется как accessible name в дереве доступности.

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

<label for="email">
Email
</label>
<input
type="email"
id="email"
autocomplete="email"
>


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

<label>
Email
<input
type="email"
id="email"
autocomplete="email"
>
</label>


В обоих подходах элемент получит accessible name «Email». Программы чтения с экрана объявят имя при переходе к элементу. С помощью программ голосового управления можно обратиться к элементу по его имени.

<label> переопределяет accessible name, полученный из атрибута noscript или текстового контента элемента, как в случае с кнопками. Атрибуты aria-label и aria-labelledby приоритетнее, поэтому они переопределяют значение из <label>.

Нажатие по <label> активирует связанный элемент. Для флажков и радио-кнопок — это изменение состояния отмечен/не отмечен, для текстовых полей — установка фокуса, для кнопки — нажатие, для выбора файла — активация окна выбора.

К одному элементу можно привязать несколько <label>, которые будут объединяться в единую подпись. Это можно использовать для комплексных подписей с дополнительными инструкциями, которые размещены в разных местах:

<label for="birthday">
Дата рождения
</label>
<input
type="text"
id="birthday"
autocomplete="bday"
pattern="\d{2}\/\d{2}\/\d{4}"
>
<label for="birthday">
(формат: ДД/ММ/ГГГГ)
</label>

<!--
Accessible name:
Дата рождения (формат: ДД/ММ/ГГГГ)
-->


Создавать комплексные подписи можно путём размещения всего текста внутри одного элемента <label>. Accessible name элемента управления будет генерироваться на основе всего текстового содержимого внутри <label>.

<label>
<span>Дата рождения</span>
<input
type="text"
id="birthday"
autocomplete="bday"
pattern="\d{2}\/\d{2}\/\d{4}"
>
<small>(формат: ДД/ММ/ГГГГ)</small>
</label>

<!--
Accessible name:
Дата рождения (формат: ДД/ММ/ГГГГ)
-->


<label> можно привязывать к пользовательским элементам, если они помечены как ассоциированные с формой с помощью свойства formAssociated. Но <label> нельзя привязать к элементу внутри Shadow DOM (на данный момент).

<label for="custom-checkbox">
Согласен получать спам
</label>
<custom-checkbox
id="custom-checkbox"
checked
>
</custom-checkbox>

<label for="custom-input">
Email для спама
</label>
<custom-input>
<template shadowrootmode="open">
<!-- подпись не привяжется -->
<input
type="email"
id="custom-input"
autocomplete="email"
>
</template>
</custom-input>


В JS у <label> есть два свойства. control возвращает элемент, которому привязан <label>. Свойство form возвращает элемент <form>, к которому относится связанный с <label> элемент управления.

У самих элементов управления есть свойство labels, которое возвращает коллекцию NodeList со связанными элементами <label>. У пользовательских элементов такого свойства нет, но оно доступно через объект ElementInternals.

#ydkhtml #html #a11y
👍193
Вы неправильно загружаете шрифты

Джоно Олдерсон написал исчерпывающую статью «You’re loading fonts wrong (and it’s crippling your performance)» о шрифтах. От исторического экскурса до современных подходов. Советую прочитать статью полностью. Основные принципы кратко:

- Системный стек. Начните с хорошего набора системных шрифтов и используйте пользовательские шрифты как прогрессивное улучшение. Если брендинг не важен, рассмотрите использование только системных шрифтов;

- Нарезка на подмножества. Нарежьте монолитный файл шрифта на несколько подмножеств («бандлов») под разные языки и загружайте только те подмножества, которые нужны на странице;

- Предварительная загрузка и встраивание. Предоставьте браузеру информацию о критических шрифтах как можно раньше с помощью Early Hints, <link rel="preload"> и встраивания @font-face в <style>;

- WOFF2. Используйте шрифты в современном формате woff2, не подключайте в @font-face форматы ttf, otf, eot, noscript. Если необходима поддержка старых браузеров, добавьте к woff2 шрифт в формате woff;

- SVG для иконок. Не используйте иконочные шрифты, такие как Font Awesome для добавления иконок. SVG поддерживается во всех браузерах, хорошо подходит для иконок и позволяет объединить их в один файл (спрайт);

- Вариативные шрифты. Используйте вариативные шрифты, если на сайте требуется тонкая настройка параметров (толщина, наклон, ширина) или используется много вариаций одного шрифта;

- Настройка резервных вариантов. Подберите настройки метрик для резервных шрифтов, чтобы внешне они максимально совпадали с пользовательским шрифтами во избежание прыжков макета при перерисовке;

- Глобальные сценарии. Учитывайте особенности и применяйте индивидуальные оптимизации для арабских языков, языков группы CJK, разных направлений написания и эмодзи;

- Тестирование. Проверяйте поведение и отображение шрифтов на различных устройствах, сетях и языках.

Шрифты — это не украшение, а часть инфраструктуры. Они находятся на критическом пути рендеринга, влияют на метрики LCP и CLS, представляют брендинг и влияют на то, смогут ли пользователи прочитать контент.

#css #performance
👍163
Псевдо-кнопки

Марианна Минич пишет:

На какие угодно ухищрения готовы пойти разработчики, лишь бы не использовать семантичную <button> 😅


И сопровождается пост подобным примером:

<div
tabindex="0"
role="button"
class="button"
(mouseenter)="onMouseEnter"
(mouseleave)="onMouseLeave"
(click)="onClick"
(keydown)="onKeyDown"
[attr.aria-pressed]="isPressed"
[attr.aria-label]="label"
...
>
...
</div>


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

- tabindex="0", чтобы не фокусируемый <div> попал в последовательность табуляции и стал доступен для клавиатуры;
- role="button", чтобы семантически нейтральный <div> попал в дерево доступности как кнопка и корректно обрабатывался вспомогательными технологиями;
- keydown, чтобы нажатия клавиш Enter и Space приводили к нажатию на <div> и вызову события click;
- aria-pressed, чтобы передавать в дерево доступности информацию о том, находится ли кнопка в нажатом состоянии;
- aria-label, чтобы задать кнопке имя для дерева доступности, которое переопределяет текстовый контент внутри (если его нет, как в случае с кнопками-иконками).

Эти манипуляции нужны, чтобы «исправить» кнопки и воспроизвести поведение <button> у <div>. Но это не всё: отправка, сброс и переопределение параметров формы, отключение, управление поповером и вызов команд — это то, что сегодня умеет <button>.

Поэтому просто используйте <button>! Причина создания псевдо-кнопок из <div> в стилизации. У <button> специфические стили, которые отличаются от браузера к браузеру. У <div> таких стилей нет и всё одинаково во всех браузерах.

Вместо написания лишнего JS для воспроизведения поведения у <div>, куда проще сделать сброс стилей у <button>, применив all: unset. Важно вернуть стандартную рамку фокуса (или реализовать свою), так как all: unset сбрасывает её:

button {
all: unset;
}

button:focus-visible {
outline: auto;
}


Два CSS-свойства вместо десятков строк кода на JS и полотна из атрибутов в HTML (или директив в шаблоне компонента фреймворка), чтобы получить стилизуемую, семантическую, доступную кнопку со всеми её встроенными возможностями и API.

#html #css #a11y
👍2810❤‍🔥2👏2🤡1
Современный CSS

Предлагаю запись доклада Адама Аргайла с прошедшего в апреле митапа Smashing Meets CSS. Адам рассказал о 33-х новых возможностях CSS. Практически каждая функция сопровождается демонстрациями на codepen, где можно изучить код и скопировать его в собственные проекты.

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

#css
14👍7🌚1👾1
Меню без скриптов

Я уже писал о CSS-хаках и раскрывал эту тему на примере вкладок на основе ссылок и псевдо-класса :target. Продолжу разбором решения из статьи «меню для отзывчивого интерфейса без скриптов» с очередным хаком.

В этом руководстве мы покажем, как сделать отзывчивое mobile-first меню только с помощью HTML и CSS.


По вводному предложению можно подумать, что статья расскажет о новых Popover и Anchor Positioning, с помощью которых действительно можно создать меню только с помощью HTML и CSS. Но статья про старый добрый хак с флажком.

<!-- Hamburger icon -->
<input class="side-menu" type="checkbox" id="side-menu"/>
<label class="hamb" for="side-menu"><span class="hamb-line"></span></label>
<!-- Menu -->
<nav class="nav">


<label> привязывается к скрытому флажку и меняет его состояние при нажатии. Псевдо-класс :checked позволяет отследить отмеченное состояние флажка и через комбинатор соседства ~ применить стили к нижележащему элементу <nav>.

.side-menu {
display: none;
} /* Hide checkbox */

/* Toggle menu icon */
.side-menu:checked ~ nav{
max-height: 100%;
}


Таким меню невозможно воспользоваться с клавиатуры, так как флажок скрыт, а <label> не фокусируемый. Можно скрыть флажок классом visually-hidden и добавить видимый индикатор фокуса, тогда меню можно будет открыть с клавиатуры.

<label> не содержит текста, поэтому у флажка нет доступного имени. Пользователи программ чтения с экрана услышат что-то вроде «checkbox, not checked» и это их только запутает. Также не получиться активировать флажок голосовым управлением.

Доступность — важный аспект веб-разработки. Он гарантирует, что ваш сайт будет удобным для пользователей с ограниченными возможностями. Атрибуты ARIA (Accessible Rich Internet Applications) играют ключевую роль в этом.


В статье есть раздел про улучшение доступности. На <label> установлен атрибут aria-label="Menu", что исправляет проблему с безымянным флажком. Но в этом нет смысла, так как флажок скрыт, а <label> не интерактивный.

Добавлены роли banner и navigation для <header> и <nav>, у которых они встроены по умолчанию. На <nav> установлен атрибут aria-label="Main", a на подменю — aria-haspopup. При этом подменю отображается при ховере и фокусе без передачи соответствующего состояния.

Добавления ARIA-атрибутов не сделало меню доступным. Флажок скрыт и недоступен для клавиатуры, программ чтения с экрана и голосового управления. Не передаётся состояние aria-expanded. Подменю работают при ховере и фокусе. Анимации не сокращены.

Это плохое меню с точки зрения пользовательского опыта и доступности. Стоит ли погоня за реализацией «без JS» сломанного опыта для пользователей? Для меня ответ очевиден: не стоит. Хаки есть хаки, применять их в интерфейсах не стоит.

#html #css #a11y #ui
👍71👾1
System design и выбор фреймворков

Недавно прошёл 13й митап сообщества MinskJS, где по традиции было три доклада. Хочу отметить первые два, которые дополняют друг друга. Василий Ванчук рассказал про System Design для фронтендеров, а Анна Ширяева про выбор фреймворков.

Оба доклада так или иначе затрагивают темы кругозора и осознанного выбора инструментов и подходов для решения задачи. Периодически я пишу о выборе неподходящих инструментов и вытекающих из этого последствиях.

#js
5🔥3👍2
You don’t know HTML: виды скриптов

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

<noscript>

Классический скрипт. Выполняется сразу же при обнаружении парсером и блокирует его.

<noscript type="not-javanoscript">

Если значение атрибута type не равно одному из MIME-типов JS или одному из ключевых слов, то это блок данных. Код в теле <noscript> не обрабатывается и не выполняется, что можно использовать для хранения данных.

<noscript src="/noscript.js">

Как классический скрипт, но загружается внешний файл. Во время загрузки и обработки парсер блокируется. Тело элемента <noscript> с атрибутом src игнорируется, даже если там JS. Поэтому внутри можно оставить комментарии и пояснения.

<noscript src="/noscript.js" async>

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

<noscript src="/noscript.js" defer>

Атрибут defer не блокирует парсер во время загрузки скрипта, но откладывает выполнение до момента готовности DOM. Отложенные скрипты выполняются в том же порядке, в котором они расположены в HTML, что отличает defer от async.

<noscript src="/noscript.js" async defer>

Если у скрипта одновременно указаны атрибуты async и defer, то defer игнорируется. Поведение будет таким же, как у <noscript src="/noscript.js" async>.

<noscript src="/noscript.js" type="module">

Скрипт с атрибутом type="module" обрабатывается как ES-модуль, где работают операторы import и export. Такие скрипты не блокируют парсер во время загрузки и выполняются по готовности DOM, при этом все зависимые модули должны загрузиться.

<noscript src="/noscript.js" type="module" async></noscript>

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

<noscript src="/noscript.js" nomodule></noscript>

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

<noscript type="importmap">

Атрибут type="importmap" сообщает браузеру, что тело скрипта — карта импортов. Это JSON-объект с псевдонимами путей к модулям, которые используются в операторах import и export. В современных браузерах можно определить несколько карт импортов.

<noscript type="speculationrules">

Атрибут type="speculationrules" сообщает браузеру, что тело скрипта — правила спекулятивной загрузки. Браузеры заранее загружают страницы при выполнении указанных условий, что приводит к мгновенной загрузке страниц и ресурсов.

Атрибуты src, async, nomodule и defer не должны применяться для type="not-javanoscript", type="importmap" и type="speculationrules", поэтому их использование не окажет никакого влияния.

#ydkhtml #js
116👍9🤓2👾1
Web Interface Guidelines

Vercel поделилась списком лучших практик дизайна и разработки веб-интерфейсов. Список разделён на категории: взаимодействие, анимации, раскладка, контент, формы, производительность, дизайн и копирайтинг.

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

Vercel также предлагает готовый файл AGENTS.md с адаптированными для LLM рекомендациями. Его можно использовать для расширения контекста агентов, чтобы они следовали этим практикам при генерации кода.

#ui
👍7👾3😁1🌚1
Плавающие подписи

В интерфейсах часто встречаются так называемые плавающие подписи (floating labels). Это когда подпись отображается поверх поля ввода как заполнитель (placeholder), а при попадании фокуса плавно съезжает вверх и уменьшается в размерах.

Этот подход стал популярным благодаря дизайн-системе Material Design от Google. Он сохраняется в актуальной на сегодняшний день 3й версии. Его также можно встретить в других дизайн-системах и библиотеках компонентов.

Задумка плавающих подписей — компактность, учитывая что Material Design разработан в том числе для Android. Можно было бы использовать стандартный заполнитель, но это не лучшая идея. Плавающие подписи лучше, чем заполнители.

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

<label for="name">
Имя
</label>
<input
type="text"
id="name"
autocomplete="given-name"
autocapitalize="words"
spellcheck="false"
>

<!--
Имя
[___________]
-->


Недостатки плавающих полей:

1. Структура HTML. <label> нужно размещать после <input> и использовать селектор с комбинатором ~ или +. Или воспользоваться относительно новым селектором :has():
input:is(:not(:placeholder-shown), :focus) + label {
/* ... */
}

label:has(+ input:is(:not(:placeholder-shown), :focus)) {
/* ... */
}


2. Переполнение. Длина подписи может оказаться больше длины поля. Хотя длинные подписи — плохая практика с точки зрения UX, такое иногда встречается. Перенос подписи ломает смещение и перекрывает введённое значение, с overflow: hidden теряется информация, а торчащая за пределы поля подпись выглядит не красиво.

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

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

5. Размер шрифта. Наряду со смещением подписи вверх, также принято уменьшать её размер. Это может привести к слишком мелкому тексту, который трудно читать людям с нарушениями зрения. Увеличение размера текста в настройках либо ни к чему не приведёт, либо сломает плавающие подписи.

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

Это оправдано, потому что в хороших формах не должно быть много полей, либо они должны разбиваться на шаги по принципу one thing per page. К тому же вертикальная прокрутка никуда не делась, форму с видимыми подписями можно прокрутить.

#ux #a11y
14🥰1😢1👾1
Enhanced Range Input

Во всех браузерах уже достаточно давно существует элемент <input type="range">, отображающий слайдер с ползунком для изменения значения в заданном диапазоне. Элемент вполне можно использовать, но есть ряд проблем:

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

Из-за этого стандартный слайдер заменяют JS-библиотеками, вроде noUiSlider. Предложение Enhanced Range Input от OpenUI призвано решить проблемы и предоставить нативное решение.

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


Первое предложение связано со спецификацией CSS Form Control Styling, о которой я уже рассказывал. Это возможность включить «базовый» режим для стандартных элементов управления с помощью свойства appearance.

В «базовом» режиме элемент управления получает набор минималистичных стилей, которые стандартизированы и одинаковы для всех браузеров. Также «базовый» режим открывает доступ к новым псевдо-элементам для стилизации внутренних частей.

input[type="range"] {
appearance: base;

::slider-track {
/* Шкала, по которой перемещается ползунок */
}

::slider-fill {
/* Заполненная часть шкалы */
}

::slider-thumb {
/* Ползунок */
}

::slider-segment {
/* Часть шкалы между двумя ползунками */
}

::slider-tick {
/* Засечки при использовании <datalist> */
}

::slider-tick-label {
/* Подпись засечки при использовании <datalist> */
}
}


Далее предлагается новый элемент <rangegroup> для создания слайдера с несколькими ползунками. Это контейнер, который оркестрирует элементами <input type="range"> и отображает единый элемент управления.

<rangegroup>
<legend>Цена</legend>

<label for="price-min">
Минимальная цена
</label>
<input
type="range"
id="price-min"
name="price-min"
value="25"
>

<label for="price-max">
Максимальная цена
</label>
<input
type="range"
id="price-max"
name="price-max"
value="75"
>
</rangegroup>


У элемента <rangegroup> можно указать атрибуты min, max, name, list и stepbetween. Первые четыре работают как у <input type="range">, но определяют общий диапазон. stepbetween задаёт минимальную дистанцию между двумя ползунками.

Атрибуты min, max, value, name и step у <input> будут корректно взаимодействовать с атрибутами у <rangegroup>. Так min и max у <rangegroup> ограничивают min и max у <input>, чтобы они не выходили за пределы диапазона.

Предусмотрена возможность привязки элемента <datalist> с набором <option> к <rangegroup> для создания засечек с подсказками, к которым «магнитятся» ползунки. С псевдо-элементами ::slider-tick и ::slider-tick-label их можно стилизовать.

Промежутки между несколькими ползунками также можно стилизовать, используя новый псевдо-элемент ::slider-segment в сочетании с псевдо-классами :nth-child. Аналогично можно по-разному стилизовать каждый ползунок.

Для работы с диапазонами и несколькими значениями у <rangegroup> будут соответствующие свойства и методы. Никуда не исчезнет API самих элементов <input type="range">, с которыми можно работать, получив их через <rangegroup>.

const rangeGroup = document.querySelector(
'rangegroup[name="price-range"]'
);

// Получение значений
console.log(rangeGroup.values);
// [100, 750]

// Получение слайдеров
const inputs = rangeGroup.inputs;
console.log(inputs.length);
// 2

// Установка значений
rangeGroup.setRangeValue(0, 150);
console.log(rangeGroup.values);
// [150, 750]


Предложение развивается и есть ряд открытых вопросов. Примеры кода и API можно посмотреть в самом предложении. Также автор подготовил прототип в виде веб-компонента и интерактивную площадку с примерами. Ждём реализацию в браузерах.

#html #css #js #ui
👍10🔥4👾21🌚1
Atomic Accessibility

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

Atomic Accessibility предлагает атомарный подход к требованиям соответствия. Сначала нужно выбирать роль: дизайнер, веб-, iOS- или Android-разработчик. Для каждой роли есть список элементов с разбивкой по категориям.

Внутри каждой категории находятся конкретные атомарные элементы интерфейса: шапка, форма поиска, хлебные крошки, пагинация, поле ввода пароля, диалоговое окно, флажки, вкладки, подвал и так далее.

У каждого элемента есть критерии соответствия. Для дизайнеров — требования WCAG и нарушения, которые затрагивает элемент. Для разработчиков — ожидаемое поведение клавиатуры, скринридеров и настроек системы.

Информация выводятся в текстовое поле, откуда её можно скопировать и вставить в техническое задание, описание задачи, чеклист приёмки, комментарии в ревью на GitHub, инструкции к нейросетям и агентам или куда-то ещё.

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

#a11y
9👍6🤔2🌚1👾1
Custom ident и dashed ident

В CSS есть два типа значений: <custom-ident> и <dashed-ident>. Оба типа позволяют создавать произвольные идентификаторы для некоторых сущностей. В отличие от строкового типа, для идентификаторов не требуются кавычки.

<custom-ident> используется для именования счётчиков, ключевых кадров, линий и областей сетки. Это значения list, bounce, logo, nav и search в следующем примере:

ul {
counter-reset: list;
}

li {
counter-increment: list;
}

li::before {
content: counter(list);
}

@keyframes bounce {
/* ... */
}

.bounce {
animation-name: bounce;
/* ... */
}

.header {
grid-template-area: 'logo nav search' ;
}

.header__logo {
grid-area: logo;
}

.header__nav {
grid-area: nav;
}

.header__search {
grid-area: search;
}


Тип <dashed-ident> будет знаком по синтаксису пользовательских свойств. Это такой же произвольный идентификатор, но который должен начинаться с двух дефисов --. Не всем нравятся эти лишние символы, но на то есть ряд исторических причин.

Многие новые возможности CSS используют тип <dashed-ident> для именования. Среди них view-transition-name, anchor-name, anchor-target, view-timeline-name, scroll-timeline-name, функции и миксины.

Кевин Пауэлл в видеокасте рассуждает о статье Ромы Комарова об именовании сущностей. Как не запутаться, где нужно использовать <custom-ident>, а где <dashed-ident>? Рома предлагает решение.

Дело в том, что <custom-ident> может содержать в начале два тире, потому что тире — это обычный символ, который разрешён в идентификаторах. Поэтому все пользовательские идентификаторы можно начинать с --.

Тогда не имеет значения, это <custom-ident> или <dashed-ident>, синтаксис будет одинаковый, а запоминать разницу не придётся. Хотя такой подход странновато выглядит в именованных областях и линиях сетки, он имеет право на жизнь.

ul {
  counter-reset: --list;
}

li {
  counter-increment: --list;
}

li::before {
  content: counter(--list);
}

@keyframes --bounce {
  /* ... */
}

.bounce {
  animation-name: --bounce;
  /* ... */
}

.header {
  grid-template-area:
'--logo --nav --search'
;
}

.header__logo {
  grid-area: --logo;
}

.header__nav {
  grid-area: --nav;
}

.header__search {
  grid-area: --search;
}

#css
🔥4🤔21👾1
Nerdy Notebook

Адам Аргайл запустил проект Nerdy Notebook, где он собирает различные фрагменты CSS. Так как Адам работал в команде Chrome DevRel и отвечал за контент про CSS и UI, в заметках много современных возможностей.

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

#css #ui
🔥124👍1🌚1
Атрибуты 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