10 ошибок, которые ИИ допускает в веб-разработке
«Модели не соблюдают современные практики веб-разработки по умолчанию» — команда Google Chrome превратила это утверждение в измеримый факт и опубликовала список из десяти вещей, которые кодинг-агенты делают чаще всего неправильно. Автор списка — Филип Уолтон. Поводом стал выход версии 1.0 набора скиллов Modern Web Guidance, который встраивает в агентов экспертизу по веб-платформе.
Метод простой. Один и тот же промпт прогоняется через два агента: «с руководством» и без. Результат проверяется автоматическими проверками из каждого гайда. Без скиллов агенты проходят около половины проверок: 52% против 91% со скиллами (данные на 9 ноября 2026 года). Полные результаты собраны на дашборде.
Дальше — сами ошибки. Они делятся на четыре группы: доступность, UX, производительность и безопасность.
1. Доступность: порядок фокуса и атрибут inert
Без правильного порядка фокуса интерфейсом невозможно пользоваться с клавиатуры или через скринридер. Типовые промахи моделей:
- при открытии нового экрана фокус не переносится в него, а при возврате назад — не восстанавливается;
- ловушка табуляции и
aria-modal="true"строятся вручную на JavaScript, хотя есть модальный<dialog>и атрибутinert; - каждый элемент тулбара или таблицы становится отдельной точкой фокуса, вместо «бегущего» tabindex (roving tabindex);
- собственные обводки
:focus-visibleне дают контраст 3:1.
2. Доступность: поддержка prefers-reduced-motion
Анимации у пользователей с чувствительным вестибулярным аппаратом вызывают головокружение и тошноту. Базовые transition модели обрабатывают правильно, но новые возможности платформы проваливают: безусловный глобальный scroll-behavior: smooth; CSS-анимации, привязанные к скроллу (scroll-driven animations), без проверки этой настройки; удаление у карусели transition без удаления transform. Ещё две ошибки похуже. Первая: сброс всех анимаций до 0.01ms через * и псевдоэлементы. Такой сброс заодно убивает важные переходы, не связанные с движением. Вторая: включение cross-document view transitions без проверки, что пользователь не отключил анимации.
3. Доступность: валидация форм и подсказки
Основы в порядке, детали — нет:
- модели дублируют поведение CSS-селектора
:user-invalidвручную через JavaScript-валидацию на событииblur— и помечают ошибкой поля, которые пользователь ещё не трогал; - атрибут
aria-invalidрасходится с видимой ошибкой на экране; - подсказки формата прячутся в исчезающие плейсхолдеры вместо постоянных подсказок через
aria-describedby; - подсказки ставятся под полем — там их перекрывает мобильная клавиатура или поповер автозаполнения;
- стилизация
:autofillделается только цветом рамки, без нецветовой подсказки вроде внутренней тени (box-shadow).
4. UX: свайпы и жесты
Код жестов на JavaScript работает, но ощущается грубее нативного. Модели пишут обработчики pointermove и touchmove там, где платформа предлагает CSS Scroll Snap, — для «смахнул, чтобы закрыть» и «смахнул, чтобы открыть». На свайпах между табами пропадает overscroll-behavior-x: none, и случайное движение уводит пользователя назад или вперёд по истории. Прогресс свайпа считается на главном потоке JavaScript вместо анимаций, привязанных к скроллу. Свайп-панели на скролле не получают поддержки клавиатурного фокуса.
5. UX: диалоги, поповеры и top layer
Здесь модели вручную воспроизводят то, что платформа делает сама: самодельные оверлеи на position: fixed с обработкой клика вне окна вместо атрибута popover; анимации появления и исчезновения через JavaScript, requestAnimationFrame, transitionend и запасной setTimeout() вместо @starting-style и transition-behavior; блокировка скролла через overflow: hidden на <html> вместо встроенного перехвата прокрутки у модального <dialog>; ручные слушатели popstate вместо нативного закрытия диалога кнопкой «назад».
6. UX: тёмная тема
- нет
<meta name="color-scheme" content="light dark">— при загрузке вспыхивает белый экран; - тёмные цвета задаются вручную, а
color-schemeне объявлен — нативные скроллбары и контролы остаются светлыми; - игнорируется функция
light-dark(); - переключатель темы работает только как «вкл/выкл» с сохранением в
localStorage, без возврата к системной теме; - нет слушателя события
storage— открытые вкладки рассинхронизируются.
7. Производительность: отзывчивость во время работы
Модели усвоили уроки эпохи Lighthouse: они умеют ускорять загрузку страницы. А вот отзывчивость интерфейса во время работы (метрика INP) — нет:
- самодельные очереди задач на
setTimeout()вместоscheduler.postTask()иscheduler.yield(); - тяжёлые страницы с большим количеством элементов без
content-visibility: autoиcontain-intrinsic-size— любое взаимодействие заставляет браузер перерисовывать страницу целиком; - скрытые экраны SPA через
display: noneвместоcontent-visibility: hiddenсinert— во втором случае рендер остаётся в кэше; - остановка offscreen canvas через Intersection Observer вместо связки
content-visibility: autoс событиемcontentvisibilityautostatechange; - слушатели событий скролла для подсказок о прокрутке вместо CSS scroll-state queries.
8. Производительность: скачки макета и мерцание (CLS)
Размеры на <img> модели выставляют, дальше начинаются пропуски. У <video> размеров нет — по умолчанию он занимает 300×150. Вместо aspect-ratio и object-fit на медиа стилизуются обёртки. Резервные шрифты идут без font-size-adjust, и при подмене шрифта текст переезжает. Вместо единиц dvh/svh берутся статические vh, и мобильные тулбары дёргают макет. Старые анти-мерцательные фрагменты A/B-тестов с opacity: 0 не заменяются на blocking="render". Наконец, cross-document view transitions запускаются до разбора критической разметки, вместо того чтобы удерживать рендер через rel="expect" и blocking="render".
9. Безопасность: DOM XSS и санитайзинг HTML
Самый слабый блок. Без явного запроса модели вообще не добавляют CSP и заголовки cross-origin isolation. Даже с явным запросом они проваливают больше половины проверок безопасности:
- самодельные санитайзеры на
DOMParserсо списком разрешённых тегов вместо проверенной библиотеки вроде DOMPurify; - DOMPurify включается в бандл сразу, вместо нативного
setHTML()с DOMPurify как лениво подгружаемым запасным вариантом; - разрешительные политики Trusted Types вида
(input) => input, которые пропускают всё подряд и обнуляют защиту; - CSP со списком источников
'self'на статических страницах вместо хэшей SHA-256 для инлайн-скриптов; - в nonce-политиках
script-srcпропущены'strict-dynamic'и'report-sample'.
10. Безопасность: passkey-вход и жизненный цикл учётных данных
API сериализации WebAuthn модели почти полностью игнорируют:
- ручные Base64URL-хелперы на
atob()иbtoa()вместоparseCreationOptionsFromJSON(),parseRequestOptionsFromJSON()иcredential.toJSON(); - при входе не вызывается
PublicKeyCredential.signalUnknownCredential()— устаревшие passkey остаются в менеджерах паролей; - перед отрисовкой интерфейса управления не проверяется
PublicKeyCredential.getClientCapabilities(); - при повторной аутентификации не подключаются
AbortControllerиAbortSignal; - вместо правильного
uiMode: "immediate"передаётсяmediation: "immediate".
Что с этим делать
Автор подчёркивает: список не исчерпывающий, и пробелы будут меняться вместе с моделями и самой платформой. Всё перечисленное закрывает Modern Web Guidance 1.0. Скилл устанавливается одной командой:
npx modern-web-guidance@latest install
или находится в маркетплейсах плагинов вашего агента. О других ошибках моделей автор просит сообщать в issues репозитория или присылать исправления по правилам из CONTRIBUTING.md.