Перейти к содержанию
Фронтенд

12 вопросов по теме «Frontend: HTML и CSS» на собеседовании

В этом материале — 12 вопросов из русской колоды RecallDeck по теме «Frontend: HTML и CSS». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

12 мин чтения12 подробных ответовПроверено 24 августа 2026
Главная мысль

Связывайте ответ с браузером, доступностью, производительностью и пользовательским состоянием — именно там видна зрелость frontend-инженера.

Вопросы и ответы

12 подробных ответов

01

Что такое семантическая вёрстка и зачем она нужна (доступность, SEO, поддержка)?

Короткий ответ: Семантическая вёрстка — это использование HTML-тегов по их смыслу (<nav>, <article>, <button>), а не <div> на всё подряд. Браузер и вспомогательные технологии понимают структуру документа, что даёт доступность, SEO и читаемый код.

Подробно:

  1. Доступность (a11y) — скринридеры строят дерево доступности по тегам: <nav>, <main>, <h1> дают навигацию по ориентирам и заголовкам. <div>-суп озвучивается как бесструктурный текст.
  2. SEO — поисковики выделяют <article>, <h1><h6>, <time> как сигналы структуры и важности контента.
  3. Поддерживаемость<header>/<footer>/<aside> самодокументируют разметку; не нужно читать классы, чтобы понять роль блока.
  4. Бесплатное поведение<button> фокусируется и реагирует на Enter/Space, <a> навигируется, <details> сворачивается — без JS.
<!-- ❌ div-суп -->
<div class="nav"><div class="link" onclick="go()">Главная</div></div>

<!-- ✅ семантика -->
<header>
  <nav aria-label="Основная">
    <a href="/">Главная</a>
  </nav>
</header>
<main>
  <article>
    <h1>Заголовок статьи</h1>
    <time datetime="2026-06-30">30 июня 2026</time>
  </article>
</main>

⚠️ Частая ошибка: делать кликабельный <div onclick> вместо <button> — теряются фокус, клавиатура и роль для скринридера.

02

Объясните блочную модель CSS и как `box-sizing: border-box` меняет расчёт размеров.

Короткий ответ: Каждый элемент — это вложенные слои: content → padding → border → margin. По умолчанию (content-box) width задаёт только контент, а padding и border прибавляются сверху. border-box включает padding и border внутрь заданной width.

Подробно:

  1. content — область контента, на которую и ссылается width/height при content-box.
  2. padding — внутренний отступ, окрашивается фоном элемента.
  3. border — рамка между padding и margin.
  4. margin — внешний отступ, прозрачный; у соседних блоков по вертикали схлопывается (margin collapse).
┌──────────── margin ────────────┐
│  ┌───────── border ─────────┐  │
│  │  ┌────── padding ─────┐  │  │
│  │  │     content        │  │  │
│  │  └────────────────────┘  │  │
│  └──────────────────────────┘  │
└────────────────────────────────┘
/* content-box: реальная ширина = 200 + 2*16 + 2*2 = 236px */
.a { box-sizing: content-box; width: 200px; padding: 16px; border: 2px solid; }

/* border-box: реальная ширина ровно 200px, padding/border внутри */
.b { box-sizing: border-box; width: 200px; padding: 16px; border: 2px solid; }

/* типичный сброс */
*, *::before, *::after { box-sizing: border-box; }

⚠️ Частая ошибка: задать width: 100% плюс padding на content-box — блок вылезает за контейнер; border-box это чинит.

03

Flexbox или Grid: чем отличаются и когда выбирать какой?

Короткий ответ: Flexbox — одномерная раскладка (строка ИЛИ колонка), Grid — двумерная (строки И колонки одновременно). Flex хорош для распределения элементов вдоль одной оси, Grid — для управления макетом по сетке.

Подробно:

  1. Flexbox — раскладывает контент вдоль главной оси; размеры часто определяются содержимым (content-out). Идеален для тулбаров, навбаров, кнопок в ряд, центрирования.
  2. Grid — задаёшь сетку шаблоном (layout-in), помещаешь элементы в ячейки/области. Идеален для общего макета страницы, карточных сеток, выравнивания по строкам И колонкам.
  3. Совмещают — типично Grid для каркаса страницы, Flex внутри компонентов.
Критерий Flexbox Grid
Оси одна (1D) две (2D)
Подход от контента от шаблона сетки
Сильная сторона распределение в ряд/колонку строки + колонки сразу
Пример навбар, тулбар макет страницы, галерея
/* Flex: элементы в ряд, прижать края, центр по вертикали */
.toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; }

