Skip to content

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

🎯 Селектори у віджетах 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. Це не інлайн-стилі та не динамічний 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 показує, як селектори обробляються зсередини.