Skip to content

Alles für WordPress, Webentwicklung — und mehr

🎯 Selektoren in Elementor-Widgets: vollständiger Leitfaden für Entwickler

🎯 Selektoren in Elementor-Widgets: vollständiger Leitfaden für Entwickler

Warum Elementor-Entwickler Selektoren benötigen und wie sie funktionieren

Wenn ein Nutzer im Editor Widget-Einstellungen anpasst, erwartet er eine sofortige Reaktion auf dem Bildschirm. Ohne Selektoren müsste ein Entwickler für jede Feldänderung einen JS-Handler schreiben. Mit Selektoren wird alles über CSS gelöst.

Der Parameter selectors (und sein weniger bekannter Gegenpart selectors_dictionary) wird direkt im Array des add_control()-Aufrufs eingebettet. Elementor ersetzt dynamisch Werte aus Feldern in CSS-Regeln und gibt sie in die Post-Datei aus, etwa /wp-content/uploads/elementor/css/post-1234.css. Sobald der Nutzer den Editor verlässt, verschwinden die Inline-Styles und sauber generiertes CSS bleibt zurück.

💡 Kurzer Überblick:

  • Selektoren-Syntax und die Platzhalter-Tabelle verstehen
  • Live-Beispiele für Farbe und Größen sehen
  • Lernen, Werte aus benachbarten Controls zu beziehen
  • selectors_dictionary für die Substitution von CSS-Deklarationen beherrschen
  • Das Puzzle aus CSS-Variablen und versteckten Controls zusammensetzen

Wo Selektoren definiert werden

Wenn Sie ein Widget erstellen, akzeptiert jeder add_control()-Aufruf ein Einstellungs-Array. Genau dort leben die selectors. Bei Gruppen-Controls ist die Syntax identisch, das Array wird innerhalb der Gruppenregistrierung übergeben.

Grundformat:

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

Der Schlüssel ist ein CSS-Selektor (beginnt mit {{WRAPPER}}, um benachbarte Widgets auf der Seite nicht zu beeinflussen). Der Wert besteht aus einer oder mehreren CSS-Deklarationen mit dynamischen Platzhaltern. Elementor nimmt den aktuellen Control-Wert und setzt ihn anstelle des Platzhalters ein.

Das Ergebnis wird in die externe CSS-Datei des Posts geschrieben, die Styles existieren nur, solange der Editor geöffnet ist und unmittelbar nach dem Speichern. Kein Inline-Chaos.

Variablentabelle mit geschweiften Klammern

Keine Magie, nur Suchen und Ersetzen. Aber die Vielfalt der Platzhalter öffnet Türen zu recht raffinierten Konstruktionen.

Für Selektoren (Array-Schlüssel)

Platzhalter

Was er ersetzt

{{WRAPPER}}

Eindeutiger Widget-Instanz-Selektor, zum Beispiel .elementor-50 .elementor-element.elementor-element-092e113. Fast immer verwenden

{{ID}}

Nur die Widget-ID (der Teil nach dem Bindestrich, 092e113)

(desktop) / (tablet) / (mobile)

Beschränkt die Regel auf das angegebene Gerät. Mit + bedeutet „ab dieser Auflösung aufwärts": (tablet+) = Tablet und breiter

{{CURRENT_ITEM}}

Aktives Element eines Repeater-Controls

Für Deklarationen (Array-Wert)

Platzhalter

Was er ersetzt

{{VALUE}}

Roher Control-Wert. Kann durch selectors_dictionary überschrieben werden

{{SIZE}} und {{UNIT}}

Zahl und Maßeinheit aus numerischen Controls. Treten üblicherweise paarweise auf: {{SIZE}}{{UNIT}}

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

Richtungen aus dem Dimensions-Control

{{URL}} oder anderer Name

Zugriff auf benannte Eigenschaften zusammengesetzter Controls: Das Media Control liefert beispielsweise ein Array mit den Feldern url id alt

{{other.SIZE}}

Wert eines anderen Controls anhand der ID. Suffixe _tablet und _mobile liefern responsive Daten

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

Fallback: Ist das Control leer, wird 5 eingesetzt. Funktioniert mit Strings in Anführungszeichen und mit dem DEFAULT eines anderen Controls

Einfache Beispiele, von Farbe bis Hintergrundbild

Farbe aus der Palette. Nichts weiter:

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

Numerisches Control mit und ohne Einheit. Die zweite Eigenschaft (stroke-width) steht bewusst ohne {{UNIT}}, die Strichstärke ist in Pixeln, ohne px:

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

Abstände aus dem Dimensions-Control, jede Richtung separat:

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

Hintergrundbild eines Slides im Repeater-Control:

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

Bedingte Positionierung für RTL. Dasselbe Control liefert je nach Textrichtung unterschiedliche Eigenschaften:

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

Wie man einen Wert aus einem anderen Control bezieht