/* Grid: адаптивная сетка карточек без медиазапросов */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }

⚠️ Частая ошибка: строить двумерный макет на вложенных flex-контейнерах — колонки не выравниваются между строками; это работа для Grid.

04

Как считается специфичность селекторов и как каскад разрешает конфликты? Где тут `!important` и `@layer`?

Короткий ответ: Специфичность — это тройка (id, class, type): # весит больше любого числа классов, класс — больше любого числа тегов. При равной специфичности побеждает то, что объявлено позже. !important и @layer стоят над обычным каскадом.

Подробно:

  1. Подсчёт (a,b,c) — a = id-селекторы, b = классы/атрибуты/псевдоклассы, c = теги/псевдоэлементы. Сравнение слева направо.
  2. Inline-стиль — весит выше любого селектора (условно «1,0,0,0»).
  3. При равенстве — порядок — выигрывает последнее правило в источнике.
  4. !important — поднимает объявление над нормальным каскадом; перебивается только другим !important с большей специфичностью.
  5. @layer — каскадные слои: порядок слоёв важнее специфичности внутри них (поздний слой бьёт ранний), что позволяет управлять приоритетом без гонки специфичности.
/* специфичность: */
a { }                    /* 0,0,1 */
.btn { }                 /* 0,1,0 — бьёт a */
nav a.btn { }            /* 0,1,2 */
#cta { }                 /* 1,0,0 — бьёт всё выше */

@layer base, theme;      /* theme объявлен позже → побеждает */
@layer base  { .btn { color: black; } }
@layer theme { .btn { color: rebeccapurple; } }  /* выиграет */

⚠️ Частая ошибка: гасить конфликты через !important — это эскалация войны специфичности; чаще правильнее каскадные слои или менее специфичный селектор.

05

Чем отличаются значения `position`: static, relative, absolute, fixed, sticky? Приведите реальный пример для sticky.

Короткий ответ: static — обычный поток; relative — смещение от своего места без выпадения из потока; absolute — позиционирование относительно ближайшего позиционированного предка с выпадением из потока; fixed — относительно вьюпорта; sticky — гибрид: ведёт себя как relative, пока не достигнет порога прокрутки, затем «прилипает».

Подробно:

  1. static — значение по умолчанию; top/left/z-index игнорируются.
  2. relative — сдвигается смещениями, но место в потоке сохраняет; создаёт контекст для absolute-потомков.
  3. absolute — выпадает из потока, отсчитывается от ближайшего предка с position != static.
  4. fixed — выпадает из потока, привязан к вьюпорту (баннеры, модалки).
  5. sticky — нужен порог (top, bottom…) и прокручиваемый контейнер; прилипает в пределах своего родителя.
position В потоке Точка отсчёта
static да
relative да своё место
absolute нет позиционированный предок
fixed нет вьюпорт
sticky да→прилип предок + порог скролла
/* заголовок таблицы, прилипающий к верху при прокрутке */
thead th { position: sticky; top: 0; background: white; z-index: 1; }

⚠️ Частая ошибка: sticky не работает, если у родителя overflow: hidden/auto или не задан порог (top).

06

Что такое контекст наложения (stacking context) и почему больший `z-index` иногда ничего не меняет?

Короткий ответ: z-index сравнивается только внутри одного контекста наложения. Если два элемента в разных контекстах, порядок задают их предки-контексты, а не их собственные z-index — поэтому «999999» бывает бессилен против соседнего поддерева.

Подробно:

  1. Корень<html> создаёт корневой контекст наложения.
  2. Что создаёт новый контекстposition != static + z-index ≠ auto; любой opacity < 1; transform, filter, perspective, will-change; isolation: isolate; flex/grid-потомок с z-index.
  3. Сравнение локально — внутри контекста элементы сортируются по z-index, затем по порядку в DOM.
  4. Между контекстами — решает z-index их родительских контекстов, а не вложенных детей.
root (html)
├─ A  z-index:1   ← создаёт контекст A
│   └─ A1 z-index:9999   (заперт внутри A)
└─ B  z-index:2   ← контекст B
    └─ B1 z-index:1

B1 рисуется ПОВЕРХ A1: B(2) > A(1), хотя 9999 > 1
/* «починка»: повысить родительский контекст, а не ребёнка */
.panel-a { position: relative; z-index: 3; }
/* или изолировать поддерево от соседей */
.widget { isolation: isolate; }

