
Привет, Хабр.
Моя подруга постоянно читает новости о том, что искусственный интеллект заменит какую-то профессию. Тестировщик, программист, дизайнер, писатель и т. д. А сможет ли он заменить опытного верстальщика?
Мне стало интересно, сможет ли ChatGPT написать код, как я. Чтобы мог сказать: «Да, вот это мы допускаем в продакшен».
Добиваться этой цели я буду на примере нескольких популярных паттернов. Я уверен, что фронтендеры постоянно верстают их из проекта в проект. Ещё я честно признаюсь, что у меня мало опыта работы с такими системами. Я новичок. Так что тоже учитывайте это при чтении.
Давайте посмотрим, что в итоге получилось.
▍ Горизонтальная навигация
Начнём мы с навигации. Мы попросим ChatGPT создать HTML навигации, состоящей из пунктов: «Главная», «Обо мне», «Портфолио» и «Контакты». Ещё скажем, что пункт «Обо мне» у нас является текущим активным. Классы нужно написать по БЭМ.
В итоге ChatGPT создал такую разметку:
<body>
<nav>
<ul class="nav">
<li class="nav__item">
<a href="#" class="nav__link">Главная</a>
</li>
<li class="nav__item nav__item_active">
<a href="#" class="nav__link">Обо мне</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Портфолио</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Контакты</a>
</li>
</ul>
</nav>
</body>
Что ж, в целом неплохо. Есть несколько вопросов. Например, чат для активного пункта в навигации использует класс. А я бы дополнительно добавил ARIA-атрибуты. Попробуем подсказать чату. Я напишу ему: «Сделай так, чтобы скринридеры поняли, что пункт "Обо мне" активный».
<body>
<nav>
<ul class="nav">
<li class="nav__item">
<a href="#" class="nav__link">Главная</a>
</li>
<li class="nav__item nav__item_active">
<a href="#" class="nav__link" aria-current="page">Обо мне</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Портфолио</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Контакты</a>
</li>
</ul>
</nav>
</body>
Вот! Другое дело. Теперь скринридеры поймут текущий активный пункт.
Второй момент, который мне не нравится, — это отсутствие класса у элемента nav
. Я думаю, что у него должен быть класс .nav
, а у списка какой-то другой. Я спрошу у него: «А почему у тебя элемент nav без класса? Это не по БЭМ».
<body>
<nav class="nav">
<ul class="nav__list">
<li class="nav__item">
<a href="#" class="nav__link">Главная</a>
</li>
<li class="nav__item nav__item_active">
<a href="#" class="nav__link" aria-current="page">Обо мне</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Портфолио</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Контакты</a>
</li>
</ul>
</nav>
</body>
Осталось добавить атрибут aria-label
к элементу nav
. Это поможет пользователям скринридера понять, что наша навигация основная на сайте.
«Я подумал, что пользователям скринридера нужно как-то понять, что у нас основная навигация. Давай добавим это в HTML».
<body>
<nav class="nav" aria-label="Основная навигация">
<!-- список пунктов -->
</nav>
</body>
Эх, неудачно он добавил. Надо менять текст, убрав слово «Навигация». Я спросил у чата: «У меня вопросы к тексту для aria-label
. Ты уверен, что он подойдёт?».
К сожалению, он не понял в чём проблема. Он предложил несколько вариантов: «Основная навигация», «Главное меню» и «Меню сайта». Во всех вариантах используются слова, являющиеся синонимы слова «Навигация». А его скринридеры произнесут сами. Так что эти слова не подходят.
В итоге я прямо сказал: «Давай остановимся на варианте "Основная навигация". Но убери дублирование слов».
<body>
<nav class="nav" aria-label="Основная">
<ul class="nav__list">
<li class="nav__item">
<a href="#" class="nav__link">Главная</a>
</li>
<li class="nav__item nav__item_active">
<a href="#" class="nav__link" aria-current="page">Обо мне</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Портфолио</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Контакты</a>
</li>
</ul>
</nav>
</body>
Вот теперь мне нравится разметка. Можно, конечно, придраться, что надо использовать класс .nav__link_active
, а не .nav__item_active
. Но это для нашего эксперимента не так важно. Оставим так. Перейдём к CSS.
Я не буду сильно заморачиваться. Попрошу задать базовые стили, а именно расположение пунктов и отступ между ними. По моей задумке навигация у нас горизонтальная, а отступ между пунктами 15px
. Вот так я спрошу: «Напиши теперь CSS для этого HTML. Навигация располагается горизонтально. Отступ между пунктами 15 пикселей».
.nav {
display: flex; /* Располагаем пункты меню в одну строку */
}
.nav__item {
margin-right: 15px; /* Отступ между пунктами меню */
}
.nav__item:last-child {
margin-right: 0;
}
Чат использует флексбоксы. У меня вопросов нет. Это решение я приму. Но мне не нравится, что используются два правила, чтобы установить отступы. Вот так попрошу его: «Сделай так, чтобы отступы задавались без сброса у последнего элемента».
.nav {
display: flex; /* Располагаем пункты меню в одну строку */
}
.nav__item:not(:last-child) {
margin-right: 15px; /* Отступ между пунктами меню */
}
Вот уже лучше. Опять же, я так не сделаю. Для меня получился слишком сложный селектор. Надо проще. Намекну ему, что жду от него свойство gap
. Например, так: «Знаешь ли ты, как установить отступы между пунктами навигации без использования псевдо-класса :not()?».
.nav {
display: flex; /* Располагаем пункты меню в одну строку */
gap: 15px; /* Отступ между элементами меню */
}
Этот вариант я принимаю. Можно переходить к следующему компоненту.
▍ Кнопка Гамбургер
Навигация без гамбургера — это не навигация! Поэтому надо её добавить. Я попрошу ChatGPT: «Добавь в HTML кнопку Гамбургер».
<body>
<nav class="nav" aria-label="Основная">
<div class="nav__toggle">
<span class="nav__toggle-line"></span>
<span class="nav__toggle-line"></span>
<span class="nav__toggle-line"></span>
</div>
<!-- список пунктов -->
</nav>
</body>
Так, конечно, не пойдёт. Нужна именно кнопка. Надо подсказать, что элемент <div>
— плохой вариант. Сделаю это так: «Правильно ли кнопку Гамбургер размечать элементом <div>
?».
<body>
<nav class="nav" aria-label="Основная">
<button type="button" class="nav__toggle" aria-label="Открыть меню">
<span class="nav__toggle-line"></span>
<span class="nav__toggle-line"></span>
<span class="nav__toggle-line"></span>
</button>
<!-- список пунктов -->
</nav>
</body>
Он даже сразу добавил атрибут aria-label
. Какой молодец! Не пришлось даже просить. Но здесь есть задание со звёздочкой. Надо связать кнопку со списком и указать её состояние, чтобы скринридеры правильно взаимодействовали. Спрошу так: «Скринридеры поймут её?».
<body>
<nav class="nav" aria-label="Основная">
<button type="button" class="nav__toggle" aria-label="Открыть меню" aria-controls="nav-list" aria-expanded="false">
<span class="nav__toggle-line"></span>
<span class="nav__toggle-line"></span>
<span class="nav__toggle-line"></span>
</button>
<ul id="nav-list" class="nav__list">
<!-- пункты списка -->
</ul>
</nav>
</body>
Последнее, что мне не нравится, это три пустых элемента <span>
. Я лично бы вложил их вдруг в друга и добавил текст, удалив aria-label
. Но думаю, что вариант с <svg>
будет тоже правильным. Попрошу использовать его: «Используй SVG для неё».
<body>
<nav class="nav" aria-label="Основная">
<button type="button" class="nav__toggle" aria-label="Открыть меню" aria-controls="nav-list" aria-expanded="false">
<svg class="nav__burger" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<rect x="3" y="6" width="18" height="2" rx="1"></rect>
<rect x="3" y="11" width="18" height="2" rx="1"></rect>
<rect x="3" y="16" width="18" height="2" rx="1"></rect>
</svg>
</button>
<ul id="nav-list" class="nav__list">
<!-- пункты списка -->
</ul>
</nav>
</body>
На этом остановимся. Я считаю, что разметка полностью готова.
▍ Модальное окно
Ещё одним компонентом станет модальное окно. Вот такое задание я дал ChatGPT: «Напиши мне HTML для модального окна, в котором есть форма авторизации. Поля для ввода электронной почты и пароля».
<body>
<div class="modal" id="auth-modal" role="dialog" aria-modal="true" aria-labelledby="auth-title">
<div class="modal__content">
<button class="modal__close" aria-label="Закрыть окно">×</button>
<h2 class="modal__title" id="auth-title">Вход в аккаунт</h2>
<form class="modal__form">
<div class="modal__field">
<label for="email" class="modal__label">Электронная почта</label>
<input type="email" id="email" class="modal__input" required>
</div>
<div class="modal__field">
<label for="password" class="modal__label">Пароль</label>
<input type="password" id="password" class="modal__input" required>
</div>
<button type="submit" class="modal__button">Войти</button>
</form>
</div>
</div>
</body>
Тут, мне кажется, не совсем честно поступил чат. Я делал этот запрос после предыдущих двух. Мне кажется, он уже знал, что мне нужна разметка, адаптированная для скринридера. Я думаю, поэтому он добавил aria-атрибуты.
Но и в этой разметке есть ошибки. Мне не нравится, что в кнопке «Закрыть» он использует текстовый символ. Нужно крестик сделать графикой. Об этом я попросил: «Используй для кнопки «Закрыть» графику».
<body>
<div class="modal" id="auth-modal" role="dialog" aria-modal="true" aria-labelledby="auth-title">
<div class="modal__content">
<button class="modal__close" aria-label="Закрыть окно">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke-width="2" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
<h2 class="modal__title" id="auth-title">Вход в аккаунт</h2>
<form class="modal__form">
<!-- элементы формы -->
</form>
</div>
</div>
</body>
Теперь надо правильно её расположить в разметке. Она должна быть последним элементом в модальном окне. Намекну чату GPT: «Разве кнопка закрыть должна быть перед формой? Что скажут пользователи скринридра?».
<body>
<div class="modal" id="auth-modal" role="dialog" aria-modal="true" aria-labelledby="auth-title">
<div class="modal__content">
<h2 class="modal__title" id="auth-title">Вход в аккаунт</h2>
<form class="modal__form">
<!-- элементы формы -->
</form>
<button class="modal__close" aria-label="Закрыть окно">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke-width="2" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
</div>
</body>
Другое дело! Теперь можно заняться стилизацией. У нас типичное модальное окно. Оно будет расположено по центру. Поставим задачу: «Напиши CSS, чтобы модальное окно располагалось по центру на тёмно-прозрачном фоне».
.modal {
display: none; /* Скрыто по умолчанию */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Тёмно-прозрачный фон */
justify-content: center;
align-items: center;
z-index: 1000; /* Модальное окно поверх остального контента */
}
/* Остальной CSS */
В этом правиле можно сократить количество свойств, а именно: width
, height
, top
и left
. Вместо них я бы использовал свойство inset
. Интересно, догадается ли чат GPT. Вот так я его спросил: «Можно ли сократить количество свойств в правиле с селектором .modal
?».
.modal {
display: none; /* Скрыто по умолчанию */
position: fixed;
inset: 0; /* Это эквивалентно top: 0, left: 0, right: 0, bottom: 0 */
background-color: rgba(0, 0, 0, 0.5); /* Темный фон */
justify-content: center;
align-items: center;
z-index: 1000;
}
Он догадался! Осталось поправить позиционирование модального окна. Мне не нравится способ с использованием свойств justify-content
и align-items
. Но с появлением ключевого слова safe
, я больше не придираюсь. Если оно использовано, то это приемлемо.
Я несколько раз просил чат додуматься, но мои попытки были четны. Пришлось спросить прямо: «Ты слышал про ключевое слово safe
?».
.modal {
display: none; /* Скрыто по умолчанию */
position: fixed;
inset: 0; /* Это эквивалентно top: 0, left: 0, right: 0, bottom: 0 */
background-color: rgba(0, 0, 0, 0.5); /* Темный фон */
justify-content: center;
align-items: safe center;
z-index: 1000;
}
И это он сделал со второго раза, объяснив это так: «Почему я не использовал safe изначально? Простой ответ — по умолчанию, большинство разработчиков используют center
. Но для модальных окон с динамическим содержимым (например, формами), safe center
— действительно более надёжное и универсальное решение».
Понимаете, что вам надо сделать? Давайте не будем больше запутывать чат GPT. А так, у меня больше нет замечаний. Код принят!
▍ Заключение
Давайте подведём итог. В целом у меня положительное впечатление. Если не учитывать вкусовщину, все задачи решены на хорошем уровне. Но есть нюансы.
Самое важное, что ChatGPT ни разу не создал код, который не нужно было дорабатывать. ChatGPT плохо знает новые возможности CSS. Он использует техники, которые были популярны много лет назад.
Ещё было бы здорово, если бы он уже по умолчанию размечал элементы с учетом требований скринридеров. Но много ли фронтендеров обращают внимание на цифровую доступность? Точно можно сказать, что массово они этого не делают.
Получается, что ChatGPT напоминает фронтендера, который верстает потому, что иногда нужно. Можно сказать, это средний срез. Точно не хуже, но точно не лучше.
Вот если поправит эти моменты, то я буду заменён им. И это будет хорошо, потому что у многих разработчиков сразу появится шанс воспользоваться хорошими техниками. Это здорово.
А что скажите вы? Получится ли у ChatGPT заменить опытного верстальщика? Делитесь своими мыслями в комментариях.
Спасибо за чтение!
P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.
© 2025 ООО «МТ ФИНАНС»
Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (19)
flancer
20.05.2025 09:08Вы противопоставляете ИИ и ЕИ :(
А что бы вы написали в статье "ChatGPT & Гик. Насколько AI увеличивает производительность опытного верстальщика?"
FifthLeg
20.05.2025 09:08Вместо zero-shot, в подобных случаях лучше использовать one-shot промпт.
Если вы профессионал, у вас уже есть набор предыдущих примеров на которые вы хотели, чтобы новый проект был подобен. Можно даже сделать RAG сервис, который будет из списка ваших проектов готовить пример для one-shot. Т.е. в промте говоришь - сделай в том же стиле как в проекте "мой прошлый проект", а RAG из вашего репозитория подтягивает и генерирует дополнение к промпту.
Zero-shot подходит совсем уж новичку.
Xtray
20.05.2025 09:08.nav { display: flex; /* Располагаем пункты меню в одну строку */ gap: 15px; /* Отступ между элементами меню */}
Этот вариант я принимаю. Можно переходить к следующему компоненту.
Зато его не принимают Сафари <15 версии.. :(achekalin
20.05.2025 09:08Я так часто вижу вёрстку, которая работает только на свежих браузерах, что начал подозревать, что весь мир начал верить, что у юзеров оно само собой обновляется до свежего. Ведь обновляется, да?
А что бывают люди, которые сознательно не уходят с версии ОС минус 2-3 от текущей, а под те версии гугл обновы уже не пилит, или что бывают просто оффлайн-компьютеры, которым тоже нужно HTML иметь дело, все словно забыли.
melnik909 Автор
20.05.2025 09:08от 10 и ниже
Xtray
20.05.2025 09:08К сожалению, поддержка свойства
gap
для Flexbox в Safari появилась в версии 14.1, которая была выпущена в апреле 2021 года (как часть macOS Big Sur 11.3 и iOS 14.5). Прошу прощения за неточность.
https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes
Safari now supports flexbox gaps with row-gap and column-gap, see Gap Shorthand: the gap property.
Тем не менее, своеобразная политика обновления браузера приводит к тому, что и по сей день приходится учитывать эту ситуацию.melnik909 Автор
20.05.2025 09:08Спасибо, что написали дату релиза. Дальше читатели сделают вывод сами
Antra
20.05.2025 09:08Тут, мне кажется, не совсем честно поступил чат. Я делал этот запрос после предыдущих двух. Мне кажется, он уже знал, что мне нужна разметка, адаптированная для скринридера. Я думаю, поэтому он добавил aria-атрибуты.
Забавно. По идее такое требование должно было быть в самом первом промпте, при постановке задачи. И то, что он подстраивается под ваш стиль по мере того, как вы его предъявлете, это большой плюс, а не "читерство" ("не совсем честно"). Он же не телепат.
А по существу - вряд ли он будет заменять вас - опытного верстальщика. Скорее нужда в "так себе, зато дешевых" уменьшится.
ktibr0
20.05.2025 09:08Напоминает взаимодействие со стажером - сидишь рядом с ним и бьёшь линейкой по рукам.
Я предложил бы сделать еще один эксперимент - те требования, которые итерационно вы ему передавали, добиваясь соответствия вашим стандартам красоты и эффективности кода, сразу сформулировать и передать ему в первом запросе, чтобы он реализовал уже понимая ожидания от подхода к приёмке результатов. Даже если ему скопировать статью, написав - смотри, есть разбор работы с chatgpt, мне близки требования этого человека, сделай [какое то другое задание] учитывая пожелания автора статьи. Проверите? Ибо знаний моих не хватит понять, насколько он исполняет требования)
melnik909 Автор
20.05.2025 09:08Если поможете мне, то давайте проверим. Напишите, пожалуйста, мне по контактам в профиле
Jhayphal
20.05.2025 09:08Судя по всему у автора мало опыта взаимодействия с LLM. Мой опыт показывает вполне логичную вещь - чем точнее запрос, тем точнее ответ. Поэтому ему необходимо сразу предоставить весь набор требований, чтобы не пришлось каждый раз просить его что-то исправить. Чем меньше ему дано информации, тем больше он её "додумывает".
Поэтому свои запросы в новом чате я всегда начинаю с указания языка программирования перечисления библиотек, которые используются в проекте и должны использоваться для решения задачи. А так же я стараюсь как можно точнее указать, что именно я ожидаю получить. И в большинстве случаев ответы такого уровня качества, что достаточно скопировать и вставить. Я ему прямо в запросе указываю типы и апи и он понимает как их нужно использовать.
Psychosynthesis
20.05.2025 09:08Я думал все вменяемые люди уже выкинули БЭМ на помойку (где ему и место), но нет, обученные на тысячах шизоидных текстов нейронки вдохнут новую жизнь в эту идиотскую концепцию, слава богу.
achekalin
Gpt обычно пытается решить задачу, как попросили - и Вы наводите её на тот код, который хотите получить, в несколько итераций. Если бы были заказчиком, а гпт - вашим подрядчиком, уверен, что столько заходов на сдачу с готовой работой Вас бы поразило, ну тут Вы просто пользуетесь умным редактором, реализуя с его помощью свою волю.
При этом, он не обязан делать ARIA совместимую разметку, я так понимаю. Условно говоря, он вообще все на таблицах мог наверстать, и задачу-то бы решил, верно же?
melnik909 Автор
В контексте замены специалиста, AI должен заменять человека с актуальными знаниями. Даже если взять современного фронтендера, то никто не сделает верстку таблицами, если это не email-верстка.