Wenn zwei Felder dasselbe CSS beeinflussen, duplizieren Sie nicht das Array, sondern referenzieren Sie einfach das benachbarte Control:

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

Hier sind dash_length und whitespace_length IDs anderer Controls im selben Widget. Keine zusätzlichen Aufrufe, nur Punktnotation.

Responsive Version, Werte werden geräteabhängig abgerufen. Praxisbeispiel aus 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],

Jeder Breakpoint erhält seinen eigenen columns-Wert. Die übrigen Eigenschaften (border, SIZE) sind gemeinsam, sie sind nicht an das Gerät gebunden.

Selectors_dictionary, switch-case für CSS

Die am meisten unterschätzte Fähigkeit. selectors_dictionary ersetzt {{VALUE}} durch eine fest codierte Zeichenkette und macht den Steuerungswert damit praktisch zu einem Dictionary-Schlüssel.

Nehmen Sie das standardmäßige Align-Control mit den Optionen left/center/right. Ohne Dictionary würden Sie etwas Unnatürliches schreiben:

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

Für center erzeugt das margin: 0 auto; margin-center: 0;. Die Eigenschaft margin-center existiert nicht, der Browser ignoriert sie stillschweigend. Aber es sieht unsauber aus.

Das Dictionary erledigt dasselbe auf saubere Weise:

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

Control-Wert center{{VALUE}} wird zu margin: 0 auto. Das ist alles.

Wichtige Einschränkung: Nach der Aktivierung von selectors_dictionary verlieren Sie das ursprüngliche {{VALUE}}. Enthält dasselbe Array ein weiteres Selektor-Deklaration-Paar, das den ursprünglichen Wert benötigt, erhält es die bereits ersetzte Zeichenkette. Hier ein problematisches Beispiel:

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

Hier erhält transform-origin den Wert margin: 0 auto 0; anstelle von center 0;. Lösung: Lagern Sie abhängige Deklarationen in ein separates Control aus.

Das Dictionary bewältigt auch die Übersetzung einzelner CSS-Werte gut:

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

Und sogar mit ganzen Deklarationssätzen, ein Schlüssel → mehrere CSS-Eigenschaften:

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-Variablen, calc() und versteckte Controls, das Puzzle zusammensetzen

Die wahre Stärke der Selektoren zeigt sich in der Kombination. Ein Control setzt eine CSS-Variable, ein anderes referenziert sie, ein drittes aktiviert oder deaktiviert einen ganzen Regelblock über eine Bedingung.

Der Scale%-Schieberegler schreibt eine Variable:

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

Der Toggle „Scale cropped" verwendet diese Variable an zwei Stellen, sowohl für transform als auch zur Weitergabe an das Gap-Control:

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

Verstecktes Control mit Bedingung, dasselbe transform, aber mit einem anderen Selektor (für den unbeschnittenen Zustand):

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

Und das Gap-Control verwendet die übergebene Variable mit Fallback:

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

Was hier geschieht: Gap kompensiert die Skalierung. Wird ein Element auf die Hälfte verkleinert, wird der Abstand mit 2 multipliziert, um optisch gleich zu bleiben. Ohne Verkleinerung (Variable nicht gesetzt) greift der Fallback-Wert 100 → Division durch 1 → Abstand ändert sich nicht. Reine CSS-Mathematik, ohne eine einzige Zeile JS.

Transform-Stacking, Workaround für Edge

Die Konstruktion scale(X) scale(0.01) verdient besondere Erwähnung. Warum nicht scale(calc(var(--sde-scale-percentage) / 100))? Weil Edge calc() innerhalb von transform nicht unterstützt. Überhaupt nicht.

Die Lösung: Stacking. Browser wenden Transform-Funktionen sequenziell an, eine nach der anderen. Daher:

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

Mathematisch äquivalent zu scale(var(--sde-scale-percentage) * 0.01), also einer Division durch 100. Der Nutzer erhält einen vertrauten Schieberegler von 0 bis 100, während der Wert im Hintergrund in einen Koeffizienten von 0 bis 1 umgewandelt wird.

Dasselbe Prinzip gilt für andere Transformationen: Rotate, Translate, Skew, und funktioniert in allen modernen Browsern, einschließlich Edge.

⁉️🤔 Häufig gestellte Fragen

Was genau landet in der generierten CSS-Datei?

Elementor sammelt alle Selektoren aus den registrierten Widget-Controls, setzt die aktuellen Werte aus den Benutzereinstellungen ein und schreibt das Ergebnis nach /wp-content/uploads/elementor/css/post-XXXX.css. Das sind keine Inline-Styles und kein dynamisches CSS zur Laufzeit: Es handelt sich um eine statische Datei, die vom Browser gecacht wird und bis zur nächsten Einstellungsänderung im Editor bestehen bleibt. Die Platzhalter selbst {{VALUE}} {{SIZE}} und andere sind weder Teil von WordPress noch der Blade-Template-Engine: Elementor führt während der CSS-Generierung ein reguläres str_replace durch, iteriert über alle Selektor-Deklarations-Paare und ersetzt die Token durch die tatsächlichen Control-Werte.

Wie debugge ich Selektoren, wenn das CSS nicht greift?

Öffnen Sie die generierte CSS-Datei des Beitrags (der Pfad ist im Seitenquelltext sichtbar) und prüfen Sie, ob die Regel vorhanden ist. Fehlt die Regel, suchen Sie nach einem Tippfehler in der Control-ID oder einem Syntaxfehler im selectors-Array. Ist die Regel vorhanden, funktioniert aber nicht, prüfen Sie die Selektorenspezifität: {{WRAPPER}} sorgt für hohe Priorität, aber verschachtelte Themes können durch !important überschreiben. Aktivieren Sie WP_DEBUG und beobachten Sie die PHP-Logs: Elementor überspringt fehlerhafte Arrays stillschweigend, ohne Fehler auf dem Bildschirm anzuzeigen. Verwenden Sie {{WRAPPER}} IMMER, außer in Fällen, in denen Sie gezielt body oder html ansprechen möchten.

Wie unterscheiden sich Selektoren von benutzerdefiniertem CSS in den Widget-Einstellungen?

Benutzerdefiniertes CSS (Tab „Erweitert") wird manuell vom Benutzer geschrieben, das sind statische Regeln, die nicht auf Einstellungsänderungen reagieren. Selektoren verknüpfen Controls dynamisch mit CSS: Sie bewegen den Schieberegler, width ändert sich; Sie schalten die Ausrichtung um, margin wird neu aufgebaut. Der Benutzer sieht diesen Mechanismus nicht, er erhält lediglich eine Live-Vorschau. Für den Entwickler liegt der Hauptgewinn im Wegfall der _content_template()-Methode: Ohne Selektoren müssten Sie für jedes Control ein JS-Vorschau-Rendering schreiben.

Brauche ich selectors_dictionary, wenn ich bereits selectors verwende?

Ja, für einen qualitativen Sprung bei der Code-Sauberkeit. Ohne Dictionary verarbeiten Sie den Control-Wert implizit, über seltsame CSS-Eigenschaften wie das nicht existierende margin-center, das der Browser ignoriert. Mit einem Dictionary legen Sie explizit fest: „Wenn der Wert left ist, setze margin-right: auto, wenn center, dann margin: 0 auto". Der Code wird selbstdokumentierend, und vor allem schleppt {{VALUE}} den ursprünglichen Control-Wert nicht mehr in andere Deklarationen desselben Arrays.

Kann ich selectors und _content_template() in einem Widget kombinieren?

Technisch ja, aber in der Praxis ist das ein Signal, die Architektur zu überdenken. Wenn Selektoren für die meisten Controls ausreichen, aber ein paar Felder JS-Rendering erfordern, extrahieren Sie die JS-Logik in eine separate Methode und rufen Sie diese gezielt auf. Der vollständige Verzicht auf Selektoren zugunsten von _content_template() bedeutet, dass Sie ein JS-Duplikat der gesamten PHP-Control-Logik schreiben, die Wartung eines solchen Widgets wird schnell zum Problem.

Lohnt es sich, Selektoren im Jahr 2026 zu beherrschen

Elementor entwickelt die atomare Infrastruktur, den Variables Manager, Grid- und Flexbox-Container sowie globale Stile kontinuierlich weiter. Aber das Fundament der Widget-Mechanik hat sich seit Version 4 nicht geändert: selectors und selectors_dictionary bleiben der primäre Weg, um ein Control mit der Live-Vorschau zu verknüpfen.

Wenn Sie diese Technik beherrschen, eliminieren Sie gut die Hälfte der gesamten JS-Logik in einem typischen Widget. Statt Handler für jedes Feld ein selectors-Array pro Control. Statt komplexer Positionierung in der Vorschau eine Kombination aus CSS-Variablen mit calc() und ein paar versteckten Controls. Das Plugin SVG Divider for Elementor ist ein lebendes Beispiel: Mehr als die Hälfte seiner Controls wird ausschließlich über Selektoren gesteuert, ohne einen einzigen _content_template()-Aufruf.

Die wichtigste Regel lautet: Überkomplizieren Sie nicht. Wenn Sie sich dabei ertappen, wie Sie ein viertes verschachteltes calc() mit drei Variablen schreiben, halten Sie inne. Vielleicht ist es einfacher, ein verstecktes Zwischen-Control hinzuzufügen oder die Logik auf zwei separate Felder aufzuteilen. Und der Elementor-Quellcode ist das beste Lehrbuch: Die Methode add_control_rules() in core/files/css/base.php zeigt, wie Selektoren intern verarbeitet werden.