Skip to content

Wszystko o WordPressie, tworzeniu stron — i nie tylko

🎯 Selektory w widgetach Elementor: pełny przewodnik dla programistów

🎯 Selektory w widgetach Elementor: pełny przewodnik dla programistów

Do czego programiście Elementora służą selektory i jak są zbudowane

Kiedy użytkownik zmienia ustawienia widżetu w edytorze, oczekuje natychmiastowej reakcji na ekranie. Bez selektorów programista musiałby pisać procedurę JS dla każdej zmiany pola. Z selektorami wszystko załatwia CSS.

Parametr selectors (i jego mniej znany odpowiednik selectors_dictionary) jest osadzany bezpośrednio w tablicy wywołania add_control(). Elementor w locie podstawia wartości z pól do reguł CSS i wyprowadza je do pliku posta, coś w rodzaju /wp-content/uploads/elementor/css/post-1234.css. Gdy tylko użytkownik wyjdzie z edytora, style inline znikają, zostaje czysty wygenerowany CSS.

💡 Szybki przegląd:

  • Proszę poznać składnię selectors i tabelę placeholderów
  • Proszę zobaczyć działające przykłady dla koloru i rozmiarów
  • Proszę nauczyć się pobierać wartości z sąsiednich kontrolek
  • Proszę opanować selectors_dictionary do podmiany deklaracji CSS
  • Proszę ułożyć puzzle ze zmiennych CSS i ukrytych kontrolek

Gdzie definiuje się selektory

Kiedy tworzą Państwo widżet, każde wywołanie add_control() przyjmuje tablicę ustawień. To właśnie w niej znajdują się selectors. Dla kontrolek grupowych składnia jest taka sama, tablica jest przekazywana wewnątrz rejestracji grupowej.

Podstawowy format:

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

Klucz, selektor CSS (zaczyna się od {{WRAPPER}}, aby nie naruszyć sąsiednich widżetów na stronie). Wartość, jedna lub kilka deklaracji CSS z dynamicznymi placeholderami. Elementor pobiera bieżącą wartość kontrolki i podstawia ją w miejsce placeholdera.

Wynik jest renderowany do zewnętrznego pliku CSS posta, style istnieją tylko gdy edytor jest otwarty i zaraz po zapisaniu. Żadnych śmieci inline.

Tabela zmiennych w nawiasach klamrowych

Żadnej magii, zwykłe szukaj-i-zamień. Ale różnorodność placeholderów otwiera drzwi do całkiem sprytnych konstrukcji.

Dla selektorów (klucz tablicy)

Placeholder

Co podstawia

{{WRAPPER}}

Unikalny selektor instancji widżetu, np. .elementor-50 .elementor-element.elementor-element-092e113. Proszę używać prawie zawsze

{{ID}}

Tylko ID widżetu (ta część po myślniku, 092e113)

(desktop) / (tablet) / (mobile)

Ogranicza regułę do wskazanego urządzenia. Z +, „od tego rozmiaru wzwyż": (tablet+) = tablet i szersze

{{CURRENT_ITEM}}

Aktywny element kontrolki typu repeater

Dla deklaracji (wartość tablicy)

Placeholder

Co podstawia

{{VALUE}}

Surowa wartość kontrolki. Może być nadpisana przez selectors_dictionary

{{SIZE}} i {{UNIT}}

Liczba i jednostka miary z kontrolek numerycznych. Zwykle występują razem: {{SIZE}}{{UNIT}}

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

Kierunki z kontrolki dimensions

{{URL}} lub inna nazwa

Dostęp do nazwanej właściwości kontrolek złożonych: np. Media Control zwraca tablicę z polami url id alt

{{other.SIZE}}

Wartość innej kontrolki po ID. Przyrostki _tablet i _mobile dają dane responsywne

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

Fallback: jeśli kontrolka jest pusta, podstawione zostanie 5. Działa i z ciągami znaków w cudzysłowie, i z DEFAULT innej kontrolki

Proste przykłady, od koloru do obrazu tła

Kolor z palety. Nic zbędnego:

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

Kontrolka numeryczna z jednostką i bez. Druga właściwość (stroke-width) celowo bez {{UNIT}}, grubość obrysu w pikselach, bez px:

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

Odstępy z kontrolki dimensions, każdy kierunek osobno:

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

Obraz tła slajdu w kontrolce repeater:

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

Warunkowa pozycja dla RTL. Ta sama kontrolka daje różne właściwości w zależności od kierunku tekstu:

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

Jak pobrać wartość z innej kontrolki

