Как сверстать интерфейс чата

Эта статья основана на одном из практических заданий verstaem.online.

Вы можете читать материал как обычное руководство или сначала попробовать выполнить задание самостоятельно, а затем сравнить свое решение с подробным разбором.

Перейти к заданию

В этой статье мы создадим полноценный интерфейс чата с боковой панелью диалогов, областью сообщений и формой отправки текста. На этом примере разберем построение сложного макета с помощью Flexbox, организацию независимых компонентов, работу с переполнением, адаптивную перестройку интерфейса и создание современного мессенджера средствами HTML и CSS.

Создаем HTML-разметку

Сначала создадим базовую HTML-разметку с помощью Emmet. Для этого достаточно ввести ! и нажать Tab. Обратите внимание на атрибут lang. Поскольку основной контент страницы написан на русском языке, здесь используем значение ru.

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Интерфейс чата</title>
  </head>
  <body>
  </body>
</html>

Приступаем к реализации чата. Сразу дам небольшой совет — не пытайтесь верстать все сразу. Один из самых важных навыков верстальщика — умение разбивать на компоненты, независимые части. Именно так мы и поступим.

Итак, у нас есть два крупных блока — боковая панель и область текущей переписки. Размечаем общий блок и область через <div>, а вот боковую панель через тег <aside>, он идеально подойдет под вспомогательную область страницы.

<div class="chat">
    <aside class="chat__aside"></aside>

    <div class="chat__content"></div>
</div>

Левая панель

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

<aside class="chat__aside">
   <div class="chat__top"></div>

   <input class="chat__input">

   <ul class="chat__items">
       ...
   </ul>
</aside>

Давайте сделаем верхний блок и инпут более обстоятельно, заполнив все необходимое.

<div class="chat__top">
  <h1 class="chat__title">Сообщения</h1>
  <button class="chat__btn" aria-label="Написать сообщение">
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <g clip-path="url(#clip0_22002_5)">
        <path
          d="M14.116 4.54134C14.4685 4.18895 14.6665 3.71098 14.6666 3.21257C14.6666 2.71416 14.4687 2.23614 14.1163 1.88367C13.7639 1.5312 13.286 1.33315 12.7876 1.33308C12.2892 1.33302 11.8111 1.53095 11.4587 1.88334L2.56133 10.7827C2.40654 10.937 2.29207 11.127 2.228 11.336L1.34733 14.2373C1.3301 14.295 1.3288 14.3562 1.34356 14.4146C1.35833 14.4729 1.38861 14.5262 1.43119 14.5687C1.47378 14.6112 1.52708 14.6414 1.58544 14.6561C1.64379 14.6707 1.70504 14.6693 1.76266 14.652L4.66466 13.772C4.87344 13.7085 5.06345 13.5947 5.218 13.4407L14.116 4.54134Z"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        />
        <path
          d="M10 3.33333L12.6667 5.99999"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        />
      </g>
      <defs>
        <clipPath id="clip0_22002_5">
          <rect width="16" height="16" fill="white" />
        </clipPath>
      </defs>
    </svg>
  </button>
</div>
<input
  type="text"
  class="chat__input"
  placeholder="Поиск диалогов..."
/>

Здесь обратите внимание на кнопку. Она не имеет текстового содержимого, только иконку, поэтому мы должны задать ей текст самостоятельно через атрибут aria-label, сделав элемент доступным. Также важно, что у иконки убраны атрибуты stroke, так как менять цвет мы будем через CSS.

Список и карточка диалогов

Далее, внутри списка диалогов мы реализуем карточку диалога. Мы создадим отдельный компонент, и обозначим это через БЭМ — chat-user-card. Это будет самостоятельный элемент, который можно будет вставить куда угодно, и он не должен никак влиять на внешнее содержимое. Например, не должен иметь отступов.

