Олег Марков
Скрытый элемент в HTML - атрибут hidden и его практическое применение
Введение
Атрибут hidden в HTML кажется простым — добавили его к элементу, и он исчез с экрана. Но как только вы начинаете активно работать с интерфейсами, формами, динамическими блоками, сразу появляется масса нюансов.
Смотрите, я покажу вам, что за этим атрибутом скрывается гораздо больше, чем просто "спрятать элемент". В статье вы разберетесь:
- что именно делает hidden на уровне HTML и CSS
- чем скрытие через hidden отличается от display none, visibility hidden, opacity 0 и других способов
- как правильно управлять скрытыми элементами через JavaScript
- как hidden влияет на доступность и работу скринридеров
- как использовать скрытые элементы в формах, SPA и динамических интерфейсах
Давайте разбираться по шагам, с примерами и комментариями в коде.
Что такое атрибут hidden в HTML
Краткое определение
Атрибут hidden — это булевый атрибут HTML, который помечает элемент как неактуальный для пользователя в текущий момент. Браузер при этом:
- не отображает элемент
- не выделяет под него место в макете
- исключает его из взаимодействия (нельзя кликнуть, попасть с клавиатуры и т.д.)
По сути, при наличии атрибута hidden браузер применяет к элементу встроенное правило стилей:
<!-- Внутренне браузер ведет себя примерно так -->
<style>
/* Комментарий: это специфика HTML5 — скрытый элемент получает display none */
[hidden] {
display: none; /* Элемент не отображается и не занимает место */
}
</style>
Обратите внимание — вы не пишете это правило сами, оно "зашито" в поведение браузера, когда он видит атрибут hidden.
Булевый атрибут — что это значит
Булевый атрибут — это атрибут, который:
- считается включенным, если просто присутствует в теге
- не требует значения "true" или "false"
- его текстовое значение (если вы вдруг его зададите) обычно игнорируется
Например, эти варианты с точки зрения браузера одинаковы:
<div hidden>Контент</div>
<div hidden="hidden">Контент</div>
<div hidden="true">Контент</div>
<div hidden="">Контент</div>
Во всех случаях элемент будет скрыт, потому что атрибут есть.
Чтобы сделать элемент видимым, атрибут нужно убрать:
<div>Контент снова виден</div>
или в JavaScript — удалить его, а не пытаться записать значение "false".
Как hidden работает в браузере
Визуальное поведение
Когда вы добавляете атрибут hidden:
- элемент не рендерится визуально
- место под него не резервируется
- не показывается ни рамка, ни фон, ни текст, ничего
Пример:
<p>Этот текст всегда виден.</p>
<p hidden>А этот текст скрыт атрибутом hidden.</p>
<p>Этот текст снова виден.</p>
На странице вы увидите только два абзаца — первый и третий. Скрытый абзац полностью "выпал" из потока.
Поведение в DOM и JS
Важно понимать — элемент остается в DOM. Вы можете:
- найти его через document.querySelector
- менять ему классы, стили, атрибуты
- добавлять внутрь текст и другие элементы
Смотрите, как это выглядит:
<div id="panel" hidden>
<!-- Комментарий: изначально панель скрыта -->
Секретная панель
</div>
<script>
// Комментарий: элемент в DOM существует, хотя его не видно
const panel = document.getElementById('panel')
// Комментарий: можем изменить текст внутри
panel.textContent = 'Обновленная секретная панель'
</script>
Хотя панель не видна, вы спокойно с ней работаете в JavaScript.
Связь с CSS display
По спецификации HTML браузер должен считать скрытый элемент неактуальным для пользователя, и типичное поведение — применить к нему display: none.
Но нюанс в том, что вы можете переопределить это правило своим CSS:
<style>
/* Комментарий: переопределяем поведение по умолчанию */
[hidden] {
display: block !important; /* Насильно показываем скрытые элементы */
}
</style>
<div hidden>Этот блок станет виден из-за CSS-правила выше</div>
Это важный момент: атрибут hidden — это рекомендация браузеру "элемент сейчас не показывать". Но если вы пишете более специфичный CSS (особенно с !important), вы можете "победить" стандартное поведение.
Основные сценарии использования hidden
Временное скрытие неактуального контента
Самый очевидный сценарий: часть интерфейса сейчас пользователю не нужна, но вы планируете ее показать позже.
Например, вкладки:
<button id="tab1-btn">Вкладка 1</button>
<button id="tab2-btn">Вкладка 2</button>
<div id="tab1-content">
<!-- Комментарий: первая вкладка видна по умолчанию -->
Контент первой вкладки
</div>
<div id="tab2-content" hidden>
<!-- Комментарий: вторая вкладка скрыта -->
Контент второй вкладки
</div>
<script>
const tab1Btn = document.getElementById('tab1-btn')
const tab2Btn = document.getElementById('tab2-btn')
const tab1 = document.getElementById('tab1-content')
const tab2 = document.getElementById('tab2-content')
tab1Btn.addEventListener('click', () => {
// Комментарий: показываем первую вкладку, скрываем вторую
tab1.hidden = false
tab2.hidden = true
})
tab2Btn.addEventListener('click', () => {
tab1.hidden = true
tab2.hidden = false
})
</script>
Здесь интерфейс остается на странице, но вы управляете видимостью через hidden.
Состояние "загружено / не загружено"
Еще один типичный сценарий — показывать пользователю индикатор загрузки, пока данные не пришли, и скрывать его после.
<div id="loader">
<!-- Комментарий: индикатор загрузки виден сначала -->
Загрузка...
</div>
<div id="content" hidden>
<!-- Комментарий: контент появится после загрузки -->
Основной контент
</div>
<script>
const loader = document.getElementById('loader')
const content = document.getElementById('content')
// Комментарий: эмулируем асинхронную загрузку
setTimeout(() => {
// Комментарий: скрываем индикатор, показываем контент
loader.hidden = true
content.hidden = false
}, 2000)
</script>
Такой подход делает код читабельным — видно, какой блок был изначально скрыт и станет видимым позже.
Вспомогательные подсказки и сообщения об ошибках
В формах часто нужно показывать сообщения об ошибках только когда они реально есть. Атрибут hidden прекрасно подходит для этого:
<form id="login-form">
<label>
Email
<input id="email" type="email">
</label>
<p id="email-error" hidden>
<!-- Комментарий: сообщение об ошибке скрыто до валидации -->
Введите корректный email
</p>
<button type="submit">Войти</button>
</form>
<script>
const form = document.getElementById('login-form')
const emailInput = document.getElementById('email')
const emailError = document.getElementById('email-error')
form.addEventListener('submit', (event) => {
if (!emailInput.value.includes('@')) {
// Комментарий: предотвращаем отправку и показываем ошибку
event.preventDefault()
emailError.hidden = false
} else {
// Комментарий: скрываем ошибку, если все ок
emailError.hidden = true
}
})
</script>
Теперь вы увидите, как просто управлять сообщениями через свойство hidden.
Сравнение hidden с другими способами скрытия
Очень часто разработчики путаются между:
- hidden
- display: none
- visibility: hidden
- opacity: 0
- aria-hidden="true"
Давайте подробно разберем, чем они отличаются и когда что использовать.
hidden vs display none
По факту в обычной ситуации эти два способа дают одно и то же визуальное поведение:
- элемент не отображается
- место не занимает
- недоступен для кликов и фокуса
Разница — где вы это задаете:
- hidden — в HTML
- display: none — в CSS
<!-- Вариант 1. HTML -->
<div hidden>Скрыто атрибутом hidden</div>
<!-- Вариант 2. CSS -->
<style>
.hidden-with-css {
display: none; /* Комментарий: скрытие через CSS */
}
</style>
<div class="hidden-with-css">Скрыто через display none</div>
Что важно:
- hidden — семантичнее, когда вы хотите сказать "этот элемент сейчас неактуален"
- display: none — более гибок, когда вы управляете видимостью через классы и медиа-запросы
Хорошая практика — использовать hidden, когда решение о видимости "жестко" задано разметкой, и CSS — когда видимость зависит от дизайна и условий (размер экрана, темы и т.д.).
hidden vs visibility hidden
visibility: hidden:
- скрывает элемент визуально
- но оставляет место в макете
- и не дает взаимодействовать с элементом
Пример:
<style>
.invisible {
visibility: hidden; /* Комментарий: элемент невидим, но место занимает */
}
</style>
<p>Сверху видимый текст</p>
<p class="invisible">Этот текст скрыт, но место под него остается</p>
<p>Снизу еще один видимый текст</p>
Разница:
- hidden — элемент как будто его нет в потоке
- visibility: hidden — как "прозрачное место" на странице
Выбирайте visibility hidden, когда вам важно сохранить структуру макета, но временно не показывать содержимое.
hidden vs opacity 0
opacity: 0:
- элемент полностью прозрачен
- но занимает место
- реагирует на события (клик, фокус и т.д.)
<style>
.transparent {
opacity: 0; /* Комментарий: элемент невидим, но живет в интерфейсе */
}
</style>
<button class="transparent" id="secret-btn">
Секретная кнопка
</button>
<script>
document.getElementById('secret-btn').addEventListener('click', () => {
// Комментарий: это событие сработает, даже если кнопку не видно
alert('Кнопка нажата, хотя вы ее не видите')
})
</script>
Здесь вы видите, что элемент как будто "призрак" — его не видно, но он реагирует.
hidden, в отличие от этого, полностью выключает элемент из взаимодействия.
hidden vs aria-hidden
aria-hidden="true" — это атрибут доступности, а не визуальная настройка. Он говорит вспомогательным технологиям (скринридерам):
"Не озвучивай этот элемент для пользователя".
При этом сам элемент:
- может быть видим
- может занимать место
- может быть интерактивным
Пример:
<p aria-hidden="true">
<!-- Комментарий: текст виден зрячему пользователю, но не озвучивается скринридером -->
Декоративный текстовой элемент
</p>
Сравнение:
- hidden — влияет на визуальное отображение и на доступность одновременно (элемент вообще считается неактуальным)
- aria-hidden — только про доступность, не трогает внешний вид
Иногда эти атрибуты комбинируют, но делать это нужно аккуратно и осознанно.
Управление hidden через JavaScript
Свойство element.hidden
У DOM-элементов есть специальное булево свойство hidden, связанное с одноименным атрибутом.
Смотрите, как это работает:
<div id="box" hidden>Скрытый блок</div>
<script>
const box = document.getElementById('box')
// Комментарий: проверяем текущее состояние
console.log(box.hidden) // true, потому что атрибут hidden есть
// Комментарий: делаем элемент видимым
box.hidden = false
// Комментарий: теперь атрибута hidden в HTML-дереве нет
console.log(box.hidden) // false
</script>
Особенность:
box.hidden = true— добавляет атрибут hidden в HTMLbox.hidden = false— удаляет атрибут hidden из HTML
Это более удобный способ, чем напрямую работать с методами setAttribute/removeAttribute.
Использование toggle для переключения
Частая задача — "по клику показать/скрыть" блок. Давайте разберем пример:
<button id="toggle-btn">Показать/скрыть панель</button>
<div id="panel" hidden>
<!-- Комментарий: панель по умолчанию скрыта -->
Я панель, которую можно показывать и скрывать
</div>
<script>
const btn = document.getElementById('toggle-btn')
const panel = document.getElementById('panel')
btn.addEventListener('click', () => {
// Комментарий: инвертируем значение свойства hidden
panel.hidden = !panel.hidden
// Комментарий: меняем текст кнопки в зависимости от состояния
if (panel.hidden) {
btn.textContent = 'Показать панель'
} else {
btn.textContent = 'Скрыть панель'
}
})
</script>
Как видите, вам не нужно помнить, был ли раньше атрибут hidden — достаточно инвертировать булево свойство.
Работа с классами и hidden одновременно
Иногда удобно комбинировать hidden с классами:
- hidden — для базового "есть/нет"
- классы — для анимаций, разных состояний, стилей
Например, вы можете убрать hidden, но добавить класс, который включает fade-in анимацию.
<style>
.fade-in {
/* Комментарий: простая анимация появления */
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
<div id="popup" hidden>
Всплывающее окно
</div>
<script>
const popup = document.getElementById('popup')
// Комментарий: функция для показа с анимацией
function showPopup() {
popup.hidden = false // Комментарий: делаем блок видимым
popup.classList.add('fade-in') // Комментарий: включаем анимацию
}
function hidePopup() {
popup.hidden = true // Комментарий: снова скрываем блок
popup.classList.remove('fade-in') // Комментарий: убираем класс анимации
}
// Здесь вы можете вызвать showPopup() и hidePopup() в нужный момент
</script>
Такая комбинация особенно полезна в SPA и компонентах.
Влияние hidden на доступность (a11y)
Скринридеры и скрытые элементы
Большинство современных скринридеров:
- игнорируют элементы с hidden
- не озвучивают их содержимое
- не позволяют к ним перейти с клавиатуры
То есть элемент с hidden фактически исчезает и визуально, и семантически.
Это хорошо, когда:
- элемент действительно неактуален
- его нельзя и не нужно активировать или читать
Но важно не злоупотреблять этим, если пользователям с особыми потребностями все-таки требуется доступ к части скрытого контента (например, подсказкам, которые видны только по наведению).
Не используйте hidden для "визуально скрыто, но доступно"
Иногда вам нужно спрятать элемент только визуально, но оставить его:
- доступным для скринридеров
- доступным для поиска по странице
- доступным для навигации с клавиатуры
В таких случаях hidden — неподходящий вариант.
Вместо этого используют специальные CSS-классы, вроде "visually-hidden":
<style>
.visually-hidden {
/* Комментарий: делаем элемент невидимым, но доступным для скринридеров */
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
}
</style>
<button>
<!-- Комментарий: иконка будет видна всем -->
<span aria-hidden="true">🔍</span>
<!-- Комментарий: текст будет скрыт визуально, но прочитан скринридером -->
<span class="visually-hidden">Поиск</span>
</button>
Здесь я размещаю пример, чтобы вам было проще понять: hidden в такой задаче лишит пользователей скринридеров важной информации.
hidden и интерактивные элементы
Если вы скрываете:
- кнопку
- ссылку
- поле ввода
- меню
через hidden, вы одновременно:
- убираете их видимость
- убираете их из таб-индекса
- лишаете их доступности
Это обычно именно то, что нужно. Но проверяйте сценарии:
- если вы показываете блок по нажатию кнопки, убедитесь, что в момент показа у него hidden не стоит
- если блок должен быть доступен с клавиатуры, он не должен быть hidden
Хорошая проверка: в любой момент времени не должно быть элементов, которые:
- скрыты hidden
- но на которые вы планируете кликать или фокусироваться из JS
Практические примеры использования hidden
Выпадающее меню
Давайте разберем базовый пример выпадающего меню без сложных библиотек.
<button id="menu-toggle" aria-expanded="false" aria-controls="menu">
Меню
</button>
<ul id="menu" hidden>
<!-- Комментарий: пункты меню, скрыты по умолчанию -->
<li><a href="#home">Главная</a></li>
<li><a href="#about">О нас</a></li>
<li><a href="#contacts">Контакты</a></li>
</ul>
<script>
const toggleButton = document.getElementById('menu-toggle')
const menu = document.getElementById('menu')
toggleButton.addEventListener('click', () => {
const isHidden = menu.hidden
// Комментарий: переключаем видимость
menu.hidden = !isHidden
// Комментарий: обновляем aria-expanded для доступности
toggleButton.setAttribute('aria-expanded', String(isHidden === true))
})
</script>
Обратите внимание:
- меню скрыто через hidden
- кнопка через aria-expanded сообщает скринридерам, открыто меню или нет
- menu.hidden управляет именно визуальной и семантической видимостью списка
Модальное окно (диалог)
Еще один важный сценарий — модальные окна. Покажу вам упрощенную версию с hidden.
<button id="open-dialog">Открыть диалог</button>
<div id="backdrop" hidden>
<!-- Комментарий: полупрозрачный фон под модальным окном -->
</div>
<div id="dialog" role="dialog" aria-modal="true" hidden>
<h2>Заголовок диалога</h2>
<p>Текст диалога</p>
<button id="close-dialog">Закрыть</button>
</div>
<style>
#backdrop {
/* Комментарий: затемняем фон всей страницы */
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
}
#dialog {
/* Комментарий: центрируем модальное окно */
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 16px;
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
</style>
<script>
const openBtn = document.getElementById('open-dialog')
const closeBtn = document.getElementById('close-dialog')
const dialog = document.getElementById('dialog')
const backdrop = document.getElementById('backdrop')
openBtn.addEventListener('click', () => {
// Комментарий: показываем диалог и подложку
dialog.hidden = false
backdrop.hidden = false
})
closeBtn.addEventListener('click', () => {
dialog.hidden = true
backdrop.hidden = true
})
// Комментарий: закроем диалог по клику по подложке
backdrop.addEventListener('click', () => {
dialog.hidden = true
backdrop.hidden = true
})
</script>
Здесь hidden помогает четко управлять тем, когда диалог существует для пользователя, а когда его "нет".
Пошаговые формы и мастер-процессы
Вы можете строить "мастер" из нескольких шагов, скрывая все, кроме нужного.
<form id="wizard">
<div id="step-1">
<!-- Комментарий: первый шаг виден -->
<h3>Шаг 1</h3>
<label>Имя <input type="text"></label>
<button type="button" id="next-1">Далее</button>
</div>
<div id="step-2" hidden>
<!-- Комментарий: второй шаг скрыт по умолчанию -->
<h3>Шаг 2</h3>
<label>Email <input type="email"></label>
<button type="button" id="prev-2">Назад</button>
<button type="submit">Готово</button>
</div>
</form>
<script>
const step1 = document.getElementById('step-1')
const step2 = document.getElementById('step-2')
const next1 = document.getElementById('next-1')
const prev2 = document.getElementById('prev-2')
next1.addEventListener('click', () => {
// Комментарий: переключаемся со шага 1 на шаг 2
step1.hidden = true
step2.hidden = false
})
prev2.addEventListener('click', () => {
// Комментарий: возвращаемся на шаг 1
step2.hidden = true
step1.hidden = false
})
</script>
Такой подход дает вам простое управление состояниями формы.
Типичные ошибки и подводные камни
Ошибка 1. Попытка поставить hidden="false"
Нередко можно встретить такой код:
<div hidden="false">Я хотел, чтобы этот блок был виден</div>
И разработчик удивляется — почему блок все равно исчез. Ответ — атрибут hidden булевый, и сам факт его наличия говорит браузеру "элемент скрыт". Значение "false" не учитывается.
Как правильно:
- чтобы элемент был видим, вообще не указывайте hidden
- или программно установите
element.hidden = false
Ошибка 2. Сломанное взаимодействие с CSS
Допустим, у вас в CSS есть:
[hidden] {
display: none !important;
}
И дальше вы пытаетесь в другом месте:
#my-block {
display: block;
}
И не понимаете, почему элемент не показывается, хотя вы ему явно задали display: block. Проблема в !important и в том, что селектор [hidden] все еще "побеждает" обычный селектор без !important.
Как избежать:
- не добавляйте !important в глобальное правило для [hidden]
- или управляйте видимостью полностью через JS, убирая сам атрибут hidden
Ошибка 3. Противоречие между hidden и aria-атрибутами
Иногда встречается такая конструкция:
<div hidden aria-hidden="false">
Я хотел, чтобы скринридер прочитал этот текст
</div>
Но не прочитает — hidden перекрывает aria-hidden. По смыслу: если элемент неактуален (hidden), то он не должен читаться даже при aria-hidden="false".
Лучшее решение — не ставить hidden там, где вы хотите сохранить доступность для скринридеров. Используйте другие способы визуального скрытия.
Ошибка 4. Работа с фокусом на скрытых элементах
Если вы пытаетесь:
element.hidden = true
element.focus() // Комментарий: пытаемся сфокусироваться на скрытом элементе
Фокус либо не установится, либо будут нестабильные эффекты, зависящие от браузера. Такое поведение считается некорректным.
Правильный порядок:
- сначала делайте элемент видимым (
hidden = false) - потом, когда он в DOM и рендерится, вызывайте
focus()
element.hidden = false
element.focus()
Ошибка 5. Смешивание hidden и сложных анимаций
Если вы одновременно:
- ставите hidden = true
- запускаете CSS-анимацию исчезновения
анимация просто не произойдет — элемент сразу исчезнет из потока. Чтобы анимация отработала, вам нужно:
- сначала добавить класс с анимацией
- дождаться окончания анимации
- только потом установить hidden = true
Это уже немного выходит за рамки базовой статьи, но важно понимать — hidden "отрезает" элемент мгновенно.
Лучшие практики использования hidden
Подведем локальный итог и сформулируем рекомендации.
Когда hidden — подходящий выбор
Используйте hidden, когда:
- элемент полностью неактуален в текущем состоянии интерфейса
- вы хотите, чтобы он был невиден и недоступен для всех пользователей
- вам удобно на уровне HTML показать "изначальное" состояние компонента
- вы строите простую логику показа/скрытия через JS без сложных условий
Примеры:
- шаги мастера, которые еще не активны
- сообщения об ошибках, пока их нет
- модальные окна до открытия
- альтернативные состояния ("загрузка / контент")
Когда лучше выбрать другие способы
Не используйте hidden:
- для временного сокрытия только визуального, но доступного текста — лучше
visually-hiddenкласс - когда нужно сохранить место в макете — лучше
visibility: hiddenили прозрачность - когда решение о видимости принимает дизайн, а не логика интерфейса — лучше
display: noneчерез CSS
Рекомендации по коду
- в JS работайте через свойство
element.hidden, а не черезsetAttribute - не задавайте hidden со значением "false" в HTML — это только запутает
- избегайте
!importantв глобальных правилах для[hidden] - продумывайте, как hidden влияет на доступность, особенно если в блоке есть важный текст или элементы управления
Частозадаваемые технические вопросы по теме и ответы
1. Как анимировать показ и скрытие элемента с hidden, чтобы анимация не ломалась
- Для показа:
- сначала уберите
hidden = true→element.hidden = false - затем добавьте класс с анимацией появления, например
element.classList.add('fade-in').
- сначала уберите
- Для скрытия:
- добавьте класс с анимацией скрытия, например
element.classList.add('fade-out') - повесьте обработчик
animationendна элемент - в обработчике по завершению анимации поставьте
element.hidden = trueи уберите класс анимации.
- добавьте класс с анимацией скрытия, например
Так вы даете анимации возможность отработать до того, как элемент исчезнет из потока.
2. Как корректно управлять hidden в React или другом фреймворке
В React/ Vue/ Svelte лучше:
- хранить состояние видимости в переменной состояния (например,
isVisible) - привязывать hidden к этому состоянию, например в React
hidden={!isVisible}.
Важно — не смешивать управление видимостью через hidden и через условный рендеринг ({isVisible && <Block />}) в одном и том же месте без необходимости, чтобы не создавать путаницу.
3. Как проверить в JS, скрыт ли элемент через hidden или другими способами
Проверка element.hidden покажет только наличие атрибута hidden. Если элемент скрыт через CSS (display none), используйте:
// Комментарий: получаем реальные вычисленные стили
const styles = getComputedStyle(element)
const isHiddenByDisplay = styles.display === 'none'
При необходимости комбинируйте проверки element.hidden и getComputedStyle.
4. Можно ли навесить обработчик события на скрытый элемент и будет ли он работать после показа
Да, вы можете повесить обработчики событий заранее, пока элемент скрыт:
const panel = document.getElementById('panel')
panel.addEventListener('click', () => {
// Комментарий: этот код сработает, когда вы кликните по панели после ее показа
})
Пока элемент hidden — события клика по нему не возникнут, потому что элемент невидим и недоступен. После того как вы установите panel.hidden = false, обработчик начнет работать без дополнительных действий.
5. Как отличить в JS элемент, который изначально был скрыт через hidden, от элемента, скрытого другими способами
Если вам важно знать, что именно hidden скрыл элемент, проверяйте:
const isHiddenAttrPresent = element.hasAttribute('hidden')
Этот метод вернет true только если атрибут hidden реально присутствует в HTML. Если элемент скрыт через CSS (display none, visibility hidden, opacity 0), hasAttribute('hidden') вернет false, и вы сможете различить эти случаи.
Постройте личный план изучения Html до уровня Middle — бесплатно!
Html — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Html
Лучшие курсы по теме

HTML и CSS
Антон Ларичев
TypeScript с нуля
Антон Ларичев