Работа со списками в CSS
Чтение — около 7 минут· без учёта практики
В предыдущем уроке мы выбирали
элементы по положению в списках: первый пункт, чётные пункты. Теперь оформим сами списки. В HTML два основных вида
списков (если не считать списки описаний <dl>):
У каждого пункта списка <li> есть маркер — кружок у маркированного списка или номер у
нумерованного. CSS позволяет менять вид маркера, его положение и оформление.
Тип маркера: list-style-type
Свойство
list-style-type
задаёт тип маркера. Меню навигации, например, часто составляют из маркированных списков. По умолчанию у них маркер в
форме закрашенного круга, и чтобы его убрать, используют значение none:
ul {
list-style-type: none; /* убираем маркер у списка */
}
Для маркированных списков предназначены три значения: закрашенный кружок disc (по умолчанию), полый
круг circle и квадрат square. Остальные значения — номера в разных системах счёта; по
умолчанию у <ol> значение decimal. Тип маркера не связан с тегом:
<ol> можно дать кружки, а <ul> — номера, но смысл списка задаёт HTML.
list-style-type: попробуйте сами
Первый список с маркером по умолчанию, у нумерованного — римские цифры lower-roman, у третьего — полые круги circle. Маркер получил цвет текста пункта: оранжевый и красный.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Тип маркера: list-style-type</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
.roman {
list-style-type: lower-roman; /* римские числа в нижнем регистре */
color: darkorange; /* маркер получает цвет текста элемента */
}
.circle {
list-style-type: circle; /* полый круг */
color: indianred;
}
</style>
</head>
<body>
<!-- маркер по умолчанию: disc -->
<ul>
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ul>
<ol class="roman">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
<ul class="circle">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ul>
</body>
</html>
В этом примере мы создали два стиля. Первый задаёт римские числа в нижнем регистре (lower-roman), его
мы применили к нумерованному списку. Второй задаёт маркер в форме полого круга (circle), его получил
маркированный список. Обратите внимание: маркер окрашен в цвет текста пункта. Как изменить цвет только маркера,
разберём ниже.
Значения list-style-type
| Значение | Описание |
|---|---|
none | Маркер не отображается. |
disc | Закрашенный кружок. Значение по умолчанию для <ul>. |
circle | Полый круг. |
square | Квадрат. |
decimal | Десятичные арабские числа, начиная с 1. Значение по умолчанию для <ol>. |
decimal-leading-zero | Десятичные числа с начальным нулём: 01, 02, 03… |
lower-roman | Римские числа в нижнем регистре: i, ii, iii, iv… |
upper-roman | Римские числа в верхнем регистре: I, II, III, IV… |
lower-alpha, lower-latin | Строчные латинские буквы: a, b, c… Два названия одного значения. |
upper-alpha, upper-latin | Заглавные латинские буквы: A, B, C… |
lower-greek | Строчные греческие буквы: α, β, γ, δ… |
cyrillic-lower, cyrillic-upper | Пользовательские стили кириллицы: а, б, в… или А, Б, В… Их нужно определить через @counter-style, как в примере ниже; это не встроенные имена. |
armenian | Традиционная армянская нумерация. |
georgian | Традиционная грузинская нумерация: an, ban, gan… |
hebrew | Традиционная еврейская нумерация. |
cjk-ideographic | Простые идеографические (китайские) числа. |
hiragana, hiragana-iroha | Японская слоговая азбука хирагана: a, i, u, e, o, ka, ki… или в порядке ироха: i, ro, ha, ni… |
katakana, katakana-iroha | Японская слоговая азбука катакана в обычном порядке или в порядке ироха. |
"строка" | Любой текст в кавычках, например "🐾 ": он и станет маркером. |
Значения list-style-type: посмотрите сами
Каждая карточка — список с одним значением list-style-type. Добавьте свою карточку со значением "🐾 " в кавычках.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Все значения list-style-type</title>
<style>
/* Пользовательские имена, а не встроенные значения list-style-type. */
@counter-style cyrillic-lower {
system: alphabetic;
symbols: а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я;
suffix: ". ";
}
@counter-style cyrillic-upper {
system: alphabetic;
symbols: А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я;
suffix: ". ";
}
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
.types { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.5rem; }
.types section { padding: 0.25rem 0.5rem; border-radius: 8px; background-color: #fffaf3; }
h2 { margin: 0; font-size: 0.9rem; font-family: monospace; }
ol { margin: 0; padding-left: 2.5rem; }
</style>
</head>
<body>
<div class="types">
<section>
<h2>none</h2>
<ol style="list-style-type: none">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>disc</h2>
<ol style="list-style-type: disc">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>circle</h2>
<ol style="list-style-type: circle">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>square</h2>
<ol style="list-style-type: square">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>decimal</h2>
<ol style="list-style-type: decimal">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>decimal-leading-zero</h2>
<ol style="list-style-type: decimal-leading-zero">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>lower-roman</h2>
<ol style="list-style-type: lower-roman">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>upper-roman</h2>
<ol style="list-style-type: upper-roman">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>lower-alpha</h2>
<ol style="list-style-type: lower-alpha">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>upper-alpha</h2>
<ol style="list-style-type: upper-alpha">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>lower-greek</h2>
<ol style="list-style-type: lower-greek">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>armenian</h2>
<ol style="list-style-type: armenian">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>georgian</h2>
<ol style="list-style-type: georgian">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>hebrew</h2>
<ol style="list-style-type: hebrew">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>cjk-ideographic</h2>
<ol style="list-style-type: cjk-ideographic">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>hiragana</h2>
<ol style="list-style-type: hiragana">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>hiragana-iroha</h2>
<ol style="list-style-type: hiragana-iroha">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>katakana</h2>
<ol style="list-style-type: katakana">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>katakana-iroha</h2>
<ol style="list-style-type: katakana-iroha">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>cyrillic-lower</h2>
<ol style="list-style-type: cyrillic-lower">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
<section>
<h2>cyrillic-upper</h2>
<ol style="list-style-type: cyrillic-upper">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ol>
</section>
</div>
</body>
</html>
Свойство можно задать и отдельному пункту <li>, и пунктам по правилу: например, чередовать
маркеры чётных и нечётных пунктов псевдоклассом :nth-child() из предыдущего урока.
Чередование маркеров: попробуйте сами
Нечётные пункты — зелёные с квадратным маркером, чётные — красные без маркера.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Чередование маркеров</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
li:nth-child(even) {
/* чётные пункты */
list-style-type: none;
color: firebrick;
}
li:nth-child(odd) {
/* нечётные пункты */
list-style-type: square;
color: seagreen;
}
</style>
</head>
<body>
<ul>
<li>Подъём</li>
<li>Прогулка</li>
<li>Завтрак</li>
<li>Сон</li>
<li>Обед</li>
<li>Игра с канатом</li>
<li>Ужин</li>
<li>Сон</li>
</ul>
</body>
</html>
Положение маркера: list-style-position
Свойство
list-style-position
определяет, где стоит маркер или номер. Значение outside (по умолчанию) ставит маркер слева за
границей пункта: длинный текст переносится ровной колонкой. Значение inside ставит маркер внутри пункта
вместе с текстом, как первую букву строки: перенесённые строки начинаются под маркером. Чтобы границы пунктов были
видны, в примере им задана рамка.
list-style-position: попробуйте сами
У маркированного списка outside: кружки стоят за оранжевой рамкой пунктов, а длинная строка переносится ровно. У нумерованного inside: номера внутри рамок, и вторая строка начинается под номером.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Положение маркера: list-style-position</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
li {
border: 1px solid orange; /* рамка показывает границы пункта */
}
.outside {
list-style-position: outside; /* маркер слева, за границей пункта (по умолчанию) */
background-color: khaki;
}
.inside {
list-style-position: inside; /* маркер внутри пункта вместе с текстом */
background-color: khaki;
}
</style>
</head>
<body>
<ul class="outside">
<li>Канат с мячиком «Удав» — моя любимая игрушка, и я готов играть с ним сколько угодно</li>
<li>Резиновая косточка</li>
</ul>
<ol class="inside">
<li>Канат с мячиком «Удав» — моя любимая игрушка, и я готов играть с ним сколько угодно</li>
<li>Резиновая косточка</li>
</ol>
</body>
</html>
Маркеры с outside помещаются в левом внутреннем отступе списка: браузер по умолчанию задаёт
<ul> и <ol> padding-left: 40px. Если обнулить этот отступ, маркеры
уйдут за край родителя. Поэтому, убирая отступ списка, проверяйте, остались ли маркеры видны.
Изображение вместо маркера: list-style-image
Свойство
list-style-image
ставит вместо маркера изображение. В скобках url() — путь к файлу, абсолютный или относительный.
Во внешней таблице относительный путь указывают от CSS-файла. Внутри style он отсчитывается от базового адреса HTML-документа — в наших примерах от папки страницы.
ol {
list-style-image: url("images/primer.png"); /* относительный путь */
}
ul {
list-style-image: url("https://basicweb.ru/images/mini5.png"); /* абсолютный путь */
}
list-style-image: попробуйте сами
Вместо кружков — лапки из файла paw.svg. Испортите путь в url() — браузер покажет обычный маркер.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Изображение вместо маркера: list-style-image</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
.paws {
list-style-image: url("assets/paw.svg"); /* путь — от файла со стилями */
}
</style>
</head>
<body>
<ul class="paws">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ul>
</body>
</html>
Изображение показывается в своём размере: list-style-image не умеет его менять. Поэтому для маркеров
берут небольшие картинки с прозрачным фоном или SVG. Если изображение не загрузится, браузер покажет маркер из
list-style-type, — задавайте его как запасной вариант. Когда нужна картинка другого размера, её чаще
ставят фоном пункта (background-image с отступом слева), а маркер убирают.
Псевдоэлемент ::marker
Маркер — отдельная часть пункта, и у неё есть свой псевдоэлемент
::marker. Через него маркер оформляют, не трогая текст пункта: меняют цвет, размер и начертание, а свойством
content заменяют маркер своим текстом.
::marker: попробуйте сами
Номера команд коричневые и жирные, а текст пунктов остался обычного цвета. У списка игрушек маркер заменён косточкой через content. Попробуйте задать маркеру font-size: 1.5rem.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Псевдоэлемент ::marker</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
.commands li::marker {
/* только маркер: текст пункта остаётся прежнего цвета */
color: saddlebrown;
font-weight: bold;
}
.toys li::marker {
content: "🦴 "; /* свой маркер — строка текста */
}
</style>
</head>
<body>
<ol class="commands">
<li>Sitz — сидеть</li>
<li>Platz — лежать</li>
<li>Fuß — рядом</li>
</ol>
<ul class="toys">
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ul>
</body>
</html>
В ::marker действуют не все свойства: цвет, шрифт (font-size, font-weight и
другие), content, анимации и переходы. Отступы, фон и рамки маркеру задать нельзя.
Старый приём: маркер через ::before
До появления ::marker цвет маркера меняли обходным путём. У списка убирали маркеры, а перед каждым
пунктом псевдоэлементом ::before вставляли символ, похожий на маркер, нужного цвета. Внутренний отступ
справа отодвигал символ от текста. Такой код часто встречается, поэтому его стоит узнавать:
Маркер через ::before: попробуйте сами
Маркеры убраны, перед каждым пунктом — оранжевая точка из ::before. Удалите padding-right — точка прижмётся к тексту.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Старый приём: маркер через ::before</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
ul {
list-style: none; /* убираем маркеры */
}
li::before {
content: "•"; /* символ, похожий на маркер */
padding-right: 10px; /* отступ от текста */
color: orange;
}
</style>
</head>
<body>
<ul>
<li>Мячик</li>
<li>Канат</li>
<li>Косточка</li>
</ul>
</body>
</html>
У этого приёма есть недостатки: символ стоит в одной строке с текстом, и длинный текст переносится под маркер, как
при inside. В новом коде оформляйте маркер через ::marker. Отступы мы разбирали в уроке
«Блочная и строчная модель».
Сокращённое свойство list-style
Свойство
list-style
задаёт тип, положение и изображение маркера одной записью в любом порядке:
ul {
list-style: square inside; /* квадраты внутри пунктов */
}
.paws {
list-style: url("../assets/paw.svg") disc; /* лапка, а если она не загрузится — кружок */
}
nav ul {
list-style: none; /* меню без маркеров */
}
Счётчики CSS
Для обычной нумерации достаточно ol. Если нужно оформить номер как «Шаг 1», можно использовать именованный счётчик: counter-reset: step создаёт его со значением 0 на списке, counter-increment: step увеличивает на 1 у каждого пункта, а counter(step) выводит значение в content. Имя step мы придумали сами. Новый список создаёт свой счётчик, поэтому начинается снова с первого шага.
Счётчик меняет оформление, но не делает произвольные блоки списком. Сохраняйте ol и li; важные указания пишите в HTML, а не только в сгенерированном номере.
Счётчики: попробуйте сами
Каждый список начинает счёт заново. Измените counter-reset: step на counter-reset: step 4 — первый номер станет 5. Добавьте пункт: номер появится автоматически.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Шаги прогулки с Тони</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Перед прогулкой</h1>
<ol class="steps"><li>Надеть шлейку.</li><li>Взять воду.</li><li>Позвать Тони.</li></ol>
<h2>После прогулки</h2>
<ol class="steps"><li>Вымыть лапы.</li><li>Налить свежую воду.</li></ol>
</body>
</html>
style.css
body {
font-family: system-ui, sans-serif;
color: #2b211b;
background-color: linen;
line-height: 1.6;
}
.steps {
counter-reset: step;
padding-inline-start: 5rem;
}
.steps > li {
counter-increment: step;
}
.steps > li::marker {
content: "Шаг " counter(step) ". ";
color: saddlebrown;
font-weight: bold;
}
Проверьте себя
Выберите ответ — объяснение появится сразу. Ответ можно менять.
Задание
Ожидаемый результат: на странице «Мои игрушки» перед каждой игрушкой стоит косточка, а номера шагов «Как играть со мной в канат» — коричневые и жирные, текст шагов прежнего цвета. Меню и фотоальбом без маркеров, как раньше.
Выполните задание
Условие задания
css/style.css
/* Общие стили сайта Тони */
/* Ширина и высота включают внутренние отступы и рамку — у всех элементов и псевдоэлементов */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Шрифт заголовков Nunito (лицензия SIL Open Font License): кириллица и латиница в разных файлах */
@font-face {
font-family: "Nunito";
src: url("../assets/nunito-cyrillic-800-normal.woff2") format("woff2");
font-weight: 800;
font-display: swap; /* пока шрифт загружается, текст виден запасным шрифтом */
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Nunito";
src: url("../assets/nunito-latin-800-normal.woff2") format("woff2");
font-weight: 800;
font-display: swap;
unicode-range: U+0000-00FF, U+2000-206F, U+20AC, U+2122;
}
body {
background-color: linen;
color: #2b211b; /* цвет для потомков без собственного значения */
font-size: 1.0625rem; /* 17px при настройке браузера по умолчанию */
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; /* системный шрифт устройства */
line-height: 1.6; /* множитель: у каждого элемента интервал от его собственного размера шрифта */
margin: 0; /* убираем отступ браузера: шапка и подвал во всю ширину окна */
}
/* Поля основного содержимого: текст не прижимается к краям окна */
main {
padding: 1.5rem 1rem;
max-width: 68rem; /* на широком экране строки не растягиваются на всё окно */
margin: 0 auto; /* колонка по центру */
}
/* Изображения и видео не шире колонки, пропорции сохраняются */
img,
video {
max-width: 100%;
height: auto;
}
/* Фреймы, аудиоплеер, индикаторы и поля формы не выходят за колонку */
progress,
meter,
iframe,
audio,
input,
select,
textarea,
button {
max-width: 100%;
}
/* Разрешаем группе полей сужаться вместе с колонкой. */
fieldset {
min-width: 0;
}
/* Общий цвет заголовков — групповой селектор */
h1,
h2 {
color: saddlebrown;
font-family: "Nunito", "Trebuchet MS", system-ui, sans-serif;
font-weight: 800;
line-height: 1.2; /* крупному тексту хватает меньшего интервала */
}
/* Размеры заголовков в rem — от размера шрифта корня, а не родителя */
h1 {
text-align: center;
font-size: 2.5rem;
}
h2 {
font-size: 1.75rem;
}
/* Вводный блок главной страницы */
.intro {
background-color: hsl(30, 80%, 95%);
padding: 1.5rem; /* фон вокруг текста; заодно не выпускает margin заголовка за пределы блока */
border-radius: 16px; /* скругляется и фон, хотя рамки нет */
/* Лапка в правом нижнем углу; цвет фона выше остаётся запасным, если картинка не загрузится */
background-image: url("../assets/paw.svg");
background-repeat: no-repeat;
background-position: right 1rem bottom 1rem;
background-size: 4rem;
}
/* Разделы главной — светлые карточки на фоне страницы */
main > section {
background-color: #fffaf3;
border: 1px solid hsl(28, 30%, 82%);
border-radius: 16px;
padding: 1.5rem;
margin-top: 1.5rem;
}
/* Ширина вводных абзацев ограничена 60ch; количество букв в строке зависит от шрифта и текста */
.intro p {
max-width: 60ch;
}
/* Важное предупреждение во вводном блоке — темнее основного акцента */
.intro strong {
color: #6e3510;
}
/* Оглавление главной страницы — переносимый ряд «таблеток» на Flexbox */
.toc {
background-color: white;
border: 1px solid tan;
display: flex;
flex-wrap: wrap; /* «таблетки», которым не хватает места, переходят на новую строку */
gap: 0.5rem; /* промежутки между «таблетками» и между строками */
list-style: none;
padding: 0.75rem;
}
/* Подчёркивание ссылок чуть ниже букв: не задевает выносные элементы */
a {
text-underline-offset: 3px;
}
/* Тонкая линия отделяет шапку от страницы */
header {
border-bottom: 1px solid tan;
}
/* Меню на Flexbox: пункты могут переноситься, промежутки задаёт gap */
nav ul {
margin: 0;
padding: 0.5rem 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
}
/* Ссылка на запись стоит отдельно у правого края: автоматический отступ забирает свободное место */
nav li:last-child {
margin-left: auto;
}
/* Ссылки меню в шапке — селектор потомков */
nav a {
color: saddlebrown;
text-decoration: none;
display: inline-block; /* у строчной ссылки вертикальный padding не раздвигает строку */
padding: 0.4rem 0.9rem;
border-radius: 999px; /* «таблетка»: радиус больше половины высоты */
}
/* Ссылки оглавления главной страницы */
.toc a {
color: #6b5d52; /* тёплый серый: контраст 6.34:1 на белом фоне оглавления */
display: block; /* ссылка заполняет «таблетку» целиком */
padding: 0.25rem 0.75rem;
border: 1px solid tan;
border-radius: 999px;
text-decoration: none;
}
/* Фотографии со скруглёнными углами */
figure img {
border-radius: 8px;
}
figcaption {
color: #6b5d52; /* тёплый серый: контраст 5.64:1 на фоне linen */
font-size: 0.95rem;
}
footer {
background-color: hsl(30, 50%, 90%);
text-align: center;
padding: 1rem;
}
/* Ссылка в подвале берёт цвет текста подвала */
footer a {
color: inherit;
}
/* Поля формы получают шрифт страницы вместо шрифта браузера */
input,
select,
textarea,
button {
font: inherit;
}
/* Ссылки темнеют при наведении указателя */
a:hover {
color: #5c2d0c;
}
/* Ссылки меню подчёркиваются при наведении и фокусе с клавиатуры */
nav a:hover,
nav a:focus-visible {
text-decoration: underline;
}
/* Заметная рамка фокуса у ссылок, полей и кнопок при работе с клавиатуры */
:focus-visible {
outline: 3px solid #1f6feb;
outline-offset: 3px;
}
/* Цитата из книги — шрифтом с засечками и курсивом */
blockquote {
font-family: Georgia, "Times New Roman", serif;
font-style: italic;
border-left: 4px solid tan; /* черта слева выделяет цитату */
padding-left: 1rem;
}
/* Подпись под цитатой — у правого края */
blockquote + figcaption {
text-align: right;
}
/* Строки формы на Flexbox: подпись — колонка постоянной ширины, поле занимает остаток строки,
а на узком экране переносится под подпись */
fieldset p {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 1rem;
}
fieldset p label {
flex: 0 1 12rem; /* не растёт, но может сжаться в узкой строке */
min-width: 0;
}
fieldset p input,
fieldset p select,
fieldset p textarea {
flex: 1 1 12rem; /* растёт и сжимается от исходной ширины 12rem */
min-width: 0; /* поле может стать уже своего автоматического минимума */
}
/* Фотоальбом на Grid: столько колонок, сколько помещается карточек шириной не меньше 15rem */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
gap: 1rem;
list-style: none;
padding: 0;
}
.gallery figure {
margin: 0;
}
.gallery img {
width: 100%; /* фото заполняет ширину своей ячейки */
}
/* Список команд: первая колонка подстраивается под текст, оставляя место для перевода. */
dl {
display: grid;
grid-template-columns: fit-content(calc(50% - 0.5rem)) minmax(0, 1fr);
gap: 0.5rem 1rem;
}
dt {
overflow-wrap: anywhere;
grid-column: 1;
font-weight: bold;
}
dd {
grid-column: 2;
overflow-wrap: anywhere; /* длинный перевод переносится внутри своей колонки */
margin: 0; /* браузерный отступ слева больше не нужен: колонку задаёт сетка */
}
/* Длинные слова и адреса переносятся, а не выходят за край узкого экрана */
body {
overflow-wrap: break-word;
}
/* Таблица режима дня на узком экране прокручивается в своей рамке, а не сжимается до нечитаемых колонок */
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
min-width: 28rem;
}
/* Подпись таблицы прокручивается вместе с ней: прижимаем её к началу, чтобы она была видна сразу */
caption {
text-align: left;
}
/* Широкий экран: в разделе «Моя любимая игрушка» фото справа, текст слева.
На узком экране правило не действует, и раздел остаётся одной колонкой. */
@media (width >= 48rem) {
.toy {
display: grid;
grid-template-columns: minmax(0, 1fr) 300px;
column-gap: 1.5rem;
}
.toy figure {
grid-column: 2;
grid-row: 1 / span 6; /* фото рядом со всеми абзацами раздела */
margin: 0;
}
.toy hr {
width: 100%;
}
}
/* Карточка альбома — точка отсчёта для значка: position: relative не сдвигает её и не меняет поток */
.gallery li {
position: relative;
}
/* Значок «Любимая» в правом верхнем углу карточки; из потока исключён, поэтому фото не сдвигается */
.badge {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.2rem 0.7rem;
border-radius: 999px;
background-color: saddlebrown;
color: white;
font-size: 0.85rem;
font-weight: bold;
}
/* Шапка прилипает к верхнему краю окна. Фон нужен, чтобы под шапкой не просвечивал текст страницы */
header {
position: sticky;
top: 0;
z-index: 10; /* выше карточек альбома: они тоже позиционированы и записаны в HTML позже шапки */
background-color: linen;
}
/* Липкая шапка должна быть низкой: пункты меню не сжимаются и не переносятся на вторую строку,
а если им не хватает места, меню прокручивается внутри шапки */
nav ul {
flex-wrap: nowrap;
overflow-x: auto;
}
nav li {
flex-shrink: 0;
}
/* Заголовок, к которому ведёт ссылка-якорь, не прячется под липкой шапкой.
В rem: при увеличении текста растут и шапка, и отступ */
[id] {
scroll-margin-top: 4.5rem;
}
/* Ссылка, которая открывается в новой вкладке, помечена стрелкой — посетитель знает, что страница не сменится */
a[target="_blank"]::after {
content: " ↗";
}
/* Ссылка на электронную почту — с конвертом */
a[href^="mailto:"]::before {
content: "✉ ";
}
/* Карточка альбома, в которой есть значок, выделена рамкой — без отдельного класса в HTML */
.gallery li:has(.badge) {
outline: 3px solid tan;
outline-offset: 4px;
border-radius: 8px;
}
/* Первая ссылка меню — «Главная» — подписана лапкой без изменения HTML */
nav li:first-child a::before {
content: "🐾 ";
}
/* Первый абзац вводного блока крупнее остальных. :first-child здесь не подойдёт — первым стоит заголовок h1 */
.intro p:first-of-type {
font-size: 1.2rem;
}
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Сайт карликового пинчера Тони: игрушки, режим дня и запись на прогулку.">
<title>Сайт Тони</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<!-- Меню сайта -->
<nav>
<ul>
<li><a href="index.html">Главная</a></li>
<li><a href="pages/toys.html">Мои игрушки</a></li>
<li><a href="pages/signup.html">Запись на прогулку</a></li>
</ul>
</nav>
</header>
<main>
<div class="intro">
<h1 id="top">Привет! Меня зовут Тони</h1>
<p>Я карликовый пинчер. Мне три года, я вешу 8 кг и люблю гулять в любую погоду. Хозяин говорит: <q>Тони — лучший сторож в доме</q>.</p>
<p>Мой день рождения — <time datetime="2023-09-22">22 сентября</time>. <strong>Пожалуйста, не кормите меня со стола!</strong></p>
<p>Моя родословная записана в Российской кинологической федерации (<abbr title="Российская кинологическая федерация">РКФ</abbr>).</p>
</div>
<ul class="toc">
<li><a href="#toy">Моя любимая игрушка</a></li>
<li><a href="#commands">Команды, которые я знаю</a></li>
<li><a href="#schedule">Мой режим дня</a></li>
<li><a href="#walks">Где я гуляю</a></li>
<li><a href="#song">Песня для прогулок</a></li>
</ul>
<section class="toy">
<h2 id="toy">Моя любимая игрушка</h2>
<p data-toy-id="17">Это канат с мячиком. Хозяин бросает его, а я приношу обратно — <em>почти</em> всегда. <a href="pages/toys.html">Посмотреть все игрушки Тони</a>.</p>
<figure>
<img src="assets/tony-toy.png" alt="Тони сидит и держит в зубах канат с красным мячиком" width="300" height="400" title="Мой канат "Удав"">
<figcaption>Я и мой канат «Удав»</figcaption>
</figure>
<p><a href="assets/tony-toy.png" download>Скачать фотографию</a></p>
<p>Подробнее о моей породе — в статье <a href="https://ru.wikipedia.org/wiki/Карликовый_пинчер" target="_blank">«Карликовый пинчер» в Википедии</a>.</p>
<hr>
<p>Вечером игра закончилась. Канат остался на полу, а я свернулся калачиком в кресле.</p>
</section>
<section>
<h2>Мой стишок</h2>
<p>
Я маленький, но смелый,<br>
Гуляю целый день,<br>
А вечером на кресле<br>
Сплю — и мне не лень.
</p>
</section>
<section>
<h2 id="commands">Команды, которые я знаю</h2>
<p>Хозяин научил меня командам на немецком:</p>
<dl>
<dt lang="de">Sitz!</dt>
<dd>сидеть</dd>
<dt lang="de">Platz!</dt>
<dd>лежать</dd>
<dt lang="de">Fuß!</dt>
<dd>рядом</dd>
</dl>
<details>
<summary>Почему команды на немецком?</summary>
<p>Карликовый пинчер — немецкая порода, и хозяину понравилась эта традиция.</p>
</details>
</section>
<section>
<h2 id="schedule">Мой режим дня</h2>
<div class="table-wrap" tabindex="0" role="region" aria-label="Режим дня Тони">
<table>
<caption>Режим дня Тони</caption>
<thead>
<tr>
<th scope="col">Время</th>
<th scope="col">Будни</th>
<th scope="col">Выходные</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">07:00</th>
<td>Прогулка, 20 минут</td>
<td>Сплю</td>
</tr>
<tr>
<th scope="row">08:00</th>
<td colspan="2">Завтрак</td>
</tr>
<tr>
<th scope="row">13:00</th>
<td>Жду хозяина и сплю</td>
<td>Прогулка в парке, 1 час</td>
</tr>
<tr>
<th scope="row">19:00</th>
<td colspan="2">Ужин</td>
</tr>
<tr>
<th scope="row">21:00</th>
<td colspan="2">Вечерняя прогулка</td>
</tr>
</tbody>
</table>
</div>
</section>
<section>
<h2 id="progress">Мои успехи</h2>
<p>Учу команду «Апорт»: пройдено 3 занятия из 5. <progress value="3" max="5">3 из 5</progress></p>
<p>Прогулка сегодня: 40 минут. <meter value="40" min="0" max="90" low="30" high="60" optimum="75">40 из 90 минут</meter></p>
</section>
<section>
<h2 id="walks">Где я гуляю</h2>
<p>Я люблю гулять в сквере у дома.</p>
<p>
<a href="pages/walks.html" target="walk">Маршрут</a> |
<a href="assets/tony.png" target="walk">Фото с прогулки</a>
</p>
<iframe src="pages/walks.html" name="walk" title="Маршрут и фото прогулки Тони" width="320" height="420"></iframe>
<h3>Видео с прогулки</h3>
<p>Субтитры помогут, если вы смотрите видео без звука.</p>
<video width="180" height="320" poster="assets/walk.jpg" controls>
<source src="assets/walk.mp4" type="video/mp4">
<track src="assets/ru.vtt" kind="subtitles" srclang="ru" label="Русский" default>
<p><a href="assets/walk.mp4">Скачать видео</a></p>
</video>
<h3>Как я лаю</h3>
<p>Так я разговариваю с хозяином — у нас целый диалог!</p>
<audio controls>
<source src="assets/bark.mp3" type="audio/mpeg">
<p><a href="assets/bark.mp3">Скачать запись</a></p>
</audio>
</section>
<section>
<h2 id="song">Песня для прогулок</h2>
<p>Хозяин включает эту песню, когда мы выходим гулять.</p>
<iframe width="480" height="270" src="https://www.youtube.com/embed/ojULkWEUsPs?start=17" title="Baha Men — Who Let the Dogs Out" loading="lazy" allow="fullscreen"></iframe>
<p><a href="https://www.youtube.com/watch?v=ojULkWEUsPs">Baha Men — Who Let the Dogs Out на YouTube</a></p>
</section>
<section>
<h2>Моя любимая книга</h2>
<p>Хозяин читал мне вслух рассказ <cite>«Каштанка»</cite> Антона Чехова. Он начинается так:</p>
<figure>
<blockquote>
<p>Молодая рыжая собака — помесь таксы с дворняжкой — очень похожая мордой на лисицу, бегала взад и вперёд по тротуару и беспокойно оглядывалась по сторонам.</p>
</blockquote>
<figcaption>— Антон Чехов, <cite>«Каштанка»</cite></figcaption>
</figure>
</section>
<p><a href="#top">Наверх</a></p>
</main>
<footer>
<p><small>© 2026 Тони и его хозяин</small></p>
<address>Написать хозяину Тони: <a href="mailto:tony@example.com">tony@example.com</a></address>
</footer>
</body>
</html>
pages/signup.html
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Запишитесь на совместную прогулку с карликовым пинчером Тони.">
<title>Запись на прогулку с Тони</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<header>
<nav>
<ul>
<li><a href="../index.html">Главная</a></li>
<li><a href="toys.html">Мои игрушки</a></li>
<li><a href="signup.html">Запись на прогулку</a></li>
</ul>
</nav>
</header>
<main>
<h1>Запись на прогулку</h1>
<p>Гуляйте вместе с Тони! Оставьте заявку, и хозяин Тони напишет вам, чтобы договориться о встрече.</p>
<p><a href="../index.html#walks">Где мы гуляем</a></p>
<form>
<fieldset>
<legend>О вас</legend>
<p>
<label for="owner">Ваше имя</label>
<input type="text" id="owner" name="owner" autocomplete="name" required>
</p>
<p>
<label for="email">Электронная почта</label>
<input type="email" id="email" name="email" autocomplete="email" placeholder="anna@example.com" required>
</p>
</fieldset>
<fieldset>
<legend>Ваша собака</legend>
<p>
<label for="breed">Порода</label>
<input type="text" id="breed" name="breed" list="breeds">
<datalist id="breeds">
<option value="Карликовый пинчер"></option>
<option value="Такса"></option>
<option value="Йоркширский терьер"></option>
<option value="Беспородная"></option>
</datalist>
</p>
<p>
<label for="dogs">Сколько собак придёт</label>
<input type="number" id="dogs" name="dogs" min="1" max="3" value="1">
</p>
</fieldset>
<fieldset>
<legend>Когда гулять</legend>
<p>
<label for="date">Дата</label>
<input type="date" id="date" name="date" required>
</p>
<label><input type="radio" name="time" value="morning" checked> Утром</label>
<label><input type="radio" name="time" value="evening"> Вечером</label>
</fieldset>
<fieldset>
<legend>Прогулка</legend>
<p>
<label for="leader">Ведёт прогулку</label>
<input type="text" id="leader" name="leader" value="Тони" readonly>
</p>
<p>
<label for="route">Маршрут</label>
<select id="route" name="route">
<optgroup label="В городе">
<option value="square">Сквер у дома</option>
<option value="pond" selected>Пруд с утками</option>
</optgroup>
<optgroup label="За городом">
<option value="forest">Лес</option>
<option value="river" disabled>Берег реки — закрыт до весны</option>
</optgroup>
</select>
</p>
<p>
<label for="wishes">Пожелания</label>
<textarea id="wishes" name="wishes" rows="4" cols="40" maxlength="300" placeholder="Например: моя собака боится велосипедов"></textarea>
</p>
</fieldset>
<fieldset>
<legend>Что взять с собой</legend>
<label><input type="checkbox" name="take" value="ball"> Мячик</label>
<label><input type="checkbox" name="take" value="rope"> Канат</label>
</fieldset>
<p>
<button type="submit">Записаться на прогулку</button>
<button type="reset">Очистить форму</button>
</p>
</form>
</main>
<footer>
<p><small>© 2026 Тони и его хозяин</small></p>
<address>Написать хозяину Тони: <a href="mailto:tony@example.com">tony@example.com</a></address>
</footer>
</body>
</html>
pages/toys.html
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Любимые игрушки Тони и правила игры в канат.">
<title>Игрушки Тони</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<header>
<nav>
<ul>
<li><a href="../index.html">Главная</a></li>
<li><a href="toys.html">Мои игрушки</a></li>
<li><a href="signup.html">Запись на прогулку</a></li>
</ul>
</nav>
</header>
<main>
<h1>Мои игрушки</h1>
<ul>
<li>Канат с мячиком «Удав» — моя любимая игрушка</li>
<li>Резиновая косточка — для вечеров, когда гулять уже поздно</li>
<li>Пищащий ёжик — его я прячу <del>под диваном</del> <ins>в своей лежанке</ins></li>
</ul>
<img src="../assets/tony.png" alt="Тони сидит и смотрит в камеру" width="300" height="400">
<h2>Как играть со мной в канат</h2>
<ol>
<li>Покажите мне канат.</li>
<li>Бросьте его подальше.</li>
<li>Подождите, пока я принесу его обратно.</li>
<li>Похвалите меня — и всё сначала!</li>
</ol>
<section>
<h2 id="album">Мой фотоальбом</h2>
<ul class="gallery">
<li>
<figure>
<img src="../assets/album-puppy.png" alt="Тони-щенок сидит и смотрит в камеру" width="300" height="400">
<figcaption>Я в детстве</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-squirrel.png" alt="Тони лежит в обнимку с плюшевой белкой" width="300" height="400">
<figcaption>С плюшевой белкой</figcaption>
</figure>
<span class="badge">Любимая</span>
</li>
<li>
<figure>
<img src="../assets/album-hoodie.png" alt="Тони стоит в бежевой толстовке с капюшоном" width="300" height="400">
<figcaption>В худи для холодной погоды</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-harness.png" alt="Тони в оранжевой шлейке ждёт прогулку" width="300" height="400">
<figcaption>Шлейка надета — идём гулять!</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-laugh.png" alt="Тони лежит, запрокинув голову, и широко открыл пасть, будто смеётся" width="300" height="400">
<figcaption>Смеюсь над шуткой хозяина</figcaption>
</figure>
</li>
<li>
<figure>
<img src="../assets/album-sleep.png" alt="Тони спит, положив голову на лапы" width="300" height="319">
<figcaption>После прогулки</figcaption>
</figure>
</li>
</ul>
</section>
<p><a href="../index.html#commands">Какие команды я знаю</a></p>
<p><a href="../index.html">Вернуться на главную</a></p>
</main>
<footer>
<p><small>© 2026 Тони и его хозяин</small></p>
<address>Написать хозяину Тони: <a href="mailto:tony@example.com">tony@example.com</a></address>
</footer>
</body>
</html>
pages/walks.html
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Маршрут прогулки Тони: сквер, пруд и поляна для игры с канатом.">
<title>Где гуляет Тони</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<h1>Мой маршрут</h1>
<ol>
<li>Сквер у дома.</li>
<li>Пруд с утками.</li>
<li>Поляна для игры с канатом.</li>
</ol>
</body>
</html>
Продолжите в VS Code
Внесите те же изменения в pages/toys.html и css/style.css своего сайта
my-site и откройте страницу «Мои игрушки». Проверка: на вкладке Elements у пункта списка виден
псевдоэлемент ::marker, а в Styles — ваше правило. Если результат отличается, нажмите в редакторе
«Показать решение» и сравните его со своим кодом.
Дополнительные задачи
1. Оформите три списка: первый — полые круги и красный текст, второй (нумерованный) — строчные латинские буквы и зелёный текст, третий — квадраты и синий текст. Классы уже есть в HTML.
Ожидаемый результат: красные пункты с ○, зелёные с a., b., c. и синие с ■.
Дополнительное задание: цветные маркеры списков
Воспроизведите цвета и маркеры трёх списков. Замените ??? в CSS.
Условие задания
Подсказка
list-style-type задаёт вид маркера: circle — окружность, square — квадрат, lower-alpha — латинские буквы. Цвет задаётся свойством color.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Дополнительная задача</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
ul {
list-style-type: ???;
color: ???;
}
ol {
list-style-type: ???;
color: ???;
}
.last {
list-style-type: ???;
color: ???;
}
</style>
</head>
<body>
<ul>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
</ul>
<ol>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
</ol>
<ul class="last">
<li>Элемент списка.</li>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
</ul>
</body>
</html>
2. Продвинутое задание: первому списку поставьте вместо маркеров смайлик из файла
assets/smile.png, а второму — зелёные треугольники ▶, текст пунктов оставьте чёрным.
Ожидаемый результат: перед пунктами первого списка — жёлтые смайлики, второго — зелёные треугольники, текст всех пунктов чёрный.
Дополнительное задание: списки с рисунками и треугольниками
Настройте маркеры двух списков и отступы по образцу. Замените ??? в CSS.
Условие задания
Подсказка
Для смайлика задайте list-style-image: url("assets/smile.png"). Треугольник задайте строкой в content у ::marker, а его цвет — свойством color. Сравните отступ маркера от текста.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Дополнительная задача</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
.smile {
list-style-image: ???;
}
.arrow li::marker {
content: ???;
color: ???;
}
</style>
</head>
<body>
<ul class="smile">
<li>Элемент списка.</li>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
</ul>
<ul class="arrow">
<li>Элемент списка.</li>
<li>Элемент списка.</li>
<li>Элемент списка.</li>
</ul>
</body>
</html>
История
Поддержка значений list-style-type в старых браузерах
Internet Explorer не поддерживал значения hebrew, cjk-ideographic,
hiragana, hiragana-iroha, katakana и katakana-iroha. Сегодня
они работают во всех современных браузерах. Псевдоэлемент ::marker поддерживается браузерами с
2020–2021 годов, а до этого цвет маркера меняли приёмом с ::before.