Webrium
Подписаться

Стиль не применяется: кто победил в каскаде

Как диагностировать и исправлять проблемы с неприменяемыми стилями в CSS пошагово и без ошибок

Из нескольких CSS-правил применяется только одно, остальные отфильтрованы каскадомПравила CSSПрименено

Смотрим не исходник, а вычисленные стили

Первое, что нужно сделать, когда стиль не применяется, — открыть инспектор и перейти на вкладку «Вычисленные». Там перечислены все свойства элемента в алфавитном порядке, а не в том, в каком они написаны в коде. В этом списке четыре возможных состояния для каждого объявления:

  1. Объявления нет вовсе — правило не попало в каскад.
  2. Объявление есть, но написанного значения не дало — отброшено при разборе или позже, на вычислении значения, уже после каскада.
  3. Объявление есть, зачёркнуто и без предупреждения — проиграло в каскаде.
  4. Объявление есть и победило, но на элементе ничего не изменилось — дело уже не в том, какое объявление выиграло. Причин здесь несколько, и признак в инспекторе у каждой свой.

Каждый из этих исходов требует своего разбора.

Правила нет в списке

Если объявления нет в вычисленных стилях, значит, правило не попало в каскад. Причин может быть несколько:

Селектор не совпал

Опечатка в имени класса, неверное состояние элемента (например, :hover вместо :active), не тот узел в дереве (потомок вместо дочернего элемента). Проверяйте не только сам селектор, но и его окружение: если элемент находится внутри теневого дерева, авторские стили страницы до него не доходят.

Условие не выполнено

Медиа-запрос (@media), контейнерный запрос (@container) или @supports не сработали. Например, правило внутри @media (min-width: 1000px) не применится, если ширина экрана меньше. Проверяйте не только синтаксис, но и текущие условия.

Негодный селектор в списке

Один недопустимый селектор в списке через запятую отменяет всё правило целиком. Например:

.button, .link:hover, ::unknown-pseudo {
  color: red;
}

Здесь ::unknown-pseudo — неизвестный псевдоэлемент, разобрать его нельзя. Из-за него стили не получат ни .button, ни .link:hover, хотя эти два селектора написаны верно. Несовпадение — не то же самое: селектор, под который в документе не нашлось ни одного элемента, остаётся допустимым и остальную часть списка не трогает. Внутри :is() и :where() список прощающий — негодные селекторы отбрасываются поштучно, но :has() прощающим не является.

Импорт ниже правил

Правило @import действует только в начале таблицы стилей: до него допустимы объявление кодировки, объявления слоёв и другие @import. Написанное ниже любого другого правила, оно отбрасывается целиком, и подключаемый файл не загружается вовсе. Симптом — стили библиотеки не применяются нигде.

Объявление есть, но написанного значения не дало

Если объявление присутствует в стилях элемента, но на элемент не влияет, значит, оно было отброшено на одном из этапов — и этапы различаются по виду в инспекторе: отброшенное при разборе зачёркнуто и помечено предупреждением, а отброшенное на вычислении значения показано победившим, без пометок.

Недопустимое значение или неизвестное свойство

Объявление с недопустимым значением или неизвестным свойством отбрасывается при разборе и в каскаде не участвует. Например:

.element {
  color: invalid-value;
  unknown-property: 10px;
}

Оба объявления будут проигнорированы.

Объявление с var()

Объявление с var() разбирается как допустимое, участвует в каскаде, но признаётся недопустимым позже, при вычислении значения. Результат равен unset:

  • для наследуемых свойств — значение родителя;
  • для ненаследуемых — начальное значение свойства.

В инспекторе такое объявление показано победившим и не зачёркнутым, но вычисленное значение не соответствует написанному. Это сбивает диагностику: внешне оно неотличимо от идущего перехода и активной анимации — там объявление тоже не зачёркнуто, а вычисленное значение написанному не соответствует. Развести их можно только по тому, идёт ли сейчас переход или анимация.

Объявление зачёркнуто

Если объявление зачёркнуто, значит, был спор, и решает его не только специфичность. Шаги сравнения идут в таком порядке:

  1. Важность и происхождение

    • Важные объявления (!important) сравниваются отдельно от обычных.
    • Пользовательские важные объявления перебивают авторские.
    • Важное объявление в атрибуте style перебивает важное объявление из таблицы стилей.
  2. Объявление из атрибута style Это отдельный шаг каскада, который сравнивается раньше слоёв. Специфичность его не касается: оно выигрывает даже у правила из последнего слоя.

  3. Слои каскада

    • Обычные объявления из более позднего слоя выигрывают независимо от селекторов.
    • Обычные объявления вне слоёв считаются последним слоем и выигрывают у всех слоёв; у важных объявлений порядок слоёв обратный, и там объявление вне слоёв слабее объявления из любого слоя.
    • Порядок слоёв задаётся первым упоминанием имени слоя, а не местом, где написаны правила.
  4. Специфичность Сравнение идёт покомпонентно слева направо, и никакое количество классов не перевешивает один идентификатор. Как считается сама специфичность — в следующем разделе.

  5. Порядок в готовом документе При полном равенстве выигрывает объявление, идущее позже. Готовый документ — это порядок узлов стилей в нём, а не порядок импортов в коде: сборщик переупорядочивает и склеивает файлы, генерация стилей из кода вставляет их во время выполнения, отложенно загруженный кусок приложения добавляет свои стили последним. Поэтому и проверять надо документ, а не строки в сборке.