Итак, внутри него будет аватарка, имя, время сообщения, а также его текст. Саму карточку мы разметим через ссылку. Это нужно для тех случаев, когда пользователь, например, может отключить работу JavaScript в браузере. В таком случае сработает обычная ссылка, и он все равно сможет пользоваться сайтом. Также обратите внимание на то, как сделано время. Используется специальный тег <time>. Кстати, обязательно заберите из макета полный текст описания в карточке, даже несмотря на то, что здесь используется многоточие. Его мы сделаем через CSS.

<ul class="chat__items">
  <li class="chat__item">
    <a href="/chat/123/" class="chat-user-card chat-user-card--active">
      <img
        src="img/1.png"
        alt="Алексей"
        class="chat-user-card__avatar"
      />
      <div class="chat-user-card__text">
        <div class="chat-user-card__top">
          <div class="chat-user-card__name">Алексей</div>
          <time
            datetime="2026-07-06T14:32:00"
            class="chat-user-card__time"
            >14:32</time
          >
        </div>
        <p class="chat-user-card__descr">
          Давай обновим layout карточек и посмотрим на spacing
        </p>
      </div>
    </a>
  </li>
  <li class="chat__item">
    <a href="/chat/123/" class="chat-user-card">
      <img src="img/2.png" alt="Мария" class="chat-user-card__avatar" />
      <div class="chat-user-card__text">
        <div class="chat-user-card__top">
          <div class="chat-user-card__name">Мария</div>
          <time
            datetime="2026-07-06T00:00:00"
            class="chat-user-card__time"
            >Вчера</time
          >
        </div>
        <p class="chat-user-card__descr">
          Я закончила адаптив для страницы профиля
        </p>
      </div>
    </a>
  </li>
</ul>

Правая часть

Основная область переписки также состоит из трех частей — верхушка, сама область сообщений и форма отправки.

<div class="chat__content">

    <div class="chat__panel"></div>

    <div class="chat__messages"></div>

    <div class="chat__bottom"></div>

</div>

Верхняя панель

В верхней панели расположены данные собеседника и пара кнопок действия. Однако, если вы посмотрите на мобильную версию макета, там добавляется или и кнопка-крестик. Мы должны тоже добавить ее в HTML. И кстати, обратите внимание, что кнопки точно такие же, как и кнопка создания чата. Поэтому мы будем использовать ровно те же классы.

