Skip to content

Всё для WordPress, веб-разработки — и не только

🎯 Селекторы в виджетах Elementor: полное руководство для разработчиков

🎯 Селекторы в виджетах Elementor: полное руководство для разработчиков

Зачем разработчику Elementor селекторы и как они устроены

Когда пользователь крутит настройки виджета в редакторе, он ожидает мгновенной реакции на экране. Без селекторов разработчику пришлось бы писать JS-обработчик на каждое изменение поля. С селекторами, всё решает CSS.

Параметр selectors (и его менее известный собрат selectors_dictionary) встраивается прямо в массив вызова add_control(). Elementor на лету подставляет значения из полей в CSS-правила и выводит их в файл поста, что-то вроде /wp-content/uploads/elementor/css/post-1234.css. Как только пользователь выходит из редактора, инлайн-стили исчезают, остаётся чистый сгенерированный CSS.

💡 Быстрый обзор:

  • Разберитесь в синтаксисе selectors и таблице плейсхолдеров
  • Посмотрите живые примеры для цвета и размеров
  • Научитесь забирать значения из соседних контролов
  • Освойте selectors_dictionary для подмены CSS-объявлений
  • Соберите пазл из CSS-переменных и скрытых контролов

Где прописываются селекторы

Когда вы создаёте виджет, каждый вызов add_control() принимает массив настроек. Именно в нём и живут selectors. Для групповых контролов синтаксис тот же, массив передаётся внутри групповой регистрации.

Базовый формат:

1'selectors' => [
2 '{{WRAPPER}} .my-widget-class' => 'color: {{VALUE}}',
3]

Ключ, CSS-селектор (начинается с {{WRAPPER}}, чтобы не задеть соседние виджеты на странице). Значение, одно или несколько CSS-объявлений с динамическими плейсхолдерами. Elementor берёт текущее значение контрола и подставляет на место плейсхолдера.

Результат рендерится во внешний CSS-файл поста, стили живут только пока открыт редактор и сразу после сохранения. Никакого инлайн-мусора.

Таблица переменных в фигурных скобках

Никакой магии, обычный поиск-и-замена. Но разнообразие плейсхолдеров открывает двери к довольно хитрым конструкциям.

Для селекторов (ключ массива)

Плейсхолдер

Что подставляет

{{WRAPPER}}

Уникальный селектор экземпляра виджета, например .elementor-50 .elementor-element.elementor-element-092e113. Используйте почти всегда

{{ID}}

Только ID виджета (та часть после дефиса, 092e113)

(desktop) / (tablet) / (mobile)

Ограничивает правило указанным устройством. С + - «от этого разрешения и выше»: (tablet+) = планшет и шире

{{CURRENT_ITEM}}

Активный элемент repeater-контрола

Для объявлений (значение массива)

Плейсхолдер

Что подставляет

{{VALUE}}

Сырое значение контрола. Может быть переопределено selectors_dictionary

{{SIZE}} и {{UNIT}}

Число и единица измерения из числовых контролов. Обычно идут парой: {{SIZE}}{{UNIT}}

{{TOP}} / {{LEFT}} / {{RIGHT}} / {{BOTTOM}}

Направления из dimensions-контрола

{{URL}} или другое имя

Доступ к именованному свойству составных контролов: к примеру, Media Control возвращает массив с полями url id alt

{{other.SIZE}}

Значение другого контрола по ID. Суффиксы _tablet и _mobile дают адаптивные данные

{{setting.SIZE \|\| 5}}

Fallback: если контрол пуст - подставится 5. Работает и со строками в кавычках, и с DEFAULT другого контрола

Простые примеры, от цвета до фонового изображения

Цвет из палитры. Ничего лишнего:

1'selectors' => [
2 '{{WRAPPER}} .elementor-svg-divider-basic-text' => 'color: {{VALUE}}',
3],

Числовой контрол с единицей и без. Второе свойство (stroke-width) намеренно без {{UNIT}}, толщина обводки в пикселях, без px:

1'selectors' => [
2 '{{WRAPPER}} svg.sde-classic' =>
3 'height: {{SIZE}}{{UNIT}}; stroke-width: {{SIZE}};',
4],

Отступы из dimensions-контрола, каждое направление отдельно:

1'selectors' => [
2 '{{WRAPPER}} .elementor-svg-divider-basic-button' =>
3 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
4],

Фоновое изображение слайда в repeater-контроле:

1'selectors' => [
2 '{{WRAPPER}} {{CURRENT_ITEM}} .swiper-slide-bg' =>
3 'background-image: url({{URL}})',
4],

Условная позиция для RTL. Один и тот же контрол даёт разные свойства в зависимости от направления текста:

1'selectors' => [
2 'body:not(.rtl) {{WRAPPER}} .dialog-close-button' => 'right: {{SIZE}}{{UNIT}}',
3 'body.rtl {{WRAPPER}} .dialog-close-button' => 'left: {{SIZE}}{{UNIT}}',
4],

Как забрать значение из другого контрола