Jeśli dwa pola wpływają na ten sam CSS, proszę nie duplikować tablicy, wystarczy odwołać się do sąsiedniej kontrolki:

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

Tutaj dash_length i whitespace_length, ID innych kontrolek tego samego widżetu. Żadnych dodatkowych wywołań, po prostu notacja z kropką.

Wersja adaptacyjna, wartości są pobierane z uwzględnieniem urządzenia. Rzeczywisty przykład z 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],

Każdy punkt kontrolny otrzymuje własną wartość columns. Pozostałe właściwości (border, SIZE) są wspólne, nie są powiązane z urządzeniem.

Selectors_dictionary, switch-case dla CSS

Główna niedoceniana możliwość. selectors_dictionary zastępuje {{VALUE}} sztywno zdefiniowanym ciągiem znaków, w istocie przekształca wartość kontrolki w klucz słownika.

Weźmy standardową kontrolkę Align z opcjami left/center/right. Bez słownika napisaliby Państwo coś nienaturalnego:

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

Dla center da to margin: 0 auto; margin-center: 0;. Właściwość margin-center nie istnieje, przeglądarka po cichu ją ignoruje. Ale wygląda to nieczysto.

Słownik robi to samo czysto:

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],

Wartość kontrolki center{{VALUE}} staje się margin: 0 auto. To wszystko.

Ważne ograniczenie: po aktywacji selectors_dictionary tracą Państwo pierwotne {{VALUE}}. Jeśli w tej samej tablicy znajduje się inna para selektor-deklaracja, która potrzebuje oryginalnej wartości, otrzyma ona już podstawiony ciąg. Oto problematyczny przykład:

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

Tutaj transform-origin otrzyma margin: 0 auto 0; zamiast center 0;. Rozwiązanie: wydzielić zależne deklaracje do osobnej kontrolki.

Słownik doskonale radzi sobie również z translacją pojedynczych wartości 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],

A nawet z całymi zestawami deklaracji, jeden klucz → kilka właściwości 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],

Zmienne CSS, calc() i ukryte kontrolki, składamy puzzle

Prawdziwa siła selektorów ujawnia się w kombinacji. Jedna kontrolka ustawia zmienną CSS, inna się do niej odwołuje, trzecia włącza/wyłącza cały blok reguł poprzez warunek.

Suwak Scale% zapisuje zmienną:

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

Przełącznik „Scale cropped" używa tej zmiennej w dwóch miejscach: zarówno dla transform, jak i do przekazania do kontrolki 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],

Ukryta kontrolka z warunkiem, ten sam transform, ale z innym selektorem (dla stanu nieściśniętego):

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],

A kontrolka Gap używa przekazanej zmiennej z fallbackiem:

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

Co tu się dzieje: Gap kompensuje skalowanie. Jeśli element jest ściśnięty dwukrotnie, odstęp jest mnożony przez 2, aby wizualnie pozostał taki sam. Bez skalowania (zmienna nie jest ustawiona) działa fallback 100 → dzielenie przez 1 → odstęp nie zmienia się. Czysta matematyka na CSS, bez ani jednej linii JS.

Stackowanie transform, manewr omijający dla Edge

Na osobną wzmiankę zasługuje konstrukcja scale(X) scale(0.01). Dlaczego nie scale(calc(var(--sde-scale-percentage) / 100))? Ponieważ Edge nie obsługuje calc() wewnątrz transform. W ogóle.

Rozwiązanie: stackowanie. Przeglądarki stosują funkcje transform sekwencyjnie, jedna po drugiej. Dlatego:

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

Matematycznie jest to równoważne scale(var(--sde-scale-percentage) * 0.01), czyli podzieleniu przez 100. Użytkownik otrzymuje zwykły suwak 0-100, a pod maską wartość jest przekształcana na współczynnik 0-1.

Ta sama zasada ma zastosowanie do innych transformacji, rotate, translate, skew, i działa we wszystkich nowoczesnych przeglądarkach, włącznie z Edge.

⁉️🤔 Najczęściej zadawane pytania

Co dokładnie trafia do wygenerowanego pliku CSS?

Elementor zbiera wszystkie selektory z zarejestrowanych kontrolek widżetu, podstawia bieżące wartości z ustawień użytkownika i zapisuje wynik w pliku /wp-content/uploads/elementor/css/post-XXXX.css. Nie są to style inline ani dynamiczny CSS generowany na bieżąco, lecz statyczny plik, który jest cache'owany przez przeglądarkę i istnieje do kolejnej zmiany ustawień w edytorze. Same symbole zastępcze {{VALUE}}, {{SIZE}} i inne nie są częścią silnika szablonów WordPress ani Blade: Elementor wykonuje zwykłe str_replace na etapie generowania CSS, przeglądając wszystkie pary selektor-deklaracja i zastępując tokeny faktycznymi wartościami kontrolek.

