agentsclimarketplace

Elementor experiments and markup

Skill Lonsdale201/wp-agent-skills/elementor/elementor-experiments-and-markup

Design Elementor addons / widgets / dynamic tags to survive Elementor's two markup-changing STABLE "Performance" experiments — both default-ON on new installs, so never assume they are off; detect at runtime with Plugin::$instance->experiments->is_feature_active('name'). Optimized Markup (e_optimized_markup) drops the .elementor-widget-container inner wrapper — the contract is Element_Base::has_widget_inner_wrapper() (default true); your widget keeps its wrapper unless you override it, but CSS / JS targeting CORE widgets' wrapper breaks when it is on. Inline Font Icons (e_font_icon_svg) renders icons as inline SVG and does NOT load Font Awesome CSS — always render icons via Icons_Manager::render_icon(), never hardcode an i-tag; it looks fine in the editor but breaks on the frontend. Use when building / reviewing a widget or addon that renders icons or reads the DOM. Triggers on is_feature_active, e_optimized_markup, e_font_icon_svg, has_widget_inner_wrapper, Icons_Manager render_icon, Optimized Markup.From its SKILL.md

Install
npx -y skills add Lonsdale201/wp-agent-skills --skill elementor-experiments-and-markup

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

13.7 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it

Elementor: experiments and markup-changing features

For developers shipping a companion plugin / theme that adds widgets, dynamic tags, or frontend CSS/JS on top of Elementor. Elementor ships "experiments" (feature flags) under core/experiments/manager.php. Most are cosmetic, but two Performance experiments change the rendered HTML and are default-ON on new installs, so a modern addon must handle them or it silently breaks on a large share of sites.

Read this first — you cannot assume the state

Experiment state is per-site and three-valued (Default / Active / Inactive), and the effective default differs between fresh and upgraded installs. Never hardcode an assumption; detect at runtime:

if ( \Elementor\Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ) ) {
    // adapt markup
}

Verified: Experiments\Manager::is_feature_active( $name, $check_dependencies = false ) at core/experiments/manager.php:257; actual state resolves to the user's explicit state unless it's Default, then the feature's default (manager.php get_feature_actual_state).

The two markup-changing experiments (both release_status => STABLE, tag "Performance", new_site.default_active => true) — verified manager.php:312-381:

ExperimentnameDefault on NEW install (≥ ver)What it changes
Inline Font Iconse_font_icon_svgON (≥ 3.17.0)Icons render as inline <svg>; Font Awesome + eicons CSS/fonts NOT loaded on frontend
Optimized Markupe_optimized_markupON (≥ 3.30.0)Removes inner wrapper HTML (.elementor-widget-container) from widgets to shrink the DOM

new_site.default_active => true means a site first installed at/after that version defaults the experiment ON. Sites upgraded from older versions may keep it OFF (e_optimized_markup / container have default => STATE_INACTIVE). So the SAME addon meets both states in the wild — which is exactly why you detect at runtime.

Misconception this skill corrects

"My widget's icons render fine in the Elementor editor, so my icon markup is correct."

The editor/preview is misleading. Icons_Manager only switches to inline SVG when not in edit/preview mode — verified includes/managers/icons.php:186 (is_font_icon_inline_svg() && ! is_edit_mode() && ! is_preview_mode()). So with Inline Font Icons ON, the editor still loads the icon fonts and hardcoded <i class="fas fa-star"> looks correct there — but on the frontend the Font Awesome CSS is absent and your hardcoded icon renders as an empty/broken glyph. Always test on the published frontend, and always emit icons through Icons_Manager::render_icon().

When to use this skill

  • Building or reviewing a custom widget (its render() markup, wrapper structure, or icon output).
  • Shipping frontend CSS/JS that targets Elementor's DOM (.elementor-widget-container, widget inner structure).
  • Any addon that renders icons chosen via the ICONS control.
  • Diagnosing "works on my site, breaks on the client's" icon/layout bugs — usually an experiment default difference.
  • The diff references is_feature_active, has_widget_inner_wrapper, Icons_Manager, e_optimized_markup, or e_font_icon_svg.

Optimized Markup (e_optimized_markup)

The wrapper contract lives on the element base. Element_Base::has_widget_inner_wrapper() returns true by default — verified includes/base/element-base.php:1588. Widget_Base::render() prints the <div class="elementor-widget-container"> only when that method returns true (includes/base/widget-base.php:427), and the widget's frontend script-dependency group flips commoncommon-optimized based on it (widget-base.php:201).

Core widgets opt IN to the DOM reduction by overriding it — the canonical one-liner, e.g. includes/widgets/button.php:85 and Pro's modules/forms/widgets/form.php:45:

public function has_widget_inner_wrapper(): bool {
    return ! \Elementor\Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
}

What this means for YOUR addon:

  • Your widget does NOT break by default. Since the base returns true, a widget that doesn't override the method keeps its .elementor-widget-container even when Optimized Markup is on. No action needed for correctness.
  • Your CSS/JS targeting CORE (and Pro) widgets DOES break when Optimized Markup is on, because those widgets drop the wrapper. A selector like .elementor-widget-heading .elementor-widget-container h2 {} stops matching; el.querySelector('.elementor-widget-container') returns null. Target the widget wrapper (.elementor-widget-{name} / .elementor-element) or the content element directly, not the inner container.
  • To let your widget participate in the DOM reduction, override has_widget_inner_wrapper() with the same one-liner — then make sure your own styles/scripts don't depend on .elementor-widget-container, and register a -optimized-aware style if needed.

Inline Font Icons (e_font_icon_svg)

When active on the frontend, Icons_Manager::render_font_icon() resolves the icon to an inline <svg> via the SVG data manager and returns it; otherwise it falls back to <i class="{icon value}"> which needs the font CSS — verified includes/managers/icons.php:309-341. The single rule:

// RIGHT — works in BOTH modes; emits <svg> when the experiment is on, <i> otherwise
\Elementor\Icons_Manager::render_icon(
    $settings['my_icon'],           // the ICONS control value: [ 'value' => ..., 'library' => ... ]
    [ 'aria-hidden' => 'true' ],
    'i'                             // wrapping tag; ignored for uploaded-SVG
);
  • render_icon() (icons.php:354) → get_icon_html() (icons.php:67) handles uploaded SVGs (library === 'svg') AND font icons transparently. Use it for every icon your widget outputs.
  • Do NOT hardcode <i class="fas fa-..."> or manually enqueue Font Awesome expecting it to be present — with the experiment on, Elementor deliberately does not load FA/eicons CSS on the frontend, so hardcoded markup renders broken.
  • If you genuinely need the font CSS (rare, legacy markup you can't convert), Icons_Manager::enqueue_shim() exists, but the right fix is to route through render_icon().
  • This experiment sets generator_tag => true, so its state is also observable in the <meta name="generator"> Elementor emits — handy for support triage.

Registering your OWN experiment (optional)

Addons can register experiments on the same system via the elementor/experiments/default-features-registered action — verified core/experiments/manager.php:433. Allowed add_feature() option keys (manager.php:1049): name, title, tag, tags, description, release_status, default, mutable, hidden, new_site, on_state_change, dependencies, generator_tag, messages, deprecated.

add_action( 'elementor/experiments/default-features-registered', function ( $experiments ): void {
    $experiments->add_feature( [
        'name'           => 'myplugin_new_renderer',
        'title'          => esc_html__( 'My Plugin: New Renderer', 'myplugin' ),
        'release_status' => \Elementor\Core\Experiments\Manager::RELEASE_STATUS_BETA,
        'default'        => \Elementor\Core\Experiments\Manager::STATE_INACTIVE,
    ] );
} );

// then gate behavior:
if ( \Elementor\Plugin::$instance->experiments->is_feature_active( 'myplugin_new_renderer' ) ) { /* ... */ }

Critical rules

  • Detect, never assume. Plugin::$instance->experiments->is_feature_active( $name ) — the effective default differs between fresh and upgraded installs.
  • Both markup experiments are STABLE and default-ON on new installs — treat "on" as the common case, not an edge case.
  • Icons: always Icons_Manager::render_icon(). Never hardcode <i class="fa">; the editor hides the breakage because it still loads fonts in edit/preview mode.
  • Optimized Markup: your widget keeps its wrapper by default (base returns true), so it won't break — but your CSS/JS against core/Pro widgets' .elementor-widget-container will. Don't target the inner container.
  • To opt your widget into DOM reduction, override has_widget_inner_wrapper() with the core one-liner, and make your own styles wrapper-independent.
  • Don't fight the experiment by force-enqueuing Font Awesome or re-adding wrappers globally — you reintroduce the DOM/asset cost the site owner opted out of and cause the exact third-party conflicts the experiment warns about.

Common mistakes

// WRONG — hardcoded font icon; broken on frontend when e_font_icon_svg is on
echo '<i class="fas fa-star"></i>';

// RIGHT — routes through the manager, SVG or font as appropriate
\Elementor\Icons_Manager::render_icon( $settings['star_icon'], [ 'aria-hidden' => 'true' ] );

// WRONG — CSS assumes the inner container always exists
// .my-addon .elementor-widget-button .elementor-widget-container { gap: 8px; }
//   → no match when Optimized Markup drops the wrapper on the core Button widget

// RIGHT — target the widget/element wrapper or the content node
// .my-addon .elementor-widget-button .elementor-button { gap: 8px; }

// WRONG — JS that expects the wrapper on a core widget
// const c = widgetEl.querySelector('.elementor-widget-container'); c.dataset.x = 1; // c is null

// RIGHT — guard / target a stable node
// const c = widgetEl.querySelector('.elementor-widget-container') || widgetEl;

// WRONG — assuming your custom widget lost its wrapper (it didn't; base returns true)
// ...adding a second wrapper "to be safe" → double nesting

// RIGHT — override only if you WANT the reduction, mirroring core
public function has_widget_inner_wrapper(): bool {
    return ! \Elementor\Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
}

Cross-references

  • Run elementor-dynamic-tag-register / elementor-dynamic-tag-fields when the work is registering a dynamic tag; if the tag outputs an icon, the Inline Font Icons rule here applies.
  • Run elementor-dynamic-tag-ajax-select for large-dataset controls (unrelated to markup, but same addon surface).
  • Run elementor-deprecations when a markup/API change you rely on may be deprecated — the has_widget_inner_wrapper / Icons_Manager APIs are current, but audit before bumping Elementor majors.

What this skill does NOT cover

  • Atomic Widgets / Editor V4 (e_atomic_elements, Pro AtomicWidgetsModule::EXPERIMENT_NAME, e_pro_atomic_form, collection-loop) — a much larger architectural shift (new widget base, styles engine, schema) that deserves its own skill. This skill is only the two STABLE markup-changing performance experiments plus the experiments mechanics.
  • Flexbox/Grid Container (container) beyond noting it's still an experiment — layout authoring, not addon-markup contract.
  • The editor-JS side of experiments (React panels, elementorCommon.config.experimentalFeatures).
  • Elementor's own internal widget CSS — you adapt to it, you don't edit it.

References

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,851. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.