Если два поля влияют на один и тот же CSS, не дублируйте массив, просто сошлитесь на соседний контрол:

1'selectors' => [
2 '{{WRAPPER}} svg.sde-classic' =>
3 'stroke-dasharray: {{dash_length.SIZE}} {{whitespace_length.SIZE}};',
4],

Здесь dash_length и whitespace_length, ID других контролов того же виджета. Никаких дополнительных вызовов, просто точечная нотация.

Адаптивная версия, значения подтягиваются с учётом устройства. Реальный пример из Elementor Pro:

1'selectors' => [
2 '(desktop).elementor-msie {{WRAPPER}} .elementor-portfolio-item' =>
3 'width: calc( 100% / {{columns.SIZE}} ); border: {{SIZE}}px solid transparent',
4 '(tablet).elementor-msie {{WRAPPER}} .elementor-portfolio-item' =>
5 'width: calc( 100% / {{columns_tablet.SIZE}} ); border: {{SIZE}}px solid transparent',
6 '(mobile).elementor-msie {{WRAPPER}} .elementor-portfolio-item' =>
7 'width: calc( 100% / {{columns_mobile.SIZE}} ); border: {{SIZE}}px solid transparent',
8],

Каждый брейкпоинт получает своё значение columns. Остальные свойства (border, SIZE) общие, они не привязаны к устройству.

selectors_dictionary, switch-case для CSS

Главная недооценённая возможность. selectors_dictionary заменяет {{VALUE}} на жёстко заданную строку, по сути, превращает значение контрола в ключ словаря.

Возьмём стандартный Align-контрол с опциями left/center/right. Без словаря вы бы писали нечто противоестественное:

1'selectors' => [
2 $sde_selector => 'margin: 0 auto; margin-{{VALUE}}: 0;',
3],

Для center это даст margin: 0 auto; margin-center: 0;. Свойства margin-center не существует, браузер его молча игнорирует. Но выглядит грязно.

Словарь делает то же самое чисто:

1'selectors_dictionary' => [
2 'left' => 'margin-right: auto',
3 'center' => 'margin: 0 auto',
4 'right' => 'margin-left: auto',
5],
6'selectors' => [
7 '{{WRAPPER}} .sde' => '{{VALUE}}',
8],

Значение контрола center{{VALUE}} становится margin: 0 auto. Всё.

Важное ограничение: после активации selectors_dictionary вы теряете исходное {{VALUE}}. Если в том же массиве есть другая пара селектор-объявление, которой нужно оригинальное значение, она получит уже подменённую строку. Вот проблемный пример:

1'selectors' => [
2 '{{WRAPPER}} .sde' => '{{VALUE}}',
3 '{{WRAPPER}}.elementor-sde-scale-the-cropped .sde-cropping-allow .sde' =>
4 'transform-origin: {{VALUE}} 0;',
5],

Здесь transform-origin получит margin: 0 auto 0; вместо center 0;. Решение, вынести зависимые объявления в отдельный контрол.

Словарь отлично справляется и с трансляцией одиночных CSS-значений:

1'selectors_dictionary' => [
2 'top' => 'flex-start',
3 'middle' => 'center',
4 'bottom' => 'flex-end',
5],
6'selectors' => [
7 '{{WRAPPER}} .elementor-price-table__currency' => 'align-self: {{VALUE}}',
8],

И даже с целыми наборами объявлений, один ключ → несколько CSS-свойств:

1'selectors_dictionary' => [
2 'left' => 'right: auto; left: 0',
3 'right' => 'left: auto; right: 0',
4],
5'selectors' => [
6 '{{WRAPPER}}.elementor-wc-products ul.products li.product span.onsale' => '{{VALUE}}',
7],

CSS-переменные, calc() и скрытые контролы, собираем пазл

Настоящая сила селекторов раскрывается в комбинации. Один контрол задаёт CSS-переменную, другой на неё ссылается, третий включает/выключает целый блок правил через условие.

Слайдер Scale% пишет переменную:

1'selectors' => [
2 '{{WRAPPER}} .sde' => '--sde-scale-percentage: {{SIZE}};',
3],

Переключатель «Scale cropped» использует эту переменную в двух местах, и для transform, и для передачи в контрол Gap:

1'selectors' => [
2 '{{WRAPPER}} .sde' =>
3 'transform: scale(var(--sde-scale-percentage)) scale(0.01);
4 --sde-scale-pct-for-gap: var(--sde-scale-percentage);',
5],

Скрытый контрол с условием, тот же transform, но с другим селектором (для несжатого состояния):

1'condition' => [
2 'scale_the_cropped!' => 'cropped',
3],
4'selectors' => [
5 '{{WRAPPER}} .sde svg' =>
6 'transform: scale(var(--sde-scale-percentage)) scale(0.01);',
7],

А контрол Gap использует переданную переменную с fallback:

1'selectors' => [
2 '{{WRAPPER}} .sde' =>
3 'padding: calc({{SIZE}}{{UNIT}} / (var(--sde-scale-pct-for-gap, 100) / 100)) 0;',
4],