<div class="chat__panel">
  <button class="chat__close" aria-label="Закрыть">
    <svg
      width="20"
      height="20"
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M15 5L5 15"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      />
      <path
        d="M5 5L15 15"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
      />
    </svg>
  </button>
  <div class="chat-user">
    <img
      src="img/1.png"
      alt="Алексей Орлов"
      class="chat-user__avatar"
    />
    <div class="chat-user__text">
      <div class="chat-user__name">Алексей Орлов</div>
      <div class="chat-user__status">Онлайн</div>
    </div>
  </div>
  <div class="chat__btns">
    <button class="chat__btn" aria-label="Позвонить">
      <svg
        width="16"
        height="16"
        viewBox="0 0 16 16"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <g clip-path="url(#clip0_22002_59)">
          <path
            d="M9.22125 11.0453C9.35894 11.1086 9.51405 11.123 9.66105 11.0863C9.80804 11.0496 9.93814 10.9639 10.0299 10.8433L10.2666 10.5333C10.3908 10.3677 10.5518 10.2333 10.737 10.1408C10.9221 10.0482 11.1263 10 11.3333 10H13.3333C13.6869 10 14.026 10.1405 14.2761 10.3905C14.5261 10.6406 14.6666 10.9797 14.6666 11.3333V13.3333C14.6666 13.687 14.5261 14.0261 14.2761 14.2762C14.026 14.5262 13.6869 14.6667 13.3333 14.6667C10.1507 14.6667 7.09841 13.4024 4.84797 11.152C2.59753 8.90152 1.33325 5.84927 1.33325 2.66668C1.33325 2.31305 1.47373 1.97392 1.72378 1.72387C1.97382 1.47382 2.31296 1.33334 2.66659 1.33334H4.66659C5.02021 1.33334 5.35935 1.47382 5.60939 1.72387C5.85944 1.97392 5.99992 2.31305 5.99992 2.66668V4.66668C5.99992 4.87367 5.95173 5.07782 5.85915 5.26296C5.76658 5.4481 5.63218 5.60915 5.46658 5.73334L5.15458 5.96734C5.0322 6.06079 4.94593 6.19373 4.91045 6.34358C4.87496 6.49342 4.89244 6.65093 4.95992 6.78934C5.87104 8.63992 7.36953 10.1365 9.22125 11.0453Z"
            stroke-width="1.6"
            stroke-linecap="round"
            stroke-linejoin="round"
          />
        </g>
        <defs>
          <clipPath id="clip0_22002_59">
            <rect width="16" height="16" />
          </clipPath>
        </defs>
      </svg>
    </button>
    <button class="chat__btn" aria-label="Еще">
      <svg
        width="16"
        height="16"
        viewBox="0 0 16 16"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M7.66667 8.33333C8.03486 8.33333 8.33333 8.03486 8.33333 7.66667C8.33333 7.29848 8.03486 7 7.66667 7C7.29848 7 7 7.29848 7 7.66667C7 8.03486 7.29848 8.33333 7.66667 8.33333Z"
          stroke-width="1.6"
          stroke-linecap="round"
          stroke-linejoin="round"
        />
        <path
          d="M12.6667 8.66668C13.0349 8.66668 13.3333 8.3682 13.3333 8.00001C13.3333 7.63182 13.0349 7.33334 12.6667 7.33334C12.2985 7.33334 12 7.63182 12 8.00001C12 8.3682 12.2985 8.66668 12.6667 8.66668Z"
          stroke-width="1.6"
          stroke-linecap="round"
          stroke-linejoin="round"
        />
        <path
          d="M3.33341 8.66668C3.7016 8.66668 4.00008 8.3682 4.00008 8.00001C4.00008 7.63182 3.7016 7.33334 3.33341 7.33334C2.96522 7.33334 2.66675 7.63182 2.66675 8.00001C2.66675 8.3682 2.96522 8.66668 3.33341 8.66668Z"
          stroke-width="1.6"
          stroke-linecap="round"
          stroke-linejoin="round"
        />
      </svg>
    </button>
  </div>
</div>

Сообщения

Каждое сообщение у нас — это отдельный блок, в котором находится текст, а также время отправки.

<div class="chat-message">
    <div class="chat-message__inner">
        ...
    </div>

    <time class="chat-message__time">
        ...
    </time>
</div>

Мы также будем разделять сообщение на свои и чужие, добавляяя отдельный CSS-класс. И конечно, время мы снова сделаем через тег <time>, обязательно указав атрибут datetime.

<div class="chat__messages">
  <div class="chat-message">
    <div class="chat-message__inner">
      Давай попробуем сделать карточки компактнее, а отступы внутри
      интерфейса привести к одной системе.
    </div>
    <time datetime="2026-07-06T14:21:00" class="chat-message__time"
      >14:21</time
    >
  </div>
  <div class="chat-message chat-message--mine">
    <div class="chat-message__inner">
      Да, и ещё нужно пересобрать layout блока прогресса.
    </div>
    <time datetime="2026-07-06T14:24:00" class="chat-message__time"
      >14:24</time
    >
  </div>
  <div class="chat-message">
    <div class="chat-message__inner">
      Отлично, тогда вечером подготовлю новый вариант интерфейса.
    </div>
    <time datetime="2026-07-06T14:28:00" class="chat-message__time"
      >14:28</time
    >
  </div>
</div>

Форма отправки

Осталось простое — сделать форму. Используем тег <form>, и делаем внутри input и кнопку. Добавлять атрибут type для кнопки в данном случае необязательно, но так вы сами будете быстрее понимать, что к чему.