⚠️ Частая ошибка: крутить z-index ребёнку, забыв, что opacity/transform на предке уже заперли его в отдельном контексте.

07

Как делать адаптивную вёрстку: mobile-first, относительные единицы, медиазапросы и современные контейнерные запросы?

Короткий ответ: Mobile-first — пишем базовые стили под узкий экран, расширяем медиазапросами min-width. Используем относительные единицы (rem, em, %, vw) вместо пикселей. Контейнерные запросы реагируют на размер родителя, а не вьюпорта — это адаптивные компоненты.

Подробно:

  1. Mobile-first — база без медиазапросов, затем @media (min-width: …) наращивает макет; меньше переопределений.
  2. Относительные единицыrem от корня (масштабируется со шрифтом пользователя), em от родителя, %/vw от контекста; пиксели — для границ/теней.
  3. Медиазапросы — реагируют на вьюпорт; брейкпоинты лучше задавать по контенту, а не по моделям устройств.
  4. Контейнерные запросы — компонент адаптируется к своему контейнеру: один и тот же блок по-разному выглядит в сайдбаре и в основной колонке.
/* mobile-first медиазапрос */
.grid { display: grid; gap: 16px; }
@media (min-width: 48rem) { .grid { grid-template-columns: 1fr 1fr; } }

/* контейнерные запросы */
.card-wrap { container-type: inline-size; }
@container (min-width: 30rem) {
  .card { grid-template-columns: 120px 1fr; }
}

⚠️ Частая ошибка: верстать desktop-first с max-width и кучей переопределений — мобильный CSS раздувается; и задавать размеры шрифта в px, ломая пользовательский зум.

08

Как масштабировать CSS в большом приложении: BEM, CSS Modules, CSS-in-JS, utility-first (Tailwind) — компромиссы?

Короткий ответ: Все четыре решают одну проблему — глобальную природу CSS и конфликты имён. BEM — соглашение об именах, CSS Modules — локальные классы на этапе сборки, CSS-in-JS — стили в JS с динамикой, utility-first — мелкие готовые классы в разметке.

Подробно:

  1. BEMblock__element--modifier; работает с любым стеком, ноль инструментов, но имена длинные и дисциплина на людях.
  2. CSS Modulesstyles.button хешируется в уникальный класс; изоляция по умолчанию, обычный CSS, но без рантайм-динамики.
  3. CSS-in-JS — стили колокированы с компонентом, легко завязать на пропсы/тему; цена — рантайм или сложный билд; тренд сместился к zero-runtime решениям.
  4. Utility-first (Tailwind) — композиция из атомарных классов; быстрая разработка, маленький итоговый CSS, но «шумная» разметка и кривая входа.
Подход Изоляция Динамика Цена
BEM по соглашению нет дисциплина
CSS Modules сборка слабая нужен бандлер
CSS-in-JS рантайм/сборка сильная вес/рантайм
Utility (Tailwind) глобально-атомарно через классы шум в HTML

⚠️ Частая ошибка: смешивать сразу несколько методологий без правил — растёт и итоговый CSS, и когнитивная нагрузка; выберите одну основную.

09

Чем псевдоклассы отличаются от псевдоэлементов? Как работают `:is()`, `:where()`, `:has()` и их специфичность?

Короткий ответ: Псевдокласс (:hover, :focus) описывает состояние существующего элемента — один двоеточие; псевдоэлемент (::before, ::first-line) создаёт/стилизует часть, которой нет в DOM — два двоеточия. :is()/:where() группируют селекторы, :has() — это «родительский» селектор.

Подробно:

  1. Псевдокласс — состояние/положение: :hover, :nth-child(2), :checked. Синтаксис :name.
  2. Псевдоэлемент — виртуальная часть: ::before, ::after, ::placeholder, ::marker. Синтаксис ::name.
  3. :is(...) — сокращает группы; берёт специфичность самого «тяжёлого» аргумента.
  4. :where(...) — то же, но специфичность всегда 0,0,0 — удобно для базовых/сбрасываемых стилей.
  5. :has(...) — матчит элемент по его потомкам/состоянию детей (родительский селектор); специфичность — по самому тяжёлому аргументу.
/* без :is — длинно */
.post h1, .post h2, .post h3 { margin-top: 0; }
/* с :is — компактно (специфичность 0,1,1) */
.post :is(h1, h2, h3) { margin-top: 0; }

/* :where — нулевая специфичность, легко переопределить */
:where(ul, ol) { padding-left: 1rem; }