Что здесь происходит: Gap компенсирует масштабирование. Если элемент сжат вдвое, зазор умножается на 2, чтобы визуально остаться прежним. Без сжатия (переменная не задана) срабатывает fallback 100 → деление на 1 → зазор не меняется. Чистая математика на CSS, без единой строки JS.

Стекование transform, обходной манёвр для Edge

Отдельного упоминания заслуживает конструкция scale(X) scale(0.01). Почему не scale(calc(var(--sde-scale-percentage) / 100))? Потому что Edge не поддерживает calc() внутри transform. Вообще.

Решение, стекование. Браузеры применяют transform-функции последовательно, одна за другой. Поэтому:

1transform: scale(var(--sde-scale-percentage)) scale(0.01);

Математически эквивалентно scale(var(--sde-scale-percentage) * 0.01), то есть делению на 100. Пользователь получает привычный слайдер 0-100, а под капотом значение превращается в коэффициент 0-1.

Тот же принцип применим к другим трансформациям, rotate, translate, skew, и работает во всех современных браузерах, включая Edge.

⁉️🤔 Частые вопросы

Что именно попадает в сгенерированный CSS-файл?

Elementor собирает все селекторы из зарегистрированных контролов виджета, подставляет текущие значения из настроек пользователя и записывает результат в файл /wp-content/uploads/elementor/css/post-XXXX.css. Это не инлайн-стили и не dynamic CSS на лету, статический файл, который кешируется браузером и живёт до следующего изменения настроек в редакторе. Сами плейсхолдеры {{VALUE}} {{SIZE}} и другие не являются частью шаблонного движка WordPress или Blade: Elementor делает обычный str_replace на этапе генерации CSS, перебирая все пары селектор-объявление и заменяя токены на фактические значения контролов.

Как отлаживать селекторы, если CSS не применяется?

Откройте сгенерированный CSS-файл поста (путь виден в исходном коде страницы) и проверьте, что правило там есть. Если правила нет, ищите опечатку в ID контрола или синтаксическую ошибку в массиве selectors. Если правило есть, но не работает, проверьте специфичность селектора: {{WRAPPER}} даёт высокий приоритет, но вложенные темы могут перебивать через !important. Включите WP_DEBUG и смотрите PHP-логи: Elementor молча пропускает некорректные массивы без вывода ошибок на экран. Используйте {{WRAPPER}} ВСЕГДА, кроме случаев намеренного таргетинга body или html.

Чем selectors отличается от кастомного CSS в настройках виджета?

Кастомный CSS (вкладка Advanced) пишет пользователь вручную, это статические правила, не реагирующие на изменения настроек. Селекторы же связывают контролы с CSS динамически: повернул слайдер, изменился width, переключил Align, перестроились margin. Пользователь не видит этой механики, он просто получает живой предпросмотр. Для разработчика главный выигрыш, отсутствие метода _content_template(): без селекторов пришлось бы писать JS-рендеринг предпросмотра для каждого контрола.

Нужен ли selectors_dictionary, если я уже использую selectors?

Да, для качественного скачка в чистоте кода. Без словаря вы обрабатываете значение контрола неявно, через странные CSS-свойства вроде несуществующего margin-center, которые браузер игнорирует. Со словарём вы явно указываете: «если значение left, подставь margin-right: auto, если center, margin: 0 auto». Код становится самодокументируемым, а главное, {{VALUE}} больше не тянет за собой оригинальное значение контрола в другие объявления того же массива.

Можно ли комбинировать selectors с _content_template() в одном виджете?

Технически да, но на практике это сигнал пересмотреть архитектуру. Если для большинства контролов хватает селекторов, а пара полей требует JS-рендеринга, вынесите JS-логику в отдельный метод и вызывайте точечно. Полный отказ от селекторов в пользу _content_template() означает, что вы пишете JS-дубликат всей PHP-логики контролов, поддержка такого виджета быстро становится проблемой.

Стоит ли осваивать селекторы в 2026 году

Elementor продолжает развивать атомарную инфраструктуру, Variables Manager, контейнеры на Grid и Flexbox, глобальные стили. Но фундамент виджетной механики не меняется с четвёртой версии: selectors и selectors_dictionary, всё тот же основной способ связать контрол с живым предпросмотром.

Освоив эту технику, вы закрываете добрую половину всей JS-логики типичного виджета. Вместо обработчиков на каждое поле, один массив selectors на контрол. Вместо сложного позиционирования в превью, связка CSS-переменных с calc() и парой скрытых контролов. Плагин SVG Divider for Elementor, живой пример: больше половины его контролов управляются исключительно через селекторы, без единого вызова _content_template().

Главное правило, не усложнять. Если вы ловите себя на том, что пишете четвёртый вложенный calc() с тремя переменными, остановитесь. Возможно, проще добавить скрытый контрол-посредник или разбить логику на два отдельных поля. А исходный код Elementor, лучший учебник: метод add_control_rules() в core/files/css/base.php показывает, как селекторы обрабатываются изнутри.