Специфичность: как считать и где она вырастает незаметно

Специфичность записывается тройкой через дефис: 0-1-2. Считается она так:

  • Идентификатор (#id) добавляет единицу к первому компоненту.
  • Класс (.class), атрибут ([attr]) или псевдокласс (:hover) — к среднему.
  • Тип элемента (div) или псевдоэлемент (::before) — к третьему.

Что не повышает специфичность:

  • псевдокласс :where() — его специфичность всегда нулевая, что бы ни стояло в скобках;
  • универсальный селектор (*);
  • комбинаторы (>, +, ~);
  • медиа-запросы, контейнерные запросы, @supports.

Где специфичность вырастает незаметно:

  • :is(), :not(), :has() — берут специфичность самого специфичного аргумента внутри скобок, а сами по себе в подсчёт не входят; о самих этих селекторах — в материале Что современный CSS убрал из вашего JS.
  • Вложенный CSS — амперсанд (&) считается как :is(): вложенное правило весит по наибольшей специфичности родительского списка селекторов.
  • :nth-child(An+B of S) — добавляет специфичность селектора S.

Объявление победило, а эффекта нет

Если объявление написано, а на элементе ничего не изменилось, общего признака у этой ветки нет: каждая причина опознаётся по-своему — где-то по виду в инспекторе, где-то по самому симптому. Вот возможные варианты:

Свойство неприменимо к элементу

Например, width и height не действуют на строчные незаменяемые элементы, а свойства раскладки — на потомков элемента, который соответствующим контейнером не является; как устроен этот выбор, разобрано в материале Flexbox или Grid. В инспекторе объявление не зачёркнуто, а вычисленное значение совпадает с написанным — этим случай отличается от перехода, анимации и объявления с var(), где вычисленное значение написанному не соответствует. Признак этот не единственный: если свойство к элементу применимо, а эффекта всё равно нет, смотрите порядок отрисовки.

Идущий переход или активная анимация

Активная анимация перебивает обычное авторское объявление любой специфичности, а идущий переход перебивает всё, включая важные объявления. В инспекторе объявление не зачёркнуто, но вычисленное значение не соответствует написанному.

Сокращённое свойство переписало отдельное

Сокращённое свойство задаёт все свои составляющие, включая неназванные. Например:

.element {
  margin-top: 20px;
  margin: 10px;
}

Здесь верхний отступ задаст сокращённая запись: она переписала все составляющие, включая ту, что была назначена отдельно выше. Спор идёт по каждому объявлению отдельно, на уровне составляющих, и проигравшее отдельное объявление в инспекторе зачёркнуто.

Несостоявшееся наследование

Наследуются не все свойства. Например, рамка, внутренние отступы, фон и ширина не наследуются. Элементы форм по умолчанию не берут шрифт от родителя, пока он не задан им явно. В инспекторе объявления на элементе нет вовсе.

Порядок отрисовки

Если речь идёт о z-index, каскад тут ни при чём: объявление побеждает, а элемент всё равно внизу — дело в порядке отрисовки, а не в том, какое объявление выиграло. Этот случай разобран в материале z-index растёт, а элемент всё равно внизу.

Тупик: !important

Первым делом дописывают !important. Значение появляется, вопрос закрыт. Но через время это же правило нужно переопределить, и сверху ложится второй !important, потом третий. Отменить такое объявление обычным селектором уже нельзя.

Ловушка в том, что помогает !important не всегда, а цену берёт всегда:

  • Если выиграло более специфичное объявление, значение появится, но настоящая причина останется неразобранной.
  • Если объявление вообще не участвовало в каскаде (селектор не совпал, правило убито негодным селектором в списке), !important не меняет ничего.

Цена у !important всегда высокая: он переносит объявление в отдельную группу сравнения, где порядок происхождений перевёрнут. Важное пользовательское объявление перебивает важное авторское, а порядок слоёв для важных объявлений обратный.

Позиция редакции

Специфичность держать плоской: правило на уровне одного класса как норма, идентификаторы и вложенные утяжеления как исключение. Различия выражать порядком объявлений и слоями, а переопределять через revert-layer и revert, а не через селектор тяжелее предыдущего.

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

  • если библиотека разложена по слоям — в слой, объявленный раньше её слоя;
  • если библиотека отдана без слоёв — в любой слой, потому что важное объявление вне слоёв слабее важного объявления из любого слоя.

Граница: важное объявление в атрибуте style не перебивается из таблиц стилей ничем, и слои тут не помогут.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

↑↓ выбрать · Enter открыть · Esc закрыть