/* :has — стилизуем форму, если внутри есть невалидное поле */
form:has(input:invalid) .submit { opacity: .5; }

⚠️ Частая ошибка: считать, что :is() имеет нулевую специфичность — её даёт только :where().

10

Как работают CSS-переменные (custom properties): каскад/scope, рантайм-тематизация и чем отличаются от Sass-переменных?

Короткий ответ: Custom properties (--name) наследуются и каскадируются как обычные свойства, живут в рантайме и читаются через var(). В отличие от Sass-переменных, которые компилятор подставляет на этапе сборки, CSS-переменные можно менять динамически — через классы, медиазапросы, JS.

Подробно:

  1. Объявление и чтение--gap: 16px; затем gap: var(--gap, 8px); (второй аргумент — фолбэк).
  2. Каскад и scope — значение наследуется вниз по дереву; переопределяется на любом селекторе/:root/инлайн.
  3. Рантайм — реагируют на :hover, медиазапросы, тему; читаются/пишутся из JS через getPropertyValue/setProperty.
  4. Против Sass — Sass-переменные статичны: после компиляции их нет в CSS, тему на лету не переключить.
:root { --bg: white; --fg: #111; }
[data-theme="dark"] { --bg: #111; --fg: #eee; }  /* смена темы переопределением */

.card { background: var(--bg); color: var(--fg); }

@media (prefers-color-scheme: dark) {
  :root { --bg: #111; --fg: #eee; }
}
// чтение/запись из JS
const root = document.documentElement;
root.style.setProperty('--bg', '#0b0b0b');

⚠️ Частая ошибка: ждать от var() арифметики как у Sass — для вычислений нужен calc(), а медиазапросы по самим переменным не работают.

11

В чём разница между block, inline и inline-block, и что такое нормальный поток документа?

Короткий ответ: Block-элементы занимают всю ширину строки и принимают любые размеры/отступы; inline идут в строку по содержимому и игнорируют width/вертикальные margin; inline-block — в строке, но с управляемыми размерами. Нормальный поток — порядок, в котором элементы раскладываются сверху вниз (block) и слева направо (inline) без позиционирования.

Подробно:

  1. block (<div>, <p>, <section>) — новая строка, ширина по умолчанию 100%, уважает width/height, все margin/padding.
  2. inline (<span>, <a>, <strong>) — в потоке текста; width/height и вертикальные margin игнорируются, горизонтальные — работают.
  3. inline-block — в строке, как inline, но с полноценными размерами и отступами — классика для «кнопок» в ряд.
  4. Нормальный поток — раскладка по умолчанию; position, float, flex/grid выводят из него.
display Перенос строки width/height Верт. margin
block да да да
inline нет нет нет
inline-block нет да да
.tag { display: inline-block; width: 80px; padding: 4px 8px; }

⚠️ Частая ошибка: задавать width/height на чистом inline-элементе и ждать эффекта — их там не существует; нужен inline-block или block.

12

Что вызывает layout thrashing / рефлоу при работе CSS и JS, и как этого избежать?

Короткий ответ: Layout thrashing — это многократное чередование чтения геометрии (offsetWidth, getBoundingClientRect) и записи в DOM в одном кадре. Каждое чтение после записи заставляет браузер синхронно пересчитывать layout (forced reflow). Лечится батчингом: сначала все чтения, потом все записи.

Подробно:

  1. Reflow (layout) — пересчёт геометрии; запускается изменением размеров/позиций и чтением layout-свойств.
  2. Read→write→read в цикле — каждое чтение инвалидирует кэш и форсит синхронный reflow → джанк.
  3. Батчинг — сгруппировать все чтения, затем все записи; читать вне циклов.
  4. transform/opacity — анимировать их вместо top/left/width: они идут на композитор, минуя layout и paint.
  5. will-change / contain — подсказать браузеру слой заранее и ограничить область пересчёта (contain: layout).
// ❌ thrashing: чтение и запись чередуются
for (const el of items) {
  el.style.width = el.offsetWidth + 10 + 'px'; // read→write каждую итерацию
}

// ✅ батчинг: сначала читаем, потом пишем
const widths = items.map(el => el.offsetWidth);  // все чтения
items.forEach((el, i) => { el.style.width = widths[i] + 10 + 'px'; }); // все записи

⚠️ Частая ошибка: анимировать width/top в цикле и читать getBoundingClientRect тут же — браузер форсит reflow на каждом кадре; используйте transform и батчинг.

Источники

Источники и редакционная политика

Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS