Как сверстать форму входа

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

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

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

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

Создаем 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>

Итак, давайте работать с контентом. Создадим блок с классом content, а внутрь положим два других — левый и правый. Как раз правый и будет нашей формой. Слева мы сделаем 4 элемента. Да, не три, как в макете, а 4. Первым элементом будет заголовок h1, которого на макете явно нет. Мы добавим его, а затем скроем с помощью visually-hidden класса. Так он останется читаем для скринридеров, но не будет виден другим пользователям.

Логотип я достану из макета как изображение и вставлю также. А ниже — заголовок и описание.

<div class="content__info">
  <h1 class="visually-hidden">Верстаем Клауд</h1>
  <img src="img/logo.svg" alt="Верстаем Клауд Лого" class="content__logo">
  <h2 class="content__title">
    Добро пожаловать обратно
  </h2>
  <p class="content__descr">
    Продолжите обучение, отслеживайте прогресс и открывайте новые практические задания.
  </p>
</div>

Справа у нас форма. Добавляем ее, и не забываем добавить два атрибута — action и method. В первый мы вставим ссылку из задания https://postman-echo.com/post, а во второй вставим POST. Так мы сможем проверить, все ли правильно улетает из нашей формы.

Далее добавим заголовок h3 (т.к. h1 и h2 уже были использованы), и описание.

<h3 class="content__subtitle">
  Вход в систему
</h3>
<p class="content__subdescr">
  Используйте электронную почту и пароль.
</p>

А вот ниже пойдут поля формы. У полей есть подписи сверху, поэтому правильно будет эти подписи связать с инпутом. Можно сделать эту подпись тегом <label>, и связать с полем через атрибут for, но как по мне, просто обернуть и текст, и поле в <label> гораздо проще. Так и поступим. И обязательно укажем все важные атрибуты для поля ввода. Правильный type, заполненный name, placeholder и required.

<label class="content__label">
  <span class="content__caption">Электронная почта</span>
  <input type="email" name="email" placeholder="ivan@example.com" class="content__input" required>
</label>
<label class="content__label">
  <span class="content__caption">Пароль</span>
  <input type="password" name="password" placeholder="******" class="content__input" required>
</label>

Атрибут name нужен, если мы хотим, чтобы из нашей формы вообще что-то отправилось. Без него поле вообще не войдет в «отправляемые». required мы добавили, чтобы поля были обязательными, ведь вход не может быть произведен, если не введена почта и пароль.

Далее у нас блок, в котором находится чекбокс и кнопка «забыли пароль». Чекбокс делаем также в лейбле, и сразу называем его .custom-checkbox, т. к. чекбоксы не могут стилизоваться обычным методом. Нам придется хитрить, но про это в разделе CSS.

<div class="content__addition">
  <label class="custom-checkbox">
    <input type="checkbox" name="remember" class="custom-checkbox__input" required>
    <span class="custom-checkbox__text">Запомнить меня</span>
  </label>
  <button class="content__forgot" type="button">Забыли пароль?</button>
</div>

Обратите внимание, у кнопки явно задан type="button". Это сделает кнопку обычной. Если атрибут не указать, то кнопка внутри формы автоматически будет пытаться ее отправить, что нам в данном случае не нужно.

Остается кнопка, и подпись к ней. Вот у кнопки отправки мы явно добавим type="submit".

<button class="content__btn" type="submit">Войти</button>
<div class="content__txt">
  Нет аккаунта? <button class="content__create" type="button">Создать профиль</button>
</div>

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

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

* {
  box-sizing: border-box;
}

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

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

body {
  margin: 0;
  padding: 20px;
  font-family: "Segoe UI", sans-serif;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  border: 0;
  padding: 0;

  white-space: nowrap;
  clip-path: inset(100%);
  clip: rect(0 0 0 0);
  overflow: hidden;
}

Здесь я как раз добавил тот самый класс .visually-hidden. Посмотрите на свойства. Все они нужны, чтобы скрыть элемент внешне полностью, но при этом не скрывать от скринридеров.

Далее быстро расставим элементы на места и зададим им фон.

.content {
  display: flex;
  margin: 0 auto;
  max-width: 1040px;
}

.content__info,
.content__form {
  padding: 52px;
  width: 50%;
}

.content__info {
  background: #7c5cff;
}

.content__form {
  background: #10162e;
}

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

.content__logo {
  margin-bottom: 40px;
}

.content__title {
  margin: 0;
  margin-bottom: 20px;
  font-weight: 700;
  font-size: 52px;
  line-height: 120%;
  color: #fff;
}

.content__descr {
  margin: 0;
  font-weight: 400;
  font-size: 20px;
  line-height: 120%;
  color: rgba(255, 255, 255, 0.92);
}

Теперь текстовые элементы в правой части. Тоже берем из макета.

.content__subtitle {
  margin: 0;
  margin-bottom: 16px;
  font-weight: 700;
  font-size: 36px;
  line-height: 120%;
  color: #fff;
}

.content__subdescr {
  margin: 0;
  margin-bottom: 40px;
  font-weight: 400;
  font-size: 16px;
  line-height: 120%;
  color: rgba(255, 255, 255, 0.92);
}

Переходим к стилизации формы, к самому интересному. Лейблу сделаем flex, чтобы расставить элементы внутри. У инпута нам нужны будут отступы и слева, и справа, чтобы контент не лип к краям, также нужна будет высота из макета. И конечно, не забудем указать font-family: inherit, иначе шрифт не применится. Также мы добавим псевдокласс ::placeholder, который позволит нам стилизовать цвет текста, который мы видим еще до ввода своего.

.content__label {
  display: flex;
  flex-direction: column;
  margin-bottom: 16px;
  gap: 8px;
}

.content__caption {
  font-weight: 400;
  font-size: 12px;
  line-height: 120%;
  color: #c9d1ff;
}

.content__input {
  border: none;
  border-radius: 16px;
  padding: 0px 16px;
  height: 54px;
  font-family: inherit;
  font-weight: 400;
  font-size: 16px;
  line-height: 120%;
  color: #fff;
  background: #1b2344;
}

.content__input::placeholder {
  color: #fff;
}

Далее блок с чекбоксом. Ему дадим flex и расставим элементы по краям. А вот сам чекбокс — это интересно. По сути, управлять всем будет <input>, но мы скроем его, заменив на свое представление.

.content__addition {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  gap: 20px;
}

.custom-checkbox {
  position: relative;
  cursor: pointer;
}

.custom-checkbox__input {
  position: absolute;
  opacity: 0;
  appearance: none;
}

.custom-checkbox__text {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-weight: 400;
  font-size: 12px;
  line-height: 120%;
  color: #b9e3ff;
  gap: 8px;
}

.custom-checkbox__text::before {
  content: "";
  display: block;
  border: 1px solid #b9e3ff;
  border-radius: 4px;
  width: 16px;
  height: 16px;
}

.custom-checkbox__text::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 5px;
  border-radius: 2px;
  width: 8px;
  height: 8px;
  background: #b9e3ff;
  opacity: 0;
  transition: opacity 0.3s;
}

.custom-checkbox__input:checked + .custom-checkbox__text::after {
  opacity: 1;
}

.custom-checkbox__input:focus + .custom-checkbox__text {
  outline: 1px solid #b9e3ff;
  outline-offset: 3px;
  border-radius: 2px;
}

Давайте разберем, что же тут сделано. Ну, во-первых, мы даем cursor: pointer для лейбла, делая его как бы кликабельным. Затем явно скрываем инпут с помощью opacity и appearance. Оно как раз и нужно, чтобы сбросить внешний вид элемента.

Далее задаем стили тексту, в том числе flexbox. И вот тут начинается магия — мы делаем псевдоэлемент ::before для текста, и рисуем с его помощью чекбокс — по сути, просто квадрат с обводкой. А с помощью ::after рисуем квадратик, который будет внутри, то есть будет обозначать, отмечен чекбокс или нет. Этот квадратик мы по умолчанию скроем с помощью opacity.

И теперь, как я и говорил, всем будет управлять скрытый <input>. Важно не скрывать его с помощью display: none, иначе он не будет работать.

.custom-checkbox__input:checked + .custom-checkbox__text::after {
  opacity: 1;
}

Здесь мы явно говорим «если чекбокс отмечен, найди следующий элемент (через +), и у него псевдоэлементу ::after задай opacity: 1„. Мы используем фишку с комбинаторными селекторами, и находим следующий за инпутом элемент через селектор „+“. Как вы понимаете, если бы структура html была другой, это бы просто не сработало. Имейте это ввиду.

Ну, а далее просто стилизуем остаточные элементы из макета. Важно тут не забывать давать кнопкам font-family: inherit, cursor: pointer, а также задавать для них состояние :hover.

.content__forgot {
  border: none;
  padding: 0;
  font-family: inherit;
  font-weight: 400;
  font-size: 12px;
  line-height: 120%;
  color: #b9e3ff;
  background: transparent;
  cursor: pointer;
  transition: color 0.3s;
}

.content__forgot:hover {
  color: #76b6e2;
}

.content__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 20px;
  padding: 16px;
  width: 100%;
  min-height: 58px;
  font-family: inherit;
  font-weight: 700;
  font-size: 16px;
  line-height: 120%;
  color: #fff;
  background: #17b79a;
  cursor: pointer;
  transition: background 0.3s;
}

.content__btn:hover {
  background: #76b6e2;
}

.content__txt {
  margin-top: 16px;
  text-align: center;
  font-weight: 400;
  font-size: 12px;
  line-height: 120%;
  text-align: center;
  color: #9ea9d6;
}

.content__create {
  border: none;
  padding: 0;
  font-family: inherit;
  font-weight: 600;
  font-size: 12px;
  line-height: 120%;
  text-align: center;
  color: #9ea9d6;
  background: transparent;
  cursor: pointer;
  transition: color 0.3s;
}

.content__create:hover {
  color: #76b6e2;
}

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

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

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