<div class="chat__bottom">
  <form class="chat__form">
    <input
      type="text"
      placeholder="Напишите сообщение..."
      class="chat__input"
    />
    <button class="chat__send" type="submit">Отправить</button>
  </form>
</div>

Добавляем стили

Подключим файл стилей через <link> и зададим несколько базовых правил. Для этого задания достаточно переопределить box-sizing, сбросить отступ у <body> и подключить шрифт Inter, который используется в макете.

* {
  box-sizing: border-box;
}

@font-face {
  font-family: "Inter";
  src: url("../fonts/InterRegular.woff2");
  font-display: swap;
  font-weight: 400;
}

@font-face {
  font-family: "Inter";
  src: url("../fonts/InterBold.woff2");
  font-display: swap;
  font-weight: 700;
}

body {
  margin: 0;
  min-width: 360px;
  font-family: "Inter", sans-serif;
  background: #edf2f7;
}

Поскольку наш интерфейс будет адаптивным, я добавил еще одну запись — min-width: 360px, что позволит нашему сайту при достижении ширины 360 и ниже соответствовать макету.

Стили крупных блоков

Обертке нужно задать flexbox и отступ. При этом, левой колонке мы задалим фиксированную ширину прямо из макета, а правой — flex: 1, чтобы она тянулась на все оставшееся пространство.

Также для обертки мы зададим высоту 100vh, что соответствует 100% от экрана пользователя. Это сделает наш интерфейс зависимым от высоты экрана (браузера).

.chat {
  display: flex;
  gap: 24px;
  padding: 16px;
  height: 100vh;
}

.chat__aside {
  display: flex;
  flex-direction: column;
  border: 1px solid #dde5ef;
  border-radius: 32px;
  padding: 24px;
  width: 380px;
  background: #fff;
  gap: 20px;
}

.chat__content {
  display: flex;
  flex-direction: column;
  border: 1px solid #dde5ef;
  border-radius: 32px;
  flex: 1;
  background: #fff;
}

Верхний блок боковой панели

Оформляем верхний блок, а также его содержимое. Дадим flex для блока, расставим элементы внутри. И кнопку сделаем так, чтобы она была универсальной — зададим размеры, выровняем все по центру. И конечно, не забудем про :hover.

.chat__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.chat__title {
  margin: 0;
  font-weight: 700;
  font-size: 24px;
  color: #111827;
}

.chat__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 12px;
  padding: 0;
  width: 40px;
  height: 40px;
  color: #111827;
  background: #f3f6fb;
  cursor: pointer;
  transition: background 0.3s;
}

.chat__btn:hover {
  background: #d8dbe0;
}

.chat__btn svg {
  stroke: currentColor;
}

Также можем сразу задать стили и для кнопки закрытия.

.chat__close {
  display: none;
  align-items: center;
  justify-content: center;
  border: none;
  padding: 0;
  width: 20px;
  height: 20px;
  color: #111827;
  background: transparent;
  cursor: pointer;
  transition: background 0.3s;
}

.chat__close svg {
  stroke: currentColor;
}

Здесь я делаю зависимость цвета иконки от цвета текста, что может быть удобно. Ну и конечно, здесь нужен display: none, чтобы кнопку не было видно на десктоп-устройствах.

Инпут поиска

Тут все просто. Задаем стили из макета и не боимся фиксированной высоты. Для инпутов она разрешена.

.chat__input {
  border: none;
  border-radius: 16px;
  padding: 0 16px;
  height: 52px;
  font-family: inherit;
  font-weight: 400;
  font-size: 16px;
  color: #6b7280;
  background: #f3f6fb;
}

Список в боковой панели

Здесь используем flexbox, чтобы расставить элементы в колонку, а также используем простой, но хитрый ход — flex: 1; oveflow-y: auto. Так мы «тянем» список на всю оставшуюся высоту боковой панели и говорим «если не помещается контент — показывай скролл». Теперь наш список еще и адаптивен, т. к. подойдет под любую высоту экрана.

.chat__items {
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  gap: 12px;
}

