Помните сказку про мальчика, который кричал «волки»? Примерно так же в 2025 году случилось с «программированием на CSS». Вышла функция if(). Блогеры преждевременно хайпанули: всё, теперь у нас условия в CSS. Разработчики пошли читать спецификации, попробовали — и довольно быстро выяснилось, что внутри условного выражения style() возможностей почти нет. Многие разочаровались и похоронили идею.
В конце 2025 года Chrome выкатил революционный Range Syntax For Style Container Queries. Обновлённый style() научился сравнивать переменные между собой и поддерживать диапазонные выражения. Мы наконец‑то получили мощную условную логику в CSS, но мало кто это заметил.
В этой статье мы попытаемся реанимировать идею программирования на CSS. На примере интерфейсного паттерна — «выделение диапазона дат в календаре» — разберём, как обычная JS‑логика превращается в CSS‑логику (спойлер: очень просто).
Заинтригованы? Поехали.
Задача: выделение диапазона дат

Диапазоны дат встречаются повсюду:
бронирование отеля,
выбор отпуска,
спринты в планировщике,
фильтр по периоду в аналитике.
Типовая формулировка задачи звучит так: подсветить все дни между стартом и концом диапазона. Есть ряд особенностей:
Календарь обычно делают таблицей, а не плоским списком, то есть ячейки дней расположены внутри рядов недель.
При смене месяца выделенный диапазон должен сохраняться.
Диапазон может состоять из одного дня.
Границы диапазона включают даты старта и конца диапазона.
Обычный подход на JS
Типичный подход — пройтись по ячейкам, проверить на попадание в диапазон и поставить нужный класс. Примерно так:
const start = 4; const end = 6; document.querySelectorAll('.day-now').forEach((cell) => { const day = Number(cell.dataset.day); cell.classList.toggle('in-range', day >= start && day <= end); });
Всё работает. Проблема в том, что это исключительно «визуальная» задача. Точно такая же, как создание полосатых таблиц.
Сейчас никому и в голову не придёт делать полосатые таблицы на JS. А чем выделение диапазона дат хуже? Только тем, что у нас нет простого способа реализации на CSS. То есть не было.
Сделаем же это на CSS!
Реализация на CSS
Шаг 1: Разметка
Разметка практически не меняется. Это всё та же таблица, где ячейки — это дни, а ряды — это недели. Каждой ячейке в разметке нужно добавить CSS‑переменную с номером дня. Проще всего сделать это с помощью атрибута style:
<tr> <td class="day day-now" style="--day: 21">21</td> <td class="day day-now" style="--day: 22">22</td> ... </tr>
Да, приходится специально инлайнить стили, но это делается один раз при генерации шаблона.
Шаг 2: Переменные диапазона
Добавляем корневому элементу календаря CSS‑переменные со стартом и концом диапазона. Например, так:
.calendar { --day-start: 4; --day-end: 6; }
Шаг 3: Условная стилизация отдельных ячеек
Теперь самое интересное. Современный CSS позволяет писать условные выражения через if(). Проверка условий происходит внутри специальной конструкции style(), в соответствии с так называемым Range Syntax For Style Container Queries.
Благодаря Range Syntax можем сравнивать CSS‑переменные друг с другом, или CSS‑переменную с каким‑то значением. Причем мы можем использовать привычные операторы сравнения >, < или =.
У каждой ячейки с классом day-now есть CSS‑переменная --day с номером дня. Мы можем сравнить номер дня с начальным днём диапазона, который хранится в --day-start. И в зависимости от результата, задать ячейкам разный фон. Так выглядит сравнение:
.day-now { background-color: if( style(--day-start <= --day): #8b0000; else: rgba(255, 255, 255, 0.05); ); }
Ниже результат. Подкрасились все ячейки, день которых больше либо равен дню начала диапазона (4 число):

Шаг 4: Двойной диапазон
Мы можем использовать и двойной диапазон. То есть подсветить те ячейки, день которых одновременно больше или равен дню старта и меньше или равен дню конца:
.day-now { background-color: if( style(--day-start <= --day <= --day-end): #8b0000; else: rgba(255, 255, 255, 0.05); ); }
Результат:

Вот и всё. Решение полностью готово. С помощью одной строчки CSS. Осталось только протестировать, как всё работает. Давайте поменяем границы диапазона:
.calendar { --day-start: 2; --day-end: 20; }
Всё корректно:

Сравнение без включения границ диапазона
Естественно, можно включать или исключать границы диапазона. Для этого в зависимости от задачи используем <= и >= или < и >.
.calendar { --day-start: 2; --day-end: 20; } .day-now { background-color: if( style(--day-start < --day < --day-end): #8b0000; else: rgba(255, 255, 255, 0.05); ); }
Результат:

Диапазон в один день
Проверим, как работает сравнение, если границы диапазона совпадают:
.calendar { --day-start: 6; --day-end: 6; } .day-now { background-color: if( style(--day-start <= --day <= --day-end): #8b0000; else: rgba(255, 255, 255, 0.05); ); }
Всё корректно:

Смена месяца
Снова устанавливаем диапазон с 4 по 6 число и меняем месяц.
.calendar { --day-start: 4; --day-end: 6; }
Диапазон сохраняется, потому что логика завязана на значения переменных --day, а не на позицию в таблице. Естественно, разметка другого месяца должна быть корректной.

Вот так внезапно CSS‑превратился в язык с полноценной условной логикой, на котором действительно можно «программировать». Реализация сравнений оказалась на удивление простой и знакомой, а объём кода минимальным.
Что с поддержкой?
Единственная ложка дёгтя — это поддержка. В нашем случае придётся ждать как минимум двух последовательных событий:
Хорошей поддержки Style Container Queries. То есть самого механизма стилевых запросов. Здесь есть хорошие новости. Стилевые запросы добавили в интероп 2026, то есть в течение года планируют зарелизить во всех современных браузерах.
Хорошей поддержки Range Syntax For Style Container Queries. То есть продвинутого синтаксиса сравнений внутри стилевых запросов.
Если вам хочется узнать, в чём ценность нового подхода к стилизации, то можете сразу переходить к заключительной части статьи.
А если вы хотите использовать условную логику в CSS прямо сейчас, то читайте следующий раздел, в котором мы разберём фолбэк и его ограничения.
Фолбэк CSS-условий
Логику сравнений, включая сложные диапазонные сравнения, в CSS можно эмулировать с помощью математических функций и бинарной логики.
Для начала введём дополнительную переменную --gte-start и сохраним в неё результат вычитания номеров текущего и стартового дней:
Шаг 1. Вычитаем из номера текущего дня номер стартового
.calendar { --day-start: 10; --day-end: 12; } .day-now { --gte-start: calc(var(--day) - var(--day-start)); }
Возьмём десятое число как день старта. И выведем результат вычитания для каждой ячейки в правом нижнем углу. В днях до десятого получили отрицательный результат, в десятом дне — ноль, в днях после десятого — положительный результат:

--day-start из --day в каждой ячейке при --day-start: 10Шаг 2. Ограничиваем результат через clamp()
Теперь заменим обычное вычитание через calc() вычитанием через clamp(). Функция clamp() позволяет ограничивать выражение максимальным и минимальным значением. Зададим минимальное значение 0, а максимальное 1:
.day-now { --gte-start: clamp( 0, calc(var(--day) - var(--day-start)), 1 ); }
Теперь всё, что меньше 0 стало 0, всё, что больше 1 стало 1.

clamp()Шаг 3. Включаем стартовый день в диапазон
Для этого добавляем единицу к результату вычитания внутри clamp():
.day-now { --gte-start: clamp( 0, calc(var(--day) - var(--day-start) + 1), 1 ); }
Теперь стартовый день тоже даёт 1. Мы получили выражение, которое даёт результат аналогичный сравнению текущий день ≥ день старта.

Шаг 4. Добавляем вторую границу
По аналогии с первой границей диапазона добавим вторую, включающую дни которые меньше или равны конечному дню. Единственное отличие — это порядок вычитания. Теперь мы вычитаем не границу из дня, а день из границы:
.calendar { --day-start: 10; --day-end: 12; } .day-now { --gte-start: clamp(0, calc(var(--day) - var(--day-start) + 1), 1); --lte-end: clamp(0, calc(var(--day-end) - var(--day) + 1), 1); }
Второе выражение даёт результат аналогичный сравнению текущий день ≤ день конца. Вот такие значения будут в переменной --lte-end, если конечный день — 12.

--lte-end в каждой ячейке при --day-end: 12Шаг 5. Пересечение диапазонов
Используем умножение, чтобы получить логическое И:
1 × 1 = 1,
в остальных случаях = 0.
.day-now { --gte-start: clamp( 0, calc(var(--day) - var(--day-start) + 1), 1 ); --lte-end: clamp( 0, calc(var(--day-end) - var(--day) + 1), 1); --in-range: calc(var(--gte-start) * var(--lte-end)); }
Вот значения переменной --in-range. Единицы стоят у тех ячеек, которые попали в диапазон с 10 по 12 день включительно:

--in-range в каждой ячейке при --day-start: 10 и --day-end: 12Шаг 6. Зашиваем флаг в стили
Теперь у нас есть переменная, содержащая ноль в одних ячейках и единицу в других. Эти значения можно использовать в качестве компонента прозрачности цвета в фоновом изображении, которое сделано с помощ��ю одноцветного градиента:
.day-now { background-image: linear-gradient( rgba(139, 0, 0, var(--in-range)), rgba(139, 0, 0, var(--in-range)) ); }
В ячейках, которые не попали в диапазон, фоновое изображение будет полностью прозрачным. В ячейках, которые попали в диапазон, получим заливку цветом:

Плюсы, минусы и ограничения фолбэка
Плюс фолбэка — это поддержка. Его можно использовать прямо сейчас.
Минус фолбэка — сложность. Мало кто захочет разбираться и поддерживать эту имитацию бинарной логики. Хотя запредельной эту сложность назвать нельзя.
Второй минус — ограничения по стилизации. Можно работать только с числовыми значениями CSS‑свойств, которые можно вычислять с помощью нуля и единицы. Например, удобно использовать прозрачность, толщину рамок или обводок, размер шрифта. А вот работать с перечисляемыми или строковыми значениями свойств не получится.
В чём смысл «программирования на CSS»?
Если вкратце, то всё делается ради слабой связанности aka «Loose Coupling».
Вспомните типичную реализацию на JS. Сколько всего надо знать скрипту, чтобы выполнить задачу? Перечислим:
особенности устройства разметки (плоский список или сложная таблица);
особенности использования CSS‑классов (как помечается день текущего месяца, а как смежного);
как устроена стилизация дней диапазона (имя класса, или дополнительный класс‑модификатор, или прямая стилизация через атрибут
styleс конкретным свойством и значением).
А не слишком ли много? Это и называется сильная связность.
А теперь посмотрим на реализацию с использованием CSS‑логики. В ней диапазон дат тоже задаётся скриптом. Но у скрипта есть лаконичный и понятный интерфейс в виде двух CSS‑переменных, которые он просто меняет:
.calendar { --day-start: <number>; --day-end: <number>; }
Этого интерфейса полностью достаточно для выполнения задачи. Скрипту не нужны знания о внутреннем устройстве разметки, именовании классов и особенностях стилизации.
Вся логика отображения изолирована внутри CSS.
Это и есть слабая связанность.
Это и есть следование заветам отцов‑основателей веба про разделение содержания, внешнего вида и поведения.
Скрытый текст
Подписывайтесь на мой телеграм‑канал «CSS Боль». Там собраны все материалы, видеоролики, ссылки на пошаговые демки и новости чемпионатов по фронтенду
