Пишешь .task { color: red; }, а пункт списка остаётся чёрным. Часто причина в том, что к нему подходит другое правило, и оно сильнее. Чтобы предсказывать, какое правило сработает, нужно понимать две вещи: как селектор находит элементы и как браузер выбирает между правилами.
Селектор — первая часть CSS-правила: он говорит, к каким элементам применить свойства. В правиле .tasks li { padding: 8px; } селектор .tasks li выбирает пункты списка внутри элемента с классом tasks. Селекторы бывают простыми — по тегу, классу, id и атрибуту, их можно соединять комбинаторами, а когда к элементу подходят два правила, побеждает более специфичное.
Пример: один список, девять селекторов
Открой пример в песочнице HTML/CSS ссылкой под вторым блоком: разметка попадёт во вкладку index.html, стили — в styles.css. Каждое правило в CSS выбирает элементы по-своему, ниже разберём все.
HTML — список дел с классами, id и ссылками:
<main class="board">
<h1 id="title">Планы на неделю</h1>
<ul class="tasks">
<li class="task done">Купить зерно</li>
<li class="task">Позвонить поставщику</li>
<li class="task urgent">Оплатить аренду</li>
<li class="task">Обновить меню</li>
</ul>
<p>Подробности — на <a href="https://example.com">сайте поставщика</a> и в <a href="/menu">меню</a>.</p>
</main>
CSS — каждое правило со своим селектором:
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
}
h1 {
font-size: 1.5rem;
}
.tasks {
padding: 0;
list-style: none;
}
.tasks li {
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #dddddd;
}
.tasks li:nth-child(even) {
background: #f4f6fa;
}
.task.urgent {
color: #c62828;
font-weight: 700;
}
.task.done::before {
content: "✓ ";
color: #2e7d32;
}
.task:not(.done):hover {
background: #fff3e0;
}
a[href^="https"] {
color: #6a1b9a;
}
Что получится: второй и четвёртый пункты на светлом фоне, «Оплатить аренду» красным жирным, перед «Купить зерно» зелёная галочка, а ссылка на внешний сайт — фиолетовая, тогда как ссылка на /menu осталась обычной синей. Наведи курсор на любой несделанный пункт: он подсветится.
Простые селекторы: тег, класс, id и атрибут
По тегу. h1, p, li выбирают все элементы с таким именем. Удобно для общих правил: шрифт body, отступы заголовков.
По классу. .tasks — точка и имя класса. Один класс можно дать сколько угодно элементам, а у элемента может быть несколько классов через пробел: class="task urgent". Классы — основной способ оформлять страницу.
По id. #title — решётка и id. Id на странице должен быть один, поэтому такой селектор выбирает один элемент. Для стилей id берут редко: он слишком сильный в споре правил, об этом ниже.
По атрибуту. В квадратных скобках: [type="email"] — поля с таким типом, [href^="https"] — ссылки, адрес которых начинается с https. Есть и другие варианты сравнения: $= — заканчивается на, *= — содержит.
Простые селекторы можно склеивать без пробела, тогда элемент должен подходить под все сразу. .task.urgent — элемент с обоими классами, li.task — пункт списка с классом task. А через запятую пишут группу: h1, h2 { … } даёт одни стили обоим.
Комбинаторы: потомок, ребёнок и соседи
Комбинатор описывает, как два элемента связаны в дереве страницы:
.tasks li { } /* li где угодно внутри .tasks */
.tasks > li { } /* li — прямой ребёнок .tasks */
h2 + p { } /* p сразу после h2 */
h2 ~ p { } /* все p после h2 у того же родителя */
Пробел — потомок. .tasks li найдёт пункт на любой глубине, даже во вложенном списке.
Знак > — ребёнок. .tasks > li возьмёт только пункты верхнего уровня, вложенные списки останутся без этих стилей.
Знаки + и ~ — соседи. Оба смотрят на элементы с тем же родителем: + выбирает следующего сразу за первым, ~ — всех следующих.
Пробел в селекторе — это тоже комбинатор, поэтому .task.urgent и .task .urgent выбирают разное. Первый — элемент с двумя классами, второй — элемент с классом urgent внутри элемента с классом task.
Псевдоклассы и псевдоэлементы
Псевдоклассы. Двоеточие и слово выбирают элемент по состоянию или месту. :hover — под курсором, :focus-visible — в фокусе с клавиатуры, :checked — отмеченный флажок, :first-child и :last-child — первый и последний среди соседей. :nth-child(even) берёт чётные, :nth-child(odd) — нечётные, а :nth-child(3n + 1) — первый, четвёртый, седьмой и так далее. :not(.done) выбирает всё, кроме подходящего под скобки.
Псевдоэлементы. Два двоеточия выбирают часть элемента, которой нет в HTML. ::before и ::after добавляют содержимое до и после текста — им обязательно нужно свойство content, иначе они не появятся. ::placeholder оформляет подсказку в поле ввода, ::selection — выделенный текст. Старая запись с одним двоеточием, :before, тоже работает.
Если хочется потренировать :nth-child, в игре «Повтори макет» есть уровень «Шахматы»: доску 4 × 4 там красят как раз через :nth-child.
Какое правило победит: специфичность и порядок
Наведи курсор на второй пункт примера. К нему подходят два правила с фоном: .tasks li:nth-child(even) и .task:not(.done):hover. Побеждает второе: браузер сравнивает специфичность — три числа, которые он считает для каждого селектора.
Специфичность — счёт из трёх чисел
#id сильнее любого числа классов, а один класс сильнее любого числа тегов. Атрибуты и псевдоклассы считаются как классы, псевдоэлементы — как теги.У .task:not(.done):hover счёт 0 · 3 · 0: класс .task, псевдокласс :hover и :not(), который считается по своему аргументу .done. Это больше, чем 0 · 2 · 1 у правила с чётными пунктами, поэтому подсветка видна и на светлом фоне.
Равный счёт — решает порядок. Из двух одинаково сильных правил применится то, что ниже в CSS.
Стиль в атрибуте сильнее селекторов. style="color: red" в HTML перебивает любое правило из styles.css.
!important — крайняя мера. color: red !important перебивает всё остальное, но потом его можно перебить только другим !important. Обычно хватает селектора посильнее.
Наследование слабее любого правила. Цвет, который пункт получил от родителя, уступает любому правилу, выбравшему сам пункт, даже *.
Шпаргалка селекторов CSS
| Селектор | Что выбирает | Специфичность |
|---|---|---|
* | все элементы | 0 · 0 · 0 |
p | все абзацы | 0 · 0 · 1 |
.card | элементы с классом card | 0 · 1 · 0 |
#title | элемент с id="title" | 1 · 0 · 0 |
[type="email"] | элементы с таким атрибутом | 0 · 1 · 0 |
[href^="https"] | атрибут начинается с https | 0 · 1 · 0 |
[href$=".pdf"] | атрибут заканчивается на .pdf | 0 · 1 · 0 |
.task.urgent | элемент с обоими классами | 0 · 2 · 0 |
h1, h2 | группа: и h1, и h2 | у каждого своя |
.tasks li | li где угодно внутри .tasks | 0 · 1 · 1 |
.tasks > li | li — прямой ребёнок .tasks | 0 · 1 · 1 |
h2 + p | p сразу после h2 | 0 · 0 · 2 |
h2 ~ p | все p после h2 у того же родителя | 0 · 0 · 2 |
a:hover | ссылка под курсором | 0 · 1 · 1 |
li:nth-child(odd) | нечётные пункты | 0 · 1 · 1 |
li:not(.done) | пункты без класса done | 0 · 1 · 1 |
p::first-line | первая строка абзаца | 0 · 0 · 2 |
.task::before | содержимое перед текстом | 0 · 1 · 1 |
Три правки, чтобы закрепить
Открой пример в песочнице и перед каждой правкой предскажи результат:
- Замени
:nth-child(even)на:nth-child(odd). Светлый фон перейдёт на первый и третий пункты. - Поставь пробел между классами:
.task .urgent. «Оплатить аренду» перестанет быть красной: теперь селектор ищет элемент с классомurgentвнутри.task, а внутри пунктов только текст. - Дай первой ссылке
id="supplier"и добавь правило#supplier { color: green; }. Ссылка станет зелёной, хотя правилоa[href^="https"]тоже к ней подходит: 1 · 0 · 0 больше, чем 0 · 1 · 1.
Селекторы выбирают элементы, а дальше нужно их расставить: Flexbox выстраивает элементы в строку, CSS Grid — в сетку. Если разметка пока непривычна, начни со статьи про теги HTML.