Карточка в боковой панели

Теперь сделаем карточку боковой панели, и она полностью построена на flexbox. Общая карточка делает в строку аватарку и текст, а внутренний flexbox делает в строку имя и время. Обязательно задаем размеры и object-fit: cover для изображений, чтобы любое вставленное изображение не сломало верстку.

И да, как я говорил, мы будем обрезать текст сообщения, если оно слишком длинное. Для этого используем связку overflow: hidden; white-space: nowrap; text-overflow: ellipsis;. И обратите внимание на то, как задана ширина для текстовой части карточки — calc(100% - 56px - 12px). Мы считаем ширину текста как вся ширина карточки минус ширина аватара минус размер отступа. Получаем все оставшееся пространство.

.chat-user-card {
  display: flex;
  align-items: center;
  border-radius: 20px;
  padding: 12px;
  width: 100%;
  text-decoration: none;
  gap: 12px;
  transition: background 0.3s;
}

.chat-user-card:hover {
  background: #f3f6fb;
}

.chat-user-card--active {
  pointer-events: none;
  background: #f3f6fb;
}

.chat-user-card__avatar {
  display: block;
  border-radius: 16px;
  width: 56px;
  height: 56px;
  object-fit: cover;
}

.chat-user-card__text {
  width: calc(100% - 56px - 12px);
}

.chat-user-card__top {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
  gap: 12px;
}

.chat-user-card__name {
  font-weight: 700;
  font-size: 16px;
  color: #111827;
}

.chat-user-card__time {
  font-weight: 400;
  font-size: 12px;
  color: #6b7280;
}

.chat-user-card__descr {
  overflow: hidden;
  margin: 0;
  width: 100%;
  font-weight: 400;
  font-size: 14px;
  color: #6b7280;
  white-space: nowrap;
  text-overflow: ellipsis;
}

Верхняя панель переписки

Здесь снова работает уже знакомый прием. Информация располагается слева, кнопки — справа. Однако, вместо space-between давайте используем авто-отступ. Этот отступ займет все свободное пространство, и толкнет кнопки вправо.

.chat__panel {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #dde5ef;
  padding: 24px;
  gap: 10px;
}

.chat-user {
  display: flex;
  align-items: center;
  gap: 12px;
}

.chat-user__avatar {
  display: block;
  border-radius: 16px;
  width: 56px;
  height: 56px;
  object-fit: cover;
}

.chat-user__name {
  overflow: hidden;
  margin-bottom: 8px;
  font-weight: 700;
  font-size: 16px;
  color: #111827;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.chat-user__status {
  font-weight: 400;
  font-size: 14px;
  color: #6b7280;
}

.chat__btns {
  display: flex;
  align-items: center;
  margin-left: auto;
  gap: 12px;
}

Обратите внимание, здесь я снова использовал text-overflow: ellipsis, но уже для имени пользователя. Это нужно для мобильных устройств, если вас зовут Константин Константинопольский :)

Сообщения

Каждое сообщение — это свой flex-контейнер с вертикальным направлением. По умолчанию расположим их слева, а через модификатор --mine сделаем справа. Это можно сделать с помощью align-items. Ну, а элементам просто задаем стили из макета. Кстати, с помощью модификатора мы еще и меняем цвет сообщения на синий.

.chat__messages {
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow-y: auto;
  padding: 24px;
  gap: 16px;
}

.chat-message {
  display: flex;
  align-items: flex-start;
  flex-direction: column;
  gap: 8px;
}

.chat-message__inner {
  border-radius: 24px;
  padding: 16px 20px;
  max-width: 520px;
  font-weight: 400;
  font-size: 16px;
  line-height: 160%;
  color: #111827;
  background: #f3f6fb;
}

.chat-message__time {
  padding: 0px 6px;
  font-weight: 400;
  font-size: 12px;
  line-height: 160%;
  color: #6b7280;
}

.chat-message--mine {
  align-items: flex-end;
}