Jak debugować selektory, jeśli CSS nie jest stosowany?

Proszę otworzyć wygenerowany plik CSS posta (ścieżka jest widoczna w kodzie źródłowym strony) i sprawdzić, czy reguła tam jest. Jeśli reguły nie ma, proszę szukać literówki w ID kontrolki lub błędu składniowego w tablicy selectors. Jeśli reguła jest, ale nie działa, proszę sprawdzić specyficzność selektora: {{WRAPPER}} daje wysoki priorytet, ale zagnieżdżone motywy mogą nadpisywać go przez !important. Proszę włączyć WP_DEBUG i sprawdzić logi PHP: Elementor po cichu pomija nieprawidłowe tablice, nie wyświetlając błędów na ekranie. Proszę używać {{WRAPPER}} ZAWSZE, z wyjątkiem przypadków świadomego targetowania body lub html.

Czym selectors różni się od niestandardowego CSS w ustawieniach widżetu?

Niestandardowy CSS (zakładka Zaawansowane) użytkownik pisze ręcznie, są to statyczne reguły, które nie reagują na zmiany ustawień. Selektory natomiast wiążą kontrolki z CSS dynamicznie: przesunięto suwak, zmienił się width, przełączono Wyrównanie, przebudowały się margin. Użytkownik nie widzi tej mechaniki, po prostu otrzymuje podgląd na żywo. Dla developera główną wygraną jest brak metody _content_template(): bez selektorów trzeba by pisać renderowanie podglądu w JS dla każdej kontrolki.

Czy potrzebny jest selectors_dictionary, jeśli już używam selectors?

Tak, dla jakościowego skoku w czystości kodu. Bez słownika obsługuje Pan/Pani wartość kontrolki niejawnie, poprzez dziwne właściwości CSS, takie jak nieistniejący margin-center, które przeglądarka ignoruje. Ze słownikiem jawnie wskazuje Pan/Pani: „jeśli wartość left, podstaw margin-right: auto, jeśli center, margin: 0 auto". Kod staje się samodokumentujący, a co najważniejsze, {{VALUE}} nie ciągnie już za sobą oryginalnej wartości kontrolki do innych deklaracji w tej samej tablicy.

Czy można łączyć selectors z _content_template() w jednym widżecie?

Technicznie tak, ale w praktyce jest to sygnał, by przejrzeć architekturę. Jeśli dla większości kontrolek wystarczają selektory, a kilka pól wymaga renderowania w JS, proszę wydzielić logikę JS do osobnej metody i wywoływać ją punktowo. Całkowita rezygnacja z selektorów na rzecz _content_template() oznacza, że pisze Pan/Pani duplikat całej logiki PHP kontrolek w JS, a utrzymanie takiego widżetu szybko staje się problemem.

Czy warto opanować selektory w 2026 roku

Elementor nadal rozwija infrastrukturę atomową, Variables Manager, kontenery na Grid i Flexbox, style globalne. Jednak fundament mechaniki widżetów nie zmienia się od czwartej wersji: selectors i selectors_dictionary to wciąż ten sam podstawowy sposób na powiązanie kontrolki z podglądem na żywo.

Po opanowaniu tej techniki eliminuje Pan/Pani dobrą połowę całej logiki JS typowego widżetu. Zamiast handlerów dla każdego pola, jedna tablica selectors na kontrolkę. Zamiast skomplikowanego pozycjonowania w podglądzie, powiązanie zmiennych CSS z calc() i parą ukrytych kontrolek. Wtyczka SVG Divider for Elementor jest żywym przykładem: ponad połowa jej kontrolek jest zarządzana wyłącznie przez selektory, bez ani jednego wywołania _content_template().

Główna zasada: nie komplikować. Jeśli przyłapuje się Pan/Pani na pisaniu czwartego zagnieżdżonego calc() z trzema zmiennymi, proszę się zatrzymać. Być może prościej jest dodać ukrytą kontrolkę pośredniczącą lub rozbić logikę na dwa osobne pola. A kod źródłowy Elementora to najlepszy podręcznik: metoda add_control_rules() w core/files/css/base.php pokazuje, jak selektory są przetwarzane od wewnątrz.