Как сверстать новостной портал
Эта статья основана на одном из практических заданий verstaem.online.
Вы можете читать материал как обычное руководство или сначала попробовать выполнить задание самостоятельно, а затем сравнить свое решение с подробным разбором.
Перейти к заданиюВ этом задании мы сверстаем главную страницу новостного портала. Здесь сразу встречается несколько разных типов блоков: большая главная новость, боковая колонка с последними публикациями, карточки статей и полноценная сетка контента.
Создаем 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>
Это задание максимально похоже на настоящий сайт, а не его фрагмент, поэтому мы будем верстать его также — с использованием шапки, main и так далее.
Шапку как раз делаем тегом <header>. Внутри нее расположим контейнер, который выровняет по центру весь контент, а уже внутри него — основное содержимое. Именно блоку внутри контейнера мы зададим обводку снизу, как на макете.
Ну, а в шапке добавляем дату, заголовок, и меню. Меню, конечно, реализуем через <nav>, а внутри используем список. Это максимально семантичный вариант верстки меню, когда скринридер поймет и что это навигация, и что здесь есть список и у него определенное количество элементов.
<header class="header">
<div class="container">
<div class="header__inner">
<div class="header__date">22 июня 2026</div>
<h1 class="header__title">Ежедневные новости</h1>
<nav class="nav">
<ul class="nav__list">
<li class="nav__item">
<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>
<li class="nav__item">
<a href="#" class="nav__link">Наука</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Культура</a>
</li>
</ul>
</nav>
</div>
</div>
</header>
Далее у нас идут статьи — основное содержимое сайта. Поэтому их стоит заключить в тег <main>. А сами статьи еще дополнительно в <section>.
Поскольку секции обязательно должны иметь внутри себя заголовок — делаем его. Даже если его нет в макете. Мы просто скроем его, используя паттерн visually-hidden. Этот паттерн подразумевает, что элемент внешне скрывается, но остается читаем для скринридеров. И конечно же, не забудьте container вместе с классом grid.
<main>
<section class="news">
<h2 class="visually-hidden">Список новостей</h2>
<div class="container grid">
</div>
</section>
</main>
Сделаем статью через <article>, а боковую колонку через <aside>, В остальном тут все стандартно, разве что рекомендую обратить внимание на уровни заголовков. Кстати, заметьте, что в данной статье изображение сделано не фоном, как могло показаться, а через <img>, и при этом у него не заполнен alt. Это сделано специально, чтобы показать, что таким образом тоже можно делать фон. А aria-hidden как раз скрывает изображение от скринридеров полностью, поэтому и заполнение alt необязательно.
<article class="news__main">
<img src="img/1.png" aria-hidden="true" alt="" class="news__pic" />
<div class="news__text">
<a href="#" class="news__category">Технологии</a>
<h3 class="news__title">
<a href="#" class="news__link">
Искусственный интеллект меняет привычные подходы к разработке
</a>
</h3>
<p class="news__descr">
Компании активно внедряют новые инструменты, повышая скорость
создания цифровых продуктов.
</p>
</div>
</article>
<aside class="news__aside">
<h3 class="news__subtitle">Последние новости</h3>
<ul class="news__list">
<li class="news__item">
<article class="news-mini-card">
<a href="#" class="news-mini-card__category">Экономика</a>
<h4 class="news-mini-card__title">
<a href="#" class="news-mini-card__link">
Рынок показывает уверенный рост второй месяц подряд.
</a>
</h4>
</article>
</li>
<li class="news__item">
<article class="news-mini-card">
<a href="#" class="news-mini-card__category">Наука</a>
<h4 class="news-mini-card__title">
<a href="#" class="news-mini-card__link">
Учёные представили новый материал для аккумуляторов.
</a>
</h4>
</article>
</li>
<li class="news__item">
<article class="news-mini-card">
<a href="#" class="news-mini-card__category">Спорт</a>
<h4 class="news-mini-card__title">
<a href="#" class="news-mini-card__link">
Финал чемпионата завершился неожиданной победой.
</a>
</h4>
</article>
</li>
<li class="news__item">
<article class="news-mini-card">
<a href="#" class="news-mini-card__category">Культура</a>
<h4 class="news-mini-card__title">
<a href="#" class="news-mini-card__link">
Открылась выставка современного искусства.
</a>
</h4>
</article>
</li>
</ul>
</aside>
Также, не забудьте, что статьи мы делаем через <article>, именно поэтому он используется и внутри боковой панели.
Ну, а ниже у нас классический список из нескольких элементов-статей. Здесь все довольно просто, главное указывать alt изображениям и вложить ссылки внутрь заголовок статей, иначе статьи будут некликабельными, и потеряют свой смысл. Также, мы задаем списку еще один класс grid, чтобы сделать сетку внутри списка. И конечно, последним двум элементам списка мы добавляем дополнительный класс news__item--big, чтобы с помощью гридов сделать им другой размер.
<ul class="news__items grid">
<li class="news__item">
<article class="news-card">
<img
src="img/2.png"
alt="Заседание в Думе"
class="news-card__pic"
/>
<div class="news-card__text">
<h3 class="news-card__title">
<a href="#" class="news-card__link"> Политика </a>
</h3>
<p class="news-card__descr">
Обзор ключевых событий за последние сутки.
</p>
</div>
</article>
</li>
<li class="news__item">
<article class="news-card">
<img
src="img/3.png"
alt="Материнская плата"
class="news-card__pic"
/>
<div class="news-card__text">
<h3 class="news-card__title">
<a href="#" class="news-card__link"> Технологии </a>
</h3>
<p class="news-card__descr">
Обзор ключевых событий за последние сутки.
</p>
</div>
</article>
</li>
<li class="news__item">
<article class="news-card">
<img
src="img/4.png"
alt="Подсчет финансов"
class="news-card__pic"
/>
<div class="news-card__text">
<h3 class="news-card__title">
<a href="#" class="news-card__link"> Бизнес </a>
</h3>
<p class="news-card__descr">
Главные события финансового рынка.
</p>
</div>
</article>
</li>
<li class="news__item news__item--big">
<article class="news-card">
<img
src="img/5.png"
alt="Человек читает газету"
class="news-card__pic"
/>
<div class="news-card__text">
<h3 class="news-card__title">
<a href="#" class="news-card__link">
Большой материал недели
</a>
</h3>
<p class="news-card__descr">
Подробный разбор главной темы с комментариями экспертов и
аналитикой.
</p>
</div>
</article>
</li>
<li class="news__item">
<article class="news-card">
<img
src="img/6.png"
alt="Человек смотрит в камеру"
class="news-card__pic"
/>
<div class="news-card__text">
<h3 class="news-card__title">
<a href="#" class="news-card__link"> Интервью </a>
</h3>
<p class="news-card__descr">
Разговор с руководителем крупной технологической компании.
</p>
</div>
</article>
</li>
</ul>
Добавляем стили
Подключим файл стилей через <link> и зададим несколько базовых правил. Для этого задания достаточно переопределить box-sizing, сбросить отступ у <body> и подключить шрифты, которые используются в макете.
* {
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/SegoeUISemiBold.woff2");
font-display: swap;
font-weight: 600;
}
@font-face {
font-family: "Segoe UI";
src: url("../fonts/SegoeUIBold.woff2");
font-display: swap;
font-weight: 700;
}
@font-face {
font-family: "PT Serif";
src: url("../fonts/PTSerifBold.woff2");
font-display: swap;
font-weight: 700;
}
body {
margin: 0;
background: #f5f3ee;
font-family: "Segoe UI", sans-serif;
}
.container {
margin: 0 auto;
padding: 0 15px;
max-width: 1150px;
}
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 24px;
}
.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;
}
Обратите внимание на подключение шрифтов. В макете используется два — PT Serif для заголовок, и Segoe UI как основной. Именно поэтому он указан у <body>. Также, посмотрите на стили сетки. Мы устанавливаем 12 колонок, как в макете.
И конечно, отдельное внимание на visually-hidden. Именно с помощью этих стилей мы полностью скрываем элемент, но он все еще остается доступен для скринридеров.
Идем дальше, и можно стилизовать шапку. Здесь нет ничего сложного, просто не забывайте, что заголовкам нам нужно вручную указывать font-family, т. к. у них свой шрифт.
.header__inner {
border-bottom: 2px solid #ddd5ca;
padding: 24px 0px;
}
.header__date {
margin-bottom: 8px;
font-weight: 400;
font-size: 16px;
line-height: 100%;
color: #6f6f6f;
}
.header__title {
margin: 0;
margin-bottom: 20px;
font-family: "PT Serif", sans-serif;
font-weight: 700;
font-size: 44px;
line-height: 100%;
text-transform: uppercase;
color: #222;
}
.nav__list {
margin: 0;
padding: 0;
list-style: none;
display: flex;
gap: 24px;
}
.nav__link {
font-weight: 600;
font-size: 16px;
line-height: 100%;
text-decoration: none;
color: #222;
}
Далее поработаем с главной новостью. С помощью align-items: flex-end мы прибьем контент карточки к нижней части, а с помощью grid-column: 8 span сделаем ширину у самой карточки, чтобы она равнялась 8 колонкам.
Изображение в данном случае сделано через <img>, а значит нам нужно как-то разместить его в фоне. Используем абсолютное позиционирование, inset, и растягиваем изображение на 100% ширины и высоты. Также стоит добавить object-fit. В остальном в стилях карточки мы все берем из макета.
.news {
padding-top: 36px;
padding-bottom: 24px;
}
.news__main {
position: relative;
display: flex;
align-items: flex-end;
border-radius: 16px;
padding: 28px;
min-height: 430px;
grid-column: 8 span;
}
.news__text {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.news__pic {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.news__category {
border-radius: 100px;
padding: 6px 12px;
font-weight: 400;
font-size: 12px;
line-height: 100%;
text-transform: uppercase;
text-decoration: none;
color: #fff;
background: rgba(255, 255, 255, 0.18);
}
.news__title {
margin: 0;
font-family: "PT Serif", sans-serif;
font-weight: 700;
font-size: 36px;
line-height: 120%;
color: #fff;
}
.news__link {
color: inherit;
text-decoration: none;
}
.news__descr {
margin: 0;
font-weight: 400;
font-size: 16px;
line-height: 120%;
color: #fff;
}
Далее у нас правая колонка. Задаем ей свой grid-column, чтобы управлять ее размером. Внутри все по стандарту, только посмотрите, как устроена обводка у карточки. Мы задаем ее снизу для всех карточек, а затем, используя .news__item:last-child .news-mini-card { мы находим последний элемент списка, а в нем карточку, и явно убираем обводку, чтобы привести все в соответствие с макетом.
.news__aside {
grid-column: 4 span;
border: 1px solid #ddd5ca;
border-radius: 16px;
padding: 20px;
background: #fff;
}
.news__subtitle {
margin: 0;
margin-bottom: 12px;
font-family: "PT Serif", sans-serif;
font-weight: 700;
font-size: 24px;
line-height: 100%;
color: #222;
}
.news__list {
margin: 0;
padding: 0;
list-style: none;
}
.news__item:last-child .news-mini-card {
border: none;
}
.news-mini-card {
border-bottom: 1px solid #ece7df;
padding: 12px 0px;
}
.news-mini-card__category {
margin-bottom: 4px;
font-weight: 700;
font-size: 12px;
line-height: 120%;
text-decoration: none;
color: #8d2d2d;
}
.news-mini-card__title {
margin: 0;
font-weight: 400;
font-size: 16px;
line-height: 120%;
color: #222;
}
.news-mini-card__link {
text-decoration: none;
color: inherit;
}
Ну и последнее, что нам нужно сделать — это список из одинаковых карточек. Здесь мы сбросим у списка все ненужные стили, а также растянем его на 100% ширины блока, используя grid-column: 12 span. Поскольку мы уже задавали класс grid списку, он уже «гридовый». Поэтому, нам стоит лишь дать понять браузеру, сколько какой элемент списка колонок должен занимать.
.news__items {
margin: 0;
padding: 0;
list-style: none;
grid-column: 12 span;
}
.news__item {
grid-column: 4 span;
}
.news__item--big {
grid-column: 8 span;
}
Остается простая карточка с изображением и текстом. Из интересного здесь — растягивание ссылки на всю карточку. Несмотря на то, что ссылка ограничена лишь свои размером, с помощью псевдоэлемента мы можем ее «растянуть». Для этого всей карточке задаем position: relative, а у ссылки делаем псевдоэлемент с position: absolute и inset: 0. Таким образом, псевдоэлемент растягивается именно относительно карточки.
.news-card {
position: relative;
overflow: hidden;
border: 1px solid #ddd5ca;
border-radius: 16px;
background: #fff;
}
.news-card__pic {
display: block;
max-width: 100%;
}
.news-card__text {
padding: 20px;
}
.news-card__title {
margin: 0;
margin-bottom: 8px;
font-family: "PT Serif", sans-serif;
font-weight: 700;
font-size: 20px;
line-height: 120%;
color: #222;
}
.news-card__link {
color: inherit;
text-decoration: none;
}
.news-card__link:after {
content: "";
position: absolute;
inset: 0;
}
.news-card__descr {
margin: 0;
min-height: 38px;
font-weight: 400;
font-size: 16px;
line-height: 120%;
color: #222;
}
В этом задании мы сверстали главную страницу новостного портала и познакомились с несколькими важными приемами построения сложных интерфейсов. Разобрали семантическую структуру страницы, построили композицию на CSS Grid, научились комбинировать Grid и Flexbox, оформили карточки новостей и реализовали боковую колонку с последними публикациями.