.chat-message--mine .chat-message__inner {
  color: #fff;
  background: #2563eb;
}

Обратите внимание, что для сообщений мы также задаем flex: 1; overflow-y: auto;, то есть тянем их на максимальный размер родителя, и включаем скролл, если элементы не помещаются. Так у нас форма сообщения всегда будет прибита к низу, а сами сообщения, даже если их будут сотни, всегда можно будет прочитать, поскроллив блок.

Форма

Форма устроена просто. Инпуту мы задаем flex: 1, чтобы тянулся на все свободное место, а кнопке просто задаем ее стили. Не забудьте про font-family: inherit, чтобы и инпут, и кнопка получили шрифт.

.chat__bottom {
  margin-top: auto;
  border-top: 1px solid #dde5ef;
  padding: 24px;
}

.chat__form {
  display: flex;
  gap: 16px;
}

.chat__form .chat__input {
  flex: 1;
}

.chat__send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 16px;
  padding: 10px 20px;
  font-family: inherit;
  font-weight: 700;
  font-size: 16px;
  line-height: 120%;
  color: #fff;
  background: #2563eb;
  cursor: pointer;
  transition: background 0.3s;
}

.chat__send:hover {
  background: #225ad3;
}

Адаптив

Итак, теперь нам нужно сделать так, чтобы наш чат хорошо смотрелся на мобильном устройстве. Не могу не сказать об одном компромиссе. Поскольку мы не используем в этом задании Javascript, на мобильном устройстве мы покажем сразу два блока — левую панель, а под ней чат, хотя по логике они должны подменять друг друга. Вы можете реализовать данное поведение самостоятельно, если хотите. Наша же задача — верстка, а не интерактивность.

Теперь о самом адаптиве. На самом деле, интерфейс чата такой, что мы можем сделать буквально один медиа-запрос, чтобы перестроить его как нам надо. Если вы начнете сжимать браузер, то заметите, что где-то на 840 пикселях уже проблемы. Поэтому я предлагаю исправить их заранее — взять безопасный брейкпоинт 992 и использовать его. Больше нам ничего не понадобится.

Пожалуй, вдаваться в подробности по каждому элементу не имеет смысла, мы в основном берем все размеры и отступы из макета. Пожалуй, самое важное тут — это то, как используется flexbox, и практически всегда мы просто меняем flex-direction на column, таким образом быстро и легко перестраивая верстку.

@media (max-width: 992px) {
  .chat {
    flex-direction: column;
  }

  .chat__aside {
    padding: 16px;
    width: 100%;
  }

  .chat__content {
    width: 100%;
  }

  .chat__bottom {
    padding: 16px;
  }

  .chat__form {
    flex-direction: column;
  }

  .chat__form .chat__input {
    flex: initial;
  }

  .chat__send {
    min-height: 52px;
  }

  .chat-user {
    gap: 8px;
  }

  .chat-user__avatar {
    width: 42px;
    height: 42px;
  }

  .chat-user__name {
    margin-bottom: 4px;
    max-width: 100px;
    font-size: 12px;
  }

  .chat-user__status {
    font-size: 10px;
  }

  .chat__messages {
    padding: 16px;
  }

  .chat-message {
    gap: 4px;
  }

  .chat-message__inner {
    padding: 12px 16px;
    max-width: 240px;
    font-size: 14px;
  }

  .chat-message__time {
    font-size: 10px;
  }

  .chat__close {
    display: flex;
  }

  .chat__aside,
  .chat__content {
    height: 100%;
  }
}

На первый взгляд этот интерфейс кажется очень большим. Но если разбить его на небольшие независимые компоненты, окажется, что внутри используются всё те же приемы, которые мы уже могли делать раньше: Flexbox, переиспользуемые блоки, семантическая разметка, переполнение, margin-left: auto, flex: 1, overflow-y: auto и медиазапросы. Именно так обычно и строятся современные интерфейсы веб-приложений.

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

Решение задачи

предыдущий пост следующий пост