
🎯 Селекторы в виджетах 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-файл поста, стили живут только пока открыт редактор и сразу после сохранения. Никакого инлайн-мусора.
Таблица переменных в фигурных скобках
Никакой магии, обычный поиск-и-замена. Но разнообразие плейсхолдеров открывает двери к довольно хитрым конструкциям.
Для селекторов (ключ массива)
Плейсхолдер | Что подставляет |
|---|---|
| Уникальный селектор экземпляра виджета, например |
| Только ID виджета (та часть после дефиса, |
| Ограничивает правило указанным устройством. С |
| Активный элемент repeater-контрола |
Для объявлений (значение массива)
Плейсхолдер | Что подставляет |
|---|---|
| Сырое значение контрола. Может быть переопределено |
| Число и единица измерения из числовых контролов. Обычно идут парой: |
| Направления из dimensions-контрола |
| Доступ к именованному свойству составных контролов: к примеру, Media Control возвращает массив с полями |
| Значение другого контрола по ID. Суффиксы |
| Fallback: если контрол пуст - подставится |
Простые примеры, от цвета до фонового изображения
Цвет из палитры. Ничего лишнего:
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-функции последовательно, одна за другой. Поэтому:
1 transform: 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 показывает, как селекторы обрабатываются изнутри.



