Skip to content

Tudo para WordPress, desenvolvimento web — e não só

🎯 Seletores nos widgets do Elementor: guia completo para developers

🎯 Seletores nos widgets do Elementor: guia completo para developers

Porque é que os developers de Elementor precisam de seletores e como funcionam

Quando um utilizador ajusta as definições de um widget no editor, espera uma resposta instantânea no ecrã. Sem seletores, um developer teria de escrever um handler JS para cada alteração de campo. Com seletores, tudo se resolve com CSS.

O parâmetro selectors (e o seu equivalente menos conhecido selectors_dictionary) é incorporado diretamente no array da chamada add_control(). O Elementor substitui dinamicamente os valores dos campos nas regras CSS e escreve-os no ficheiro do post, algo como /wp-content/uploads/elementor/css/post-1234.css. Assim que o utilizador sai do editor, os estilos inline desaparecem, deixando CSS gerado limpo.

💡 Visão geral rápida:

  • Compreender a sintaxe dos seletores e a tabela de placeholders
  • Ver exemplos práticos para cor e tamanhos
  • Aprender a obter valores de controlos vizinhos
  • Dominar o selectors_dictionary para substituição de declarações CSS
  • Montar o puzzle das variáveis CSS e controlos ocultos

Onde os seletores são definidos

Quando cria um widget, cada chamada add_control() aceita um array de definições. É exatamente aí que os selectors residem. Para controlos de grupo, a sintaxe é a mesma, o array é passado dentro do registo do grupo.

Formato básico:

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

A chave é um seletor CSS (começa com {{WRAPPER}} para evitar afetar widgets vizinhos na página). O valor é uma ou mais declarações CSS com placeholders dinâmicos. O Elementor obtém o valor atual do controlo e substitui-o no lugar do placeholder.

O resultado é renderizado no ficheiro CSS externo do post, os estilos existem apenas enquanto o editor está aberto e imediatamente após guardar. Sem confusão de inline.

Tabela de variáveis entre chavetas

Não há magia, apenas localizar e substituir. Mas a variedade de placeholders abre portas a construções bastante engenhosas.

Para seletores (chave do array)

Placeholder

O que substitui

{{WRAPPER}}

Seletor único da instância do widget, por exemplo .elementor-50 .elementor-element.elementor-element-092e113. Usar quase sempre

{{ID}}

Apenas o ID do widget (a parte depois do hífen, 092e113)

(desktop) / (tablet) / (mobile)

Restringe a regra ao dispositivo especificado. Com + significa "desta resolução para cima": (tablet+) = tablet e superior

{{CURRENT_ITEM}}

Elemento ativo de um controlo repetidor

Para declarações (valor do array)

Placeholder

O que substitui

{{VALUE}}

Valor bruto do controlo. Pode ser substituído por selectors_dictionary

{{SIZE}} e {{UNIT}}

Número e unidade de medida de controlos numéricos. Normalmente usados em pares: {{SIZE}}{{UNIT}}

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

Direções do controlo de dimensões

{{URL}} ou outro nome

Acesso a propriedade nomeada de controlos compostos: por exemplo, o Media Control devolve um array com os campos url id alt

{{other.SIZE}}

Valor de outro controlo pelo ID. Os sufixos _tablet e _mobile fornecem dados responsivos

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

Fallback: se o controlo estiver vazio, 5 será substituído. Funciona com strings entre aspas e com DEFAULT de outro controlo

Exemplos simples, da cor à imagem de fundo

Cor da paleta. Nada de extra:

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

Controlo numérico com e sem unidade. A segunda propriedade (stroke-width) está intencionalmente sem {{UNIT}}, a espessura do traço é em píxeis, sem px:

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

Espaçamento do controlo de dimensões, cada direção separadamente:

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

Imagem de fundo de um slide num controlo repetidor:

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

Posição condicional para RTL. O mesmo controlo fornece propriedades diferentes dependendo da direção do texto:

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

Como obter um valor de outro controlo

Se dois campos afetam o mesmo CSS, não duplique o array, basta referenciar o controlo vizinho:

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

Aqui dash_length e whitespace_length são IDs de outros controlos no mesmo widget. Sem chamadas adicionais, apenas notação de ponto.

Versão responsiva, os valores são obtidos tendo em conta o dispositivo. Exemplo real do 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],

Cada breakpoint recebe o seu próprio valor de columns. As restantes propriedades (border, SIZE) são comuns, não estão vinculadas ao dispositivo.

Selectors_dictionary, switch-case para CSS

A principal capacidade subestimada. O selectors_dictionary substitui {{VALUE}} por uma string fixa, transformando essencialmente o valor do controlo numa chave de dicionário.

Veja o controlo de Alinhamento padrão com opções esquerda/centro/direita. Sem um dicionário, escreveria algo pouco natural:

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

Para center, isto produz margin: 0 auto; margin-center: 0;. A propriedade margin-center não existe, o navegador ignora-a silenciosamente. Mas fica confuso.

O dicionário faz o mesmo de forma limpa:

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

Valor do controlo center{{VALUE}} torna-se margin: 0 auto. Só isso.

Limitação importante: após ativar o selectors_dictionary, perde o {{VALUE}} original. Se o mesmo array tiver outro par seletor-declaração que precise do valor original, receberá a string já substituída. Eis um exemplo problemático:

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

Aqui, transform-origin receberá margin: 0 auto 0; em vez de center 0;. Solução: extrair as declarações dependentes para um controlo separado.

O dicionário também lida bem com a tradução de valores CSS individuais:

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 até com conjuntos completos de declarações, uma chave → várias propriedades 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],

Variáveis CSS, calc() e controlos ocultos, a montagem do puzzle

