
🎯 Selettori nei widget di Elementor: guida completa per sviluppatori
Perché gli sviluppatori Elementor hanno bisogno dei selettori e come funzionano
Quando un utente modifica le impostazioni di un widget nell'editor, si aspetta una risposta immediata sullo schermo. Senza i selettori, uno sviluppatore dovrebbe scrivere un gestore JS per ogni modifica di campo. Con i selettori, tutto si risolve con i CSS.
Il parametro selectors (e la sua controparte meno nota selectors_dictionary) è incorporato direttamente nell'array della chiamata add_control(). Elementor sostituisce dinamicamente i valori dei campi nelle regole CSS e li scrive nel file del post, qualcosa come /wp-content/uploads/elementor/css/post-1234.css. Non appena l'utente esce dall'editor, gli stili inline scompaiono, lasciando CSS generato pulito.
💡 Panoramica rapida:
- Comprendere la sintassi dei selettori e la tabella dei segnaposto
- Vedere esempi pratici per colore e dimensioni
- Imparare a recuperare valori da controlli adiacenti
- Padroneggiare selectors_dictionary per la sostituzione di dichiarazioni CSS
- Mettere insieme il puzzle delle variabili CSS e dei controlli nascosti
Dove si definiscono i selettori
Quando crei un widget, ogni chiamata add_control() accetta un array di impostazioni. È esattamente lì che risiedono i selectors. Per i controlli di gruppo la sintassi è la stessa, l'array viene passato all'interno della registrazione del gruppo.
Formato base:
1 'selectors' => [ 2 '{{WRAPPER}} .my-widget-class' => 'color: {{VALUE}}', 3 ]
La chiave è un selettore CSS (inizia con {{WRAPPER}} per evitare di influenzare i widget vicini nella pagina). Il valore è una o più dichiarazioni CSS con segnaposto dinamici. Elementor prende il valore corrente del controllo e lo sostituisce al posto del segnaposto.
Il risultato viene renderizzato nel file CSS esterno del post, gli stili esistono solo mentre l'editor è aperto e subito dopo il salvataggio. Nessun pasticcio inline.
Tabella delle variabili con parentesi graffe
Nessuna magia, solo trova e sostituisci. Ma la varietà di segnaposto apre le porte a costruzioni piuttosto ingegnose.
Per i selettori (chiave dell'array)
Segnaposto | Cosa sostituisce |
|---|---|
| Selettore univoco dell'istanza del widget, ad esempio |
| Solo l'ID del widget (la parte dopo il trattino, |
| Limita la regola al dispositivo specificato. Con |
| Elemento attivo di un controllo ripetitore |
Per le dichiarazioni (valore dell'array)
Segnaposto | Cosa sostituisce |
|---|---|
| Valore grezzo del controllo. Può essere sovrascritto da |
| Numero e unità di misura dai controlli numerici. Di solito vanno in coppia: |
| Direzioni dal controllo dimensioni |
| Accesso alla proprietà nominata dei controlli compositi: ad esempio, il Controllo Media restituisce un array con i campi |
| Valore di un altro controllo tramite ID. I suffissi |
| Fallback: se il controllo è vuoto, verrà sostituito |
Esempi semplici, dal colore all'immagine di sfondo
Colore da palette. Niente di più:
1 'selectors' => [ 2 '{{WRAPPER}} .elementor-svg-divider-basic-text' => 'color: {{VALUE}}', 3 ],
Controllo numerico con e senza unità. La seconda proprietà (stroke-width) è intenzionalmente senza {{UNIT}}, lo spessore del tratto è in pixel, senza px:
1 'selectors' => [ 2 '{{WRAPPER}} svg.sde-classic' => 3 'height: {{SIZE}}{{UNIT}}; stroke-width: {{SIZE}};', 4 ],
Spaziatura dal controllo dimensioni, ogni direzione separatamente:
1 'selectors' => [ 2 '{{WRAPPER}} .elementor-svg-divider-basic-button' => 3 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', 4 ],
Immagine di sfondo di una slide in un controllo ripetitore:
1 'selectors' => [ 2 '{{WRAPPER}} {{CURRENT_ITEM}} .swiper-slide-bg' => 3 'background-image: url({{URL}})', 4 ],
Posizione condizionale per RTL. Lo stesso controllo fornisce proprietà diverse a seconda della direzione del testo:
1 'selectors' => [ 2 'body:not(.rtl) {{WRAPPER}} .dialog-close-button' => 'right: {{SIZE}}{{UNIT}}', 3 'body.rtl {{WRAPPER}} .dialog-close-button' => 'left: {{SIZE}}{{UNIT}}', 4 ],
Come recuperare un valore da un altro controllo
Se due campi influenzano lo stesso CSS, non duplicare l'array, fai semplicemente riferimento al controllo adiacente:
1 'selectors' => [ 2 '{{WRAPPER}} svg.sde-classic' => 3 'stroke-dasharray: {{dash_length.SIZE}} {{whitespace_length.SIZE}};', 4 ],
Qui dash_length e whitespace_length sono ID di altri controlli nello stesso widget. Nessuna chiamata aggiuntiva, solo notazione a punto.
Versione responsive, i valori vengono recuperati in base al dispositivo. Esempio reale da 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 ],
Ogni breakpoint riceve il proprio valore columns. Le altre proprietà (border, SIZE) sono comuni, non sono legate al dispositivo.
Selectors_dictionary, switch-case per i CSS
La principale capacità sottovalutata. selectors_dictionary sostituisce {{VALUE}} con una stringa cablata, trasformando di fatto il valore del controllo in una chiave di dizionario.
Prendiamo il controllo standard Allinea con le opzioni sinistra/centro/destra. Senza un dizionario scriveresti qualcosa di innaturale:
1 'selectors' => [ 2 $sde_selector => 'margin: 0 auto; margin-{{VALUE}}: 0;', 3 ],
Per center questo produce margin: 0 auto; margin-center: 0;. La proprietà margin-center non esiste, il browser la ignora silenziosamente. Ma il codice appare disordinato.
Il dizionario fa la stessa cosa in modo pulito:
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 ],
Valore del controllo center → {{VALUE}} diventa margin: 0 auto. Tutto qui.
Limitazione importante: dopo aver attivato selectors_dictionary perdi il {{VALUE}} originale. Se lo stesso array ha un'altra coppia selettore-dichiarazione che necessita del valore originale, riceverà la stringa già sostituita. Ecco un esempio problematico:
1 'selectors' => [ 2 '{{WRAPPER}} .sde' => '{{VALUE}}', 3 '{{WRAPPER}}.elementor-sde-scale-the-cropped .sde-cropping-allow .sde' => 4 'transform-origin: {{VALUE}} 0;', 5 ],
Qui transform-origin riceverà margin: 0 auto 0; invece di center 0;. Soluzione: estrarre le dichiarazioni dipendenti in un controllo separato.
Il dizionario gestisce bene anche la traduzione di singoli valori 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 ],
E persino con interi set di dichiarazioni, una chiave → più proprietà 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 ],
Variabili CSS, calc() e controlli nascosti, comporre il puzzle
La vera potenza dei selettori si rivela in combinazione. Un controllo imposta una variabile CSS, un altro vi fa riferimento, un terzo abilita o disabilita un intero blocco di regole tramite una condizione.
Il cursore Scala% scrive una variabile:
1 'selectors' => [ 2 '{{WRAPPER}} .sde' => '--sde-scale-percentage: {{SIZE}};', 3 ],
L'interruttore "Scala ritagliata" usa questa variabile in due punti, sia per transform sia per passarla al controllo 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 ],
Controllo nascosto con condizione, lo stesso transform ma con un selettore diverso (per lo stato non ritagliato):
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 ],
E il controllo Gap usa la variabile passata con fallback:
1 'selectors' => [ 2 '{{WRAPPER}} .sde' => 3 'padding: calc({{SIZE}}{{UNIT}} / (var(--sde-scale-pct-for-gap, 100) / 100)) 0;', 4 ],
Quello che accade: il gap compensa il ridimensionamento. Se un elemento viene ridotto della metà, il gap viene moltiplicato per 2 per rimanere visivamente identico. Senza ridimensionamento (variabile non impostata) entra in gioco il fallback 100 → divisione per 1 → il gap non cambia. Puro calcolo CSS, senza una sola riga di JS.
Sovrapposizione di trasformazioni, workaround per Edge
La costruzione scale(X) scale(0.01) merita una menzione speciale. Perché non scale(calc(var(--sde-scale-percentage) / 100))? Perché Edge non supporta calc() all'interno di transform. In nessun caso.
Soluzione: la sovrapposizione. I browser applicano le funzioni di trasformazione in sequenza, una dopo l'altra. Quindi:
1 transform: scale(var(--sde-scale-percentage)) scale(0.01);
Matematicamente equivalente a scale(var(--sde-scale-percentage) * 0.01), ovvero divisione per 100. L'utente vede un familiare slider 0-100, mentre dietro le quinte il valore diventa un coefficiente 0-1.
Lo stesso principio si applica ad altre trasformazioni: rotate, translate, skew, e funziona in tutti i browser moderni, incluso Edge.
⁉️🤔 Domande frequenti
Cosa finisce esattamente nel file CSS generato?
Elementor raccoglie tutti i selettori dai controlli registrati del widget, sostituisce i valori correnti presi dalle impostazioni utente e scrive il risultato in
/wp-content/uploads/elementor/css/post-XXXX.css. Non si tratta di stili inline né di CSS dinamico generato al volo: è un file statico che viene messo in cache dal browser e rimane invariato fino alla successiva modifica delle impostazioni nell'editor. I segnaposto stessi{{VALUE}}{{SIZE}}e altri non fanno parte di WordPress né del motore di template Blade: Elementor esegue un semplicestr_replacedurante la generazione del CSS, iterando tutte le coppie selettore-dichiarazione e sostituendo i token con i valori effettivi dei controlli.
Come faccio il debug dei selettori se il CSS non viene applicato?
Apri il file CSS generato del post (il percorso è visibile nel sorgente della pagina) e verifica che la regola sia presente. Se la regola manca, cerca un errore di battitura nell'ID del controllo o un errore di sintassi nell'array
selectors. Se la regola c'è ma non funziona, verifica la specificità del selettore:{{WRAPPER}}fornisce un'alta priorità, ma i temi annidati possono sovrascrivere con!important. AttivaWP_DEBUGe controlla i log PHP: Elementor salta silenziosamente gli array errati senza mostrare errori a schermo. Usa{{WRAPPER}}SEMPRE, tranne nei casi in cui devi mirare intenzionalmente a body o html.
In cosa i selettori differiscono dal CSS personalizzato nelle impostazioni del widget?
Il CSS personalizzato (scheda Avanzate) è scritto manualmente dall'utente, sono regole statiche che non reagiscono ai cambiamenti delle impostazioni. I selettori collegano dinamicamente i controlli con il CSS: muovi lo slider,
widthcambia, cambi Allineamento,marginsi ricostruisce. L'utente non vede questo meccanismo, ottiene semplicemente un'anteprima dal vivo. Per lo sviluppatore il vantaggio principale è l'assenza del metodo_content_template(): senza selettori saresti costretto a scrivere il rendering JS dell'anteprima per ogni controllo.
Mi serve selectors_dictionary se uso già i selettori?
Sì, per un salto di qualità nella pulizia del codice. Senza dizionario elabori il valore del controllo in modo implicito, attraverso strane proprietà CSS come l'inesistente
margin-center, che il browser ignora. Con un dizionario specifichi esplicitamente: «se il valore è left, sostituisci margin-right: auto, se center, margin: 0 auto». Il codice diventa auto-documentante e, cosa più importante,{{VALUE}}non trascina più il valore originale del controllo nelle altre dichiarazioni dello stesso array.
Posso combinare i selettori con _content_template() in uno stesso widget?
Tecnicamente sì, ma in pratica questo è un segnale per riconsiderare l'architettura. Se i selettori bastano per la maggior parte dei controlli, ma un paio di campi richiedono rendering JS, estrai la logica JS in un metodo separato e richiamalo in modo mirato. Rinunciare completamente ai selettori a favore di
_content_template()significa scrivere un duplicato JS di tutta la logica PHP dei controlli, e mantenere un widget del genere diventa rapidamente un problema.
Vale la pena padroneggiare i selettori nel 2026
Elementor continua a sviluppare infrastruttura atomica, Variables Manager, contenitori Grid e Flexbox, stili globali. Ma le fondamenta della meccanica dei widget non sono cambiate dalla versione quattro: selectors e selectors_dictionary restano il modo principale per collegare un controllo all'anteprima dal vivo.
Padroneggiando questa tecnica, elimini una buona metà di tutta la logica JS in un widget tipico. Invece di gestori per ogni campo, un array selectors per controllo. Invece di posizionamenti complessi nell'anteprima, una combinazione di variabili CSS con calc() e un paio di controlli nascosti. Il plugin SVG Divider for Elementor è un esempio concreto: più della metà dei suoi controlli è gestita esclusivamente tramite selettori, senza una singola chiamata a _content_template().
La regola principale è non complicare troppo le cose. Se ti accorgi di star scrivendo un quarto calc() annidato con tre variabili, fermati. Forse è più semplice aggiungere un controllo intermedio nascosto o dividere la logica in due campi separati. E il codice sorgente di Elementor è il miglior libro di testo: il metodo add_control_rules() in core/files/css/base.php mostra come i selettori vengono elaborati internamente.