O verdadeiro poder dos seletores revela-se em combinação. Um controlo define uma variável CSS, outro referencia-a, um terceiro ativa/desativa um bloco inteiro de regras através de uma condição.

O slider de Escala% escreve uma variável:

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

O toggle "Escala cortada" usa esta variável em dois locais, tanto para o transform como para passar ao controlo de Intervalo:

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

Controlo oculto com condição, o mesmo transform mas com um seletor diferente (para o estado não cortado):

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 o controlo de Intervalo usa a variável passada com fallback:

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

O que está a acontecer aqui: o Gap compensa a escala. Se um elemento for reduzido a metade, o gap é multiplicado por 2 para permanecer visualmente igual. Sem redução (variável não definida), o fallback 100 entra em ação → divisão por 1 → o gap não se altera. Matemática CSS pura, sem uma única linha de JS.

Empilhamento de transformações, solução para o Edge

A construção scale(X) scale(0.01) merece uma menção especial. Porque não scale(calc(var(--sde-scale-percentage) / 100))? Porque o Edge não suporta calc() dentro de transform. De todo.

Solução: empilhamento. Os navegadores aplicam as funções de transformação sequencialmente, uma após a outra. Portanto:

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

Matematicamente equivalente a scale(var(--sde-scale-percentage) * 0.01), por outras palavras, divisão por 100. O utilizador obtém um slider familiar de 0 a 100, enquanto nos bastidores o valor se transforma num coeficiente de 0 a 1.

O mesmo princípio aplica-se a outras transformações: rotate, translate, skew, e funciona em todos os navegadores modernos, incluindo o Edge.

⁉️🤔 Perguntas frequentes

O que vai exatamente para o ficheiro CSS gerado?

O Elementor recolhe todos os seletores dos controlos de widgets registados, substitui os valores atuais das definições do utilizador e escreve o resultado em /wp-content/uploads/elementor/css/post-XXXX.css. Não são estilos inline nem CSS dinâmico gerado em tempo real: é um ficheiro estático que fica em cache no navegador e permanece até à próxima alteração de definições no editor. Os próprios placeholders {{VALUE}} {{SIZE}} e outros não fazem parte do WordPress nem do motor de templates Blade: o Elementor faz um str_replace normal durante a geração do CSS, percorrendo todos os pares seletor-declaração e substituindo os tokens pelos valores reais dos controlos.

Como faço debug aos seletores se o CSS não estiver a ser aplicado?

Abra o ficheiro CSS gerado do post (o caminho está visível no código fonte da página) e verifique se a regra está lá. Se a regra estiver em falta, procure um erro de digitação no ID do controlo ou um erro de sintaxe no array selectors. Se a regra estiver lá mas não funcionar, verifique a especificidade do seletor: {{WRAPPER}} fornece alta prioridade, mas temas com aninhamento podem sobrepor-se através de !important. Ative o WP_DEBUG e monitorize os logs do PHP: o Elementor ignora silenciosamente arrays incorretos sem mostrar erros no ecrã. Use {{WRAPPER}} SEMPRE, exceto em casos de segmentação intencional do body ou html.

Em que é que os seletores diferem do CSS personalizado nas definições do widget?

O CSS personalizado (separador Avançado) é escrito manualmente pelo utilizador, são regras estáticas que não reagem a alterações de definições. Os seletores ligam dinamicamente os controlos ao CSS: move o slider, a width muda, alterna o Alinhamento, a margin é reconstruída. O utilizador não vê este mecanismo, apenas obtém uma pré-visualização ao vivo. Para o programador, a principal vantagem é a ausência do método _content_template(): sem seletores, teria de escrever a renderização da pré-visualização em JS para cada controlo.

Preciso do selectors_dictionary se já uso seletores?

Sim, para um salto qualitativo na limpeza do código. Sem um dicionário, processa o valor do controlo implicitamente, através de propriedades CSS estranhas como a inexistente margin-center, que o navegador ignora. Com um dicionário, especifica explicitamente: «se o valor for left, substitui margin-right: auto, se for center, margin: 0 auto». O código torna-se autodocumentado e, mais importante, o {{VALUE}} deixa de arrastar o valor original do controlo para outras declarações do mesmo array.

Posso combinar seletores com _content_template() num só widget?

Tecnicamente sim, mas na prática isto é um sinal para reconsiderar a arquitetura. Se os seletores forem suficientes para a maioria dos controlos, mas alguns campos exigirem renderização JS, extraia a lógica JS para um método separado e invoque-o de forma precisa. A rejeição total dos seletores em favor do _content_template() significa que está a escrever um duplicado em JS de toda a lógica de controlo PHP, e a manutenção de um widget assim torna-se rapidamente um problema.

Vale a pena dominar os seletores em 2026

O Elementor continua a desenvolver infraestrutura atómica, Gestor de Variáveis, contentores Grid e Flexbox, estilos globais. Mas a base da mecânica dos widgets não mudou desde a versão quatro: selectors e selectors_dictionary continuam a ser a principal forma de ligar um controlo à pré-visualização ao vivo.

Ao dominar esta técnica, elimina uma boa metade de toda a lógica JS num widget típico. Em vez de handlers para cada campo, um array selectors por controlo. Em vez de posicionamento complexo na pré-visualização, uma combinação de variáveis CSS com calc() e alguns controlos ocultos. O plugin SVG Divider for Elementor é um exemplo vivo: mais de metade dos seus controlos são geridos exclusivamente através de seletores, sem uma única chamada a _content_template().

A regra principal é não complicar demasiado. Se der por si a escrever um quarto calc() aninhado com três variáveis, pare. Talvez seja mais simples adicionar um controlo intermediário oculto ou dividir a lógica em dois campos separados. E o código fonte do Elementor é o melhor manual: o método add_control_rules() em core/files/css/base.php mostra como os seletores são processados internamente.