{"slug":"wordpress-elementor-dev","title":"wordpress-elementor-dev","summary":"Expert WordPress and Elementor Pro development skill. Use this whenever the user asks about WordPress theme development, Elementor Pro customization, plugin architecture, custom post types, hooks/filters, Loop Grids, Dynamic Tags, Theme Builder templates, off-canvas UIs, custom C","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-14T20:43:46.930361Z","repo":{"url":"https://github.com/guramzhgamadze/WordPress-Elementor-Skill","stars":25,"forks":1,"license":"MIT","updatedAt":"2026-09-14T06:48:52Z"},"bodyHtml":"<hr>\n<h2>name: wordpress-elementor-dev\ndescription: &gt;\nExpert WordPress and Elementor Pro development skill. Use this whenever the user\nasks about WordPress theme development, Elementor Pro customization, plugin architecture,\ncustom post types, hooks/filters, Loop Grids, Dynamic Tags, Theme Builder templates,\noff-canvas UIs, custom CSS/JS injection, performance optimization, or any PHP/JS/CSS\ncode targeting a WordPress or Elementor environment. Trigger even for general questions\nlike \"how do I add a custom field in Elementor\", \"what hook should I use for X in\nWordPress\", \"how do I override a WooCommerce template\", or \"how do I register a\ncustom widget\" — do not wait for the user to explicitly say \"use the skill.\"\nAlso trigger for ACF integration, REST API endpoints, child theme setup, WooCommerce\ncustomization, WPML/multilingual setups, and any debugging of WordPress/Elementor issues.</h2>\n<h1>WordPress &amp; Elementor Pro — Skill Router</h1>\n<p>A complete procedural guide for producing production-grade WordPress and Elementor Pro\ncode. This file is the <strong>router</strong> — read it first, then load the relevant sub-file(s)\nfor the task at hand.</p>\n<hr>\n<h2>Sub-file Map — Read the Right File for Each Task</h2>\n<table>\n<thead>\n<tr>\n<th>Task type</th>\n<th>Read this file</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Plugin scaffold, child theme, code placement, CPT, AJAX handler</td>\n<td><strong>scaffolding.md</strong></td>\n</tr>\n<tr>\n<td>PHP standards, sanitization, escaping, nonces, WP_Error, transients, password hashing</td>\n<td><strong>php-standards.md</strong></td>\n</tr>\n<tr>\n<td>JavaScript standards, enqueue API, defer/async, wp_add_inline_script</td>\n<td><strong>js-css-standards.md</strong></td>\n</tr>\n<tr>\n<td>CSS standards, BEM, design tokens, Elementor CSS selectors</td>\n<td><strong>js-css-standards.md</strong></td>\n</tr>\n<tr>\n<td>Elementor custom widget, Dynamic Tags, Loop Grid, Form actions, Theme Builder conditions</td>\n<td><strong>elementor-patterns.md</strong></td>\n</tr>\n<tr>\n<td>Elementor extension points — custom <strong>form fields</strong>, <strong>theme locations</strong>, injecting controls into native widgets, <strong>adding a TAB to the editor's Elements panel</strong>, Finder/context-menu, hooks reference, deprecations</td>\n<td><strong>elementor-extending.md</strong></td>\n</tr>\n<tr>\n<td>WooCommerce HPOS, order API, template overrides, Loop Grid for products</td>\n<td><strong>woocommerce.md</strong></td>\n</tr>\n<tr>\n<td>REST API endpoints, schema, permission callbacks</td>\n<td><strong>rest-api.md</strong></td>\n</tr>\n<tr>\n<td>Off-canvas UI, off-canvas accessibility, focus trap</td>\n<td><strong>offcanvas-ui.md</strong></td>\n</tr>\n<tr>\n<td>Performance checklists (frontend + backend), speculative loading, IE conditional comments</td>\n<td><strong>performance.md</strong></td>\n</tr>\n<tr>\n<td>Accessibility checklist, WCAG 2.2 AA, ARIA patterns</td>\n<td><strong>performance.md</strong></td>\n</tr>\n<tr>\n<td><strong>Hard-won production gotchas</strong> — widget lifecycle fatals, <code>content_template()</code> escaping, CSS-in-Elementor footguns, transactional email, wp.org review/packaging, embedding apps, AJAX/loop re-rendering, dynamic CSS in loops, Swiper on optimised sites, holding a login without counting it as failed</td>\n<td><strong>field-notes.md</strong></td>\n</tr>\n<tr>\n<td><strong>wordpress.org submission</strong> — the 18 Directory Guidelines, Plugin Check 2.1.0 categories/usage, review process, required headers/readme</td>\n<td><strong>wp-org-guidelines.md</strong></td>\n</tr>\n<tr>\n<td><strong>Subversion (SVN)</strong> — deploying/tagging a release on the wordpress.org plugin/theme SVN (trunk/tags/assets), the daily work cycle, branching/merging, properties, repo admin</td>\n<td><strong>svn/svn.md</strong> (self-contained sub-bundle; <code>svn/references/</code> goes deeper)</td>\n</tr>\n<tr>\n<td><strong>Debugging &amp; static analysis</strong> — PHPCS+WPCS, PHPStan, Plugin Check, <code>WP_DEBUG</code>/Query Monitor, Elementor Safe Mode/cache, symptom→cause table</td>\n<td><strong>debugging.md</strong></td>\n</tr>\n<tr>\n<td><strong>Common WordPress APIs</strong> — admin settings page (Settings + Options API), <code>register_meta</code>, roles/capabilities, WP-Cron, internationalization (i18n)</td>\n<td><strong>wordpress-apis.md</strong></td>\n</tr>\n<tr>\n<td><strong>Database layer (MariaDB/MySQL)</strong> — custom tables + <code>dbDelta()</code>, charsets &amp; the <code>varchar(191)</code> index rule, indexes, <code>EXPLAIN</code>, MariaDB↔MySQL portability, <code>sql_mode</code>, full-text</td>\n<td><strong>mariadb.md</strong></td>\n</tr>\n</tbody>\n</table>\n<h3>Widget Boilerplates — Load when building a widget of that type</h3>\n<table>\n<thead>\n<tr>\n<th>Widget type</th>\n<th>Read this file</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Button / CTA with icon and link</td>\n<td><strong>widget-button.md</strong></td>\n</tr>\n<tr>\n<td>Container / layout wrapper / section / card shell</td>\n<td><strong>widget-container.md</strong></td>\n</tr>\n<tr>\n<td>Image with caption, link, lightbox</td>\n<td><strong>widget-image.md</strong></td>\n</tr>\n<tr>\n<td>Heading / title / HTML tag selector</td>\n<td><strong>widget-heading.md</strong></td>\n</tr>\n<tr>\n<td>Rich text / WYSIWYG body content</td>\n<td><strong>widget-text-editor.md</strong></td>\n</tr>\n<tr>\n<td>Video embed (YouTube, Vimeo, self-hosted)</td>\n<td><strong>widget-video.md</strong></td>\n</tr>\n<tr>\n<td>Widget that renders a saved Elementor template by ID</td>\n<td><strong>widget-elementor-template.md</strong> (SELECT2, <code>get_builder_content_for_display()</code>, Dynamic Tags context, CSS timing)</td>\n</tr>\n<tr>\n<td>Widget whose markup lives in a separate PHP template file</td>\n<td><strong>widget-php-template.md</strong> (Strategies A/B/C, <code>load_template()</code>, <code>ob_start</code>, path-traversal safety)</td>\n</tr>\n<tr>\n<td>Divider / horizontal rule with optional text or icon</td>\n<td><strong>widget-divider.md</strong></td>\n</tr>\n<tr>\n<td>Spacer / vertical gap</td>\n<td><strong>widget-spacer.md</strong></td>\n</tr>\n<tr>\n<td>Single standalone icon with optional link</td>\n<td><strong>widget-icon.md</strong></td>\n</tr>\n<tr>\n<td>Icon + title + description box</td>\n<td><strong>widget-icon-box.md</strong></td>\n</tr>\n<tr>\n<td>Image + title + description box</td>\n<td><strong>widget-image-box.md</strong></td>\n</tr>\n<tr>\n<td>Image grid / gallery</td>\n<td><strong>widget-image-gallery.md</strong></td>\n</tr>\n<tr>\n<td>Image slider / carousel</td>\n<td><strong>widget-image-carousel.md</strong></td>\n</tr>\n<tr>\n<td>Bullet list with icons per item</td>\n<td><strong>widget-icon-list.md</strong></td>\n</tr>\n<tr>\n<td>Animated number counter</td>\n<td><strong>widget-counter.md</strong></td>\n</tr>\n<tr>\n<td>Percentage progress bar</td>\n<td><strong>widget-progress.md</strong></td>\n</tr>\n<tr>\n<td>Customer quote / testimonial</td>\n<td><strong>widget-testimonial.md</strong></td>\n</tr>\n<tr>\n<td>Tabbed content panels</td>\n<td><strong>widget-tabs.md</strong></td>\n</tr>\n<tr>\n<td>Accordion — one panel open at a time</td>\n<td><strong>widget-accordion.md</strong></td>\n</tr>\n<tr>\n<td>Toggle — multiple panels open simultaneously</td>\n<td><strong>widget-toggle.md</strong></td>\n</tr>\n<tr>\n<td>Social media icon links row</td>\n<td><strong>widget-social-icons.md</strong></td>\n</tr>\n<tr>\n<td>Colored alert / notice box</td>\n<td><strong>widget-alert.md</strong></td>\n</tr>\n<tr>\n<td>Audio player (SoundCloud / self-hosted)</td>\n<td><strong>widget-audio.md</strong></td>\n</tr>\n<tr>\n<td>WordPress shortcode output</td>\n<td><strong>widget-shortcode.md</strong></td>\n</tr>\n<tr>\n<td>Raw custom HTML / JS / CSS embed</td>\n<td><strong>widget-html.md</strong></td>\n</tr>\n<tr>\n<td>Named anchor for in-page navigation</td>\n<td><strong>widget-menu-anchor.md</strong></td>\n</tr>\n<tr>\n<td>WordPress registered sidebar output</td>\n<td><strong>widget-sidebar.md</strong></td>\n</tr>\n<tr>\n<td>WordPress  read more tag</td>\n<td><strong>widget-read-more.md</strong></td>\n</tr>\n<tr>\n<td>Google Maps embed</td>\n<td><strong>widget-google-maps.md</strong></td>\n</tr>\n<tr>\n<td>Decorative star rating display</td>\n<td><strong>widget-star-rating.md</strong></td>\n</tr>\n<tr>\n<td>Schema-ready structured rating</td>\n<td><strong>widget-rating.md</strong></td>\n</tr>\n<tr>\n<td>Text following a curved / custom SVG path</td>\n<td><strong>widget-text-path.md</strong></td>\n</tr>\n<tr>\n<td>Nested Tabs / Nested Accordion (each panel is a Container holding any widgets)</td>\n<td><strong>widget-nested.md</strong></td>\n</tr>\n</tbody>\n</table>\n<p><strong>Always read the relevant sub-file before writing code.</strong> For tasks that span multiple\nareas (e.g. a WooCommerce widget with custom REST endpoint), read all relevant sub-files.\nFor a widget task, read BOTH the widget boilerplate file AND <strong>elementor-patterns.md</strong>.\nFor <strong>any custom widget, plugin, or wp.org-bound work, also skim <code>field-notes.md</code></strong> — it\ncatches the lint-passing, review-failing, site-down mistakes the topic files don't dwell on.</p>\n<hr>\n<h2>0. Golden Rules (Never Violate)</h2>\n<p>These override everything in all sub-files:</p>\n<ol>\n<li><strong>Native APIs first</strong> — WordPress core hook before plugin; Elementor API before template override.</li>\n<li><strong>Sanitize in, escape out</strong> — Every input sanitized. Every output escaped. No exceptions.</li>\n<li><strong>Prefix everything</strong> — All functions, classes, constants, hooks, and CSS classes use a project-specific prefix.</li>\n<li><strong>State your placement</strong> — Every code response must declare exactly where the code lives.</li>\n<li><strong>No over-clarifying</strong> — Only ask a clarification question if the missing info would materially change the code output. Otherwise, state your assumption and proceed.</li>\n<li><strong>NEVER hardcode visual settings in widgets</strong> — Every visual property (colors, fonts, sizes,\nspacing, backgrounds, borders, shadows, alignment) MUST be exposed as a standard Elementor\ncontrol in the editor panel. Users control appearance via the toolbar — not by editing code.\nSee §5 \"Mandatory Widget Controls\" below for the required controls checklist.</li>\n<li><strong>Name for the directory from day one</strong> — Naming is decided first and is effectively\nirreversible, so get it right before writing code. The plugin <strong>slug/name must NOT start with\na trademark you don't own</strong> — <code>\"CRM for WooCommerce\"</code>, never <code>\"WooCommerce CRM\"</code> (Directory\nGuideline 17). The <strong>text domain must exactly equal the plugin slug</strong>. The wp.org slug is\n<strong>permanent</strong>, and a widget's <code>get_name()</code> is <strong>sticky</strong> (stored in every page's\n<code>_elementor_data</code> — renaming it breaks placed widgets). Choose the public name, slug, text\ndomain, and code prefix (Rule #3) <strong>together, once</strong>. This is distinct from Rule #3: that\ngoverns internal code symbols; this governs the public identity. See <strong>wp-org-guidelines.md</strong>\n(Guidelines 12/16/17) and <strong>field-notes.md</strong> §1 (sticky <code>get_name()</code>).</li>\n</ol>\n<hr>\n<h2>1. Default Assumptions</h2>\n<p>Quickly assess — <strong>only ask if the answer would change the code</strong>:</p>\n<table>\n<thead>\n<tr>\n<th>Info needed</th>\n<th>Ask only if...</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Scope (plugin vs snippet vs child theme)</td>\n<td>Context doesn't make it obvious</td>\n</tr>\n<tr>\n<td>Elementor tier (Free / Pro / custom widget)</td>\n<td>Pro-only APIs are involved</td>\n</tr>\n<tr>\n<td>PHP version</td>\n<td>Code uses PHP 8.3+ features like typed class constants, or 8.4+ features like property hooks</td>\n</tr>\n<tr>\n<td>WooCommerce / ACF / WPML present</td>\n<td>Integration with those systems is required</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Default stack when not stated</strong> (full release-by-release history lives in <strong>CHANGELOG.md</strong> — keep volatile version-tracking out of this router):</p>\n<table>\n<thead>\n<tr>\n<th>Component</th>\n<th>Version</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>WordPress</strong></td>\n<td><strong>7.1</strong></td>\n<td><strong>\"Mary Lou\", released Aug 19, 2026</strong> (7.0 \"Armstrong\" was May 20; the 7.0 branch ended at 7.0.4, Aug 12). Minimum PHP <strong>7.4</strong> (7.2/7.3 dropped — sites still on them stay pinned to 6.9.x). No multisite assumed. <strong>7.1 makes the post editor iframe unconditional — see below.</strong></td>\n</tr>\n<tr>\n<td><strong>PHP</strong></td>\n<td><strong>8.3</strong> recommended</td>\n<td>7.4 = minimum. 8.4 / 8.5 = \"beta support\" (possible deprecation notices). 8.2 fully compatible but no longer the recommended default.</td>\n</tr>\n<tr>\n<td><strong>Elementor (free + Pro)</strong></td>\n<td><strong>4.2+</strong></td>\n<td>Separate plugins with <strong>independent version numbers</strong> — currently <strong>free 4.2.3 / Pro 4.2.2</strong> (both Aug 19, 2026). 4.0.0 (Mar 30, 2026) made the Atomic Editor stable + default for new installs; 4.2.0 (Jul 20) added Atomic Grid (free) and Atomic Loop (Pro). ⚠️ Elementor's header still reads <strong>\"Tested up to: 7.0.4\"</strong> — it has <strong>not</strong> declared WP 7.1 support. <strong>V3 <code>Widget_Base</code> remains fully supported — all skill code targets V3 and is production-safe.</strong></td>\n</tr>\n<tr>\n<td><strong>WooCommerce</strong></td>\n<td><strong>11.0+</strong></td>\n<td><strong>11.0 (Aug 4, 2026) REMOVED the product editor beta</strong> and bumped to <strong>Action Scheduler 4.0.0</strong> (breaking dedup change). HPOS default-on since 8.2; 10.7 disabled HPOS \"sync on read\"; 10.9 defers Store API draft-order creation — see woocommerce.md.</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Note:</strong> Elementor core and Elementor Pro have independent version numbers — always check <strong>both</strong> when diagnosing compatibility issues.</p>\n<h3>WordPress 7.1 — what changed for plugin / Elementor devs</h3>\n<p>WP 7.1 <strong>\"Mary Lou\"</strong> shipped <strong>August 19, 2026</strong> (WordCamp US). Unlike 7.0, this release\ncontains <strong>four changes that can break a shipped plugin</strong> — they are listed first. No PHP or\nWordPress minimum changed.</p>\n<p><strong>\uD83D\uDD34 Breaking — audit your plugin for these:</strong></p>\n<ol>\n<li><strong>The post editor is now ALWAYS in an iframe</strong> — including on sites registering legacy meta\nboxes, which was the last remaining escape hatch. Editor JavaScript that reaches for the\nglobal <code>document</code> / <code>window</code> now targets the <strong>wrong document</strong>. Use the canvas element's\n<strong><code>ownerDocument</code></strong> and <strong><code>defaultView</code></strong> instead:\n<pre><code>// ❌ breaks in 7.1 — this is the OUTER document, not the editor canvas\ndocument.querySelector( '.my-block' ).classList.add( 'ready' );\n\n// ✅ resolve the document from a node you already own inside the canvas\nconst doc  = myCanvasNode.ownerDocument;\nconst view = doc.defaultView;                   // the iframe's `window`\ndoc.querySelector( '.my-block' )?.classList.add( 'ready' );\nview.requestAnimationFrame( … );\n</code></pre>\nInjected <code>&lt;style&gt;</code>/<code>&lt;link&gt;</code> must go into the <strong>canvas</strong> document too, or it styles nothing.\n<strong>Elementor's own editor is unaffected</strong> (it is not the block editor) — this hits block\nregistrations, meta boxes, and any block-editor integration your plugin ships.</li>\n<li><strong><code>__next40pxDefaultSize</code> is now a no-op</strong> on <code>@wordpress/components</code> — <strong>remove the prop</strong>\nentirely; there is no replacement. Form controls render at 40px unconditionally.</li>\n<li><strong>List-table markup changed:</strong> the row header moved from the checkbox column to the <strong>title</strong>\ncolumn. Any CSS/JS selector keyed on <code>th.check-column</code> (or assuming the row header's position)\nbreaks — audit custom admin list tables and column callbacks.</li>\n<li><strong>jQuery UI updated to 1.14.2.</strong> Test anything depending on jQuery UI behaviour or styling.</li>\n</ol>\n<p><strong>\uD83D\uDFE2 New and useful:</strong></p>\n<ul>\n<li><strong>SVG Icon API</strong> (public in 7.1) — register icons and whole collections, render server-side,\nread over REST: <code>wp_register_icon_collection()</code>, <code>wp_register_icon()</code>, <code>wp_get_icon()</code>.\n⚠️ SVGs pass through a <strong>conservative allowlist — only <code>&lt;svg&gt;</code>, <code>&lt;path&gt;</code> and <code>&lt;polygon&gt;</code> survive</strong>.\nA <code>&lt;g&gt;</code>, <code>&lt;circle&gt;</code>, <code>&lt;rect&gt;</code> or <code>&lt;use&gt;</code> in your icon is stripped silently, so convert shapes to\npaths before registering. (This is a <em>core</em> API — Elementor widgets still ship their own SVGs;\nsee <code>elementor-extending.md</code> §8 and the \"don't <code>wp_kses()</code> inline SVG\" rule in <code>field-notes.md</code> §6.)</li>\n<li><strong>Abilities API matured:</strong> <code>wp_get_abilities()</code> filtering, <strong>execution-lifecycle hooks</strong>, custom\nvalidation, a unified <code>public</code> exposure flag, and client-compatible JSON-Schema preparation.\nRegistration timing rules from 7.0 are unchanged (see the 7.0 section below).</li>\n<li><strong>Global Styles / <code>theme.json</code>:</strong> responsive style variations with <strong>configurable breakpoints</strong>,\npseudo-state styling (<code>:hover</code> / <code>:focus</code> / <code>:active</code>), and <code>text-shadow</code> support.</li>\n<li><strong>Media:</strong> client-side image processing in the browser via <strong>WebAssembly</strong> (compression/resizing\nno longer hit the server), infinite scroll in the Media Library, and registration of multiple\nimage sizes at once. Multisite now enforces upload limits on <strong>media sideloading</strong>.</li>\n<li><strong>Persistent admin bar</strong> across editor screens — review any custom toolbar nodes for behaviour\nin the editor context.</li>\n<li><strong>Design System:</strong> semantic design tokens for theming admin UI (relevant if you build admin or\neditor panels — compare the Elementor panel tokens in <code>elementor-extending.md</code> §5).</li>\n<li><strong>DataViews / DataForm</strong> APIs matured for data-driven admin interfaces.</li>\n<li><strong>React stays on 18.3</strong> — React 19 was deferred again. Don't bundle <code>react/jsx-runtime</code>\nyourself, and avoid string refs / <code>defaultProps</code>, which fail under the experimental React 19 flag.</li>\n<li>Conditional block-CSS loading can affect <strong>remote content pulls</strong> — verify if you render blocks\noutside a normal page request.</li>\n</ul>\n<p><em>Sources: wordpress.org/news/2026/08/mary-lou/ · make.wordpress.org/core/2026/08/05/wordpress-7-1-field-guide/ ·\ndeveloper.wordpress.org/news/2026/08/whats-new-for-developers-august-2026/</em></p>\n<h3>WordPress 7.0 — what changed for plugin / Elementor devs</h3>\n<p>WP 7.0 \"Armstrong\" shipped <strong>May 20, 2026</strong> (delayed from the original April 9 target while\nthe RTC storage layer was redesigned — see below). Everything below is <strong>opt-in and\nnon-breaking</strong>; most plugin/Elementor work is unaffected. It remains relevant because 7.1\nchanged none of it.</p>\n<ul>\n<li><strong>Minimum PHP is now 7.4</strong> (7.2/7.3 dropped). The skill's recommended baseline stays\n<strong>PHP 8.3</strong>. Bump your plugin's <code>Requires PHP</code> header to 7.4 only once you target WP 7.0+\nexclusively. No new DB minimum is enforced; <code>wordpress.org/about/requirements/</code> recommends\n<strong>MariaDB 10.11+ or MySQL 8.0+</strong> (the MariaDB floor was raised from 10.6; re-verified Aug 2026).</li>\n<li><strong>Real-Time Collaboration (RTC):</strong> simultaneous multi-author block editing (CRDT-based, via\nan HTTP-polling sync provider — not WebRTC). Data is stored in a <strong>dedicated core database\ntable</strong>; an earlier <code>wp_post_meta</code> / <code>wp_sync_storage</code> design was rejected, and building the\ntable is what pushed the release from April to May. <strong>Plugin impact:</strong> scope every\n<code>WP_Query</code> / <code>get_posts()</code> with an explicit <code>post_type</code> so internal core post types never\nleak into your results — do <strong>not</strong> hardcode any internal RTC type name. The\n<code>WP_ALLOW_COLLABORATION</code> constant lets hosts swap the sync transport.</li>\n<li><strong>WP AI Client:</strong> provider-agnostic PHP + JS AI API — <code>wp_ai_client_prompt( $prompt )-&gt;generate_text()</code>.\nGuard with <code>function_exists( 'wp_ai_client_prompt' )</code>.</li>\n<li><strong>Abilities API:</strong> <code>wp_register_ability()</code> (PHP, since WP 6.9) plus a JS counterpart in 7.0.\nUse <code>'meta' =&gt; ['show_in_rest' =&gt; true]</code> to expose via REST. Register ONLY inside the\n<strong><code>wp_abilities_api_init</code></strong> hook (core renamed the feature plugin's <code>abilities_api_init</code>;\ncategories go on <code>wp_abilities_api_categories_init</code>) or you get <code>_doing_it_wrong</code>; a\n<code>category</code> is required and must already be registered. Calling it also sets your real\n<code>Requires at least</code> floor to <strong>6.9</strong> — Plugin Check errors on the header and\n<code>function_exists()</code> guards don't count (see <code>debugging.md</code> §1).</li>\n<li><strong>Connectors UI</strong> (Settings → Connectors) for managing AI provider credentials, and a\n<strong>Command Palette</strong> in wp-admin.</li>\n<li><strong>Iframed editor</strong> was still conditional in 7.0 — <strong>7.1 makes it unconditional.</strong> See the 7.1\nsection above; <code>\"apiVersion\": 3</code> in <code>block.json</code> is now table stakes, not preparation.</li>\n</ul>\n<p><em>Sources: make.wordpress.org/core/2026/01/09/dropping-support-for-php-7-2-and-7-3/ ·\nmake.wordpress.org/core/2026/04/22/wordpress-7-0-release-party-updated-schedule/ ·\nwordpress.org/about/requirements/</em></p>\n<blockquote>\n<p>\uD83D\uDCCC <strong>PHP support labels (unchanged in WP 7.1):</strong> PHP 7.4–8.3 fully compatible; <strong>8.3 recommended</strong>;\n8.4 (WP 6.7+) and 8.5 (WP 6.9+) carry a \"beta support\" label (possible deprecation notices).\nSource: make.wordpress.org/core/handbook/references/php-compatibility-and-wordpress-versions/</p>\n</blockquote>\n<blockquote>\n<p>✅ <strong>Elementor 4.x status (current: free 4.2.3 / Pro 4.2.2, both Aug 19, 2026):</strong> Elementor\n4.0.0 (Mar 30, 2026, free + Pro) made the <strong>Atomic Editor stable and the default for new\ninstalls</strong> and added Atomic Forms, Pro Interactions, and Component creation. Updating to 4.x\nleaves <strong>existing sites untouched</strong> — V3 widgets and V4 Atomic Elements coexist on the same\npage; Atomic features are toggled at WP Admin → Elementor → Editor → Settings.</p>\n<p><strong>There is still NO third-party API for building Atomic Elements, and Elementor has said so\nexplicitly</strong> — they will not release one soon and <em>advise against</em> integrating with Atomic\ninternals until they announce it (elementor/elementor GitHub Discussion #32950). Atomic\nElements are documented only as a <strong>data structure</strong>\n(developers.elementor.com/docs/data-structure/atomic-elements). So <strong>continue using V3\n<code>Widget_Base</code></strong> for all third-party widgets — it is the correct, production-safe API and all\nskill code targets it. <em>(Re-verified Aug 23, 2026 — unchanged since the July check.)</em></p>\n<p>⚠️ <strong>Elementor has not declared WP 7.1 compatibility yet.</strong> As of free 4.2.3 / Pro 4.2.2 the\n<code>Tested up to</code> header reads <strong>7.0.4</strong>, three days after WP 7.1 shipped. That is normal lag,\nnot a known incompatibility — but when a client reports editor breakage on a fresh 7.1 site,\ncheck Elementor's current <code>Tested up to</code> before debugging your own code.</p>\n<p><strong>V4 Atomic Elements that now ship by default (awareness only — not third-party-buildable yet):</strong>\nDiv Block &amp; Flexbox Container (layout); <strong>Atomic Grid</strong> — advanced row/column layouts (free\n4.2.0, Jul 20, 2026); Atomic Heading, Paragraph, Image, Button, Video, SVG;\n<strong>Atomic Tabs</strong>; <strong>Atomic Forms</strong> (Pro) with composable fields — Label, Input, Textarea,\nCheckbox, Submit, plus Radio, Select, Date Picker, Time Picker, and File Upload (added in\nPro 4.1.0, May 26, 2026); and <strong>Atomic Loop</strong> — dynamic content layouts, the V4 counterpart\nof the Pro Loop Grid (Pro 4.2.0, Jul 20, 2026). These are end-user elements; building\n<strong>custom</strong> atomic elements still awaits the finalized V4 extension docs — keep targeting V3\n<code>Widget_Base</code> until then.\nSource: elementor.com/products/website-builder/v4-faq/ ·\ndevelopers.elementor.com/elementor-editor-4-0-developers-update/ ·\nelementor.com/pro/changelog/ · github.com/elementor/elementor/releases</p>\n</blockquote>\n<blockquote>\n<p>\uD83D\uDDD3️ <strong>Release-by-release history (betas, RCs, point releases) lives in <code>CHANGELOG.md</code>.</strong>\nKeep this router focused on durable guidance; update version facts in the table above\nand in <code>CHANGELOG.md</code>, not scattered across the sub-files.</p>\n</blockquote>\n<hr>\n<h2>2. Architecture Decision Tree</h2>\n<p>Run through this mentally before writing a single line:</p>\n<pre><code>Does a WordPress core hook (add_action/add_filter) solve it?\n  YES → Use the hook. No plugin needed. Place in child theme functions.php\n        or Elementor Custom Code.\n  NO  → Does Elementor's PHP/JS API solve it?\n          YES → Extend via Elementor hooks, Dynamic Tags, or Widget_Base.\n                Register via elementor/widgets/register or elementor/dynamic_tags/register.\n          NO  → Is this logic reusable across themes or sites?\n                  YES → Scaffold a dedicated plugin (see scaffolding.md).\n                  NO  → Child theme functions.php or Elementor Custom Code block.\n\nIs a WooCommerce override needed?\n  → Use Elementor Loop Grid + custom query filter BEFORE touching template files.\n  → Only override woocommerce/ templates as an absolute last resort.\n\nIs this a REST API endpoint?\n  → Always register via register_rest_route() inside a plugin, never in functions.php.\n</code></pre>\n<hr>\n<h2>3. Mandatory Output Format</h2>\n<p><strong>Every single code response must follow this structure — no exceptions:</strong></p>\n<pre><code>\uD83D\uDCCD PLACEMENT\nExact file path or Elementor hook location.\ne.g. /wp-content/plugins/myplugin/includes/class-myplugin-hooks.php\n     Elementor → Site Settings → Custom Code → wp_footer\n\n⚙️ REQUIRES\nWordPress X.X+ | PHP X.X+ | Elementor Pro X.X+ | ACF X.X+ | WooCommerce X.X+\n(list only what the code actually depends on)\n\n\uD83D\uDCA1 WHY THIS APPROACH\nOne paragraph: which branch of the §2 decision tree was taken and why.\n\n\uD83D\uDCCB CODE\nComplete, commented, deployment-ready code block — no truncation, no omissions.\n\n\uD83D\uDD27 INTEGRATION NOTES (include when relevant)\nAny manual steps required: flush rewrite rules, set Query ID in Elementor editor,\nactivate plugin, clear Elementor cache, etc.\n</code></pre>\n<hr>\n<h2>4. Quick Reference — Pattern Index</h2>\n<table>\n<thead>\n<tr>\n<th>Task</th>\n<th>Approach</th>\n<th>Sub-file</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Plugin scaffold</td>\n<td>Singleton + hooks class + assets class + HPOS declaration</td>\n<td>scaffolding.md</td>\n</tr>\n<tr>\n<td>Child theme setup</td>\n<td><code>wp_enqueue_style</code> parent + child in functions.php</td>\n<td>scaffolding.md</td>\n</tr>\n<tr>\n<td>Custom Post Type + Taxonomy</td>\n<td><code>register_post_type()</code> + <code>register_taxonomy()</code> in plugin</td>\n<td>scaffolding.md</td>\n</tr>\n<tr>\n<td>Secure AJAX handler</td>\n<td><code>wp_ajax_</code> hooks + nonce verify + <code>wp_send_json_*</code></td>\n<td>scaffolding.md</td>\n</tr>\n<tr>\n<td>PHP sanitization / escaping</td>\n<td><code>wp_unslash()</code> + <code>sanitize_*</code> + <code>esc_*</code> patterns</td>\n<td>php-standards.md</td>\n</tr>\n<tr>\n<td>Transient caching</td>\n<td><code>get_transient</code> / <code>set_transient</code></td>\n<td>php-standards.md</td>\n</tr>\n<tr>\n<td>External API call + WP_Error</td>\n<td><code>wp_remote_get()</code> + <code>WP_Error</code> pattern</td>\n<td>php-standards.md</td>\n</tr>\n<tr>\n<td>Admin settings page</td>\n<td>Settings API + <code>register_setting</code> + <code>sanitize_callback</code> + <code>settings_fields</code></td>\n<td>wordpress-apis.md</td>\n</tr>\n<tr>\n<td>Store plugin options</td>\n<td>Options API + explicit boolean <code>autoload</code> (WP 6.6+)</td>\n<td>wordpress-apis.md</td>\n</tr>\n<tr>\n<td>Custom field exposed to REST / Elementor</td>\n<td><code>register_post_meta</code> + <code>show_in_rest</code></td>\n<td>wordpress-apis.md</td>\n</tr>\n<tr>\n<td>Scheduled / background task</td>\n<td>WP-Cron (<code>wp_schedule_event</code>) + Action Scheduler for heavy jobs</td>\n<td>wordpress-apis.md</td>\n</tr>\n<tr>\n<td>Create a custom DB table</td>\n<td><code>dbDelta()</code> + <code>$wpdb-&gt;prefix</code> + <code>get_charset_collate()</code>; strict formatting rules</td>\n<td>mariadb.md</td>\n</tr>\n<tr>\n<td>\"Specified key was too long\" / emoji not saving</td>\n<td><code>utf8mb4</code> + index string columns at <strong><code>varchar(191)</code></strong></td>\n<td>mariadb.md</td>\n</tr>\n<tr>\n<td>Slow query / missing index</td>\n<td><code>EXPLAIN</code> (<code>type: ALL</code>, <code>key: NULL</code>, <code>Using filesort</code>) + leftmost-prefix rule</td>\n<td>mariadb.md</td>\n</tr>\n<tr>\n<td>SQL that must run on MariaDB <strong>and</strong> MySQL</td>\n<td>Plain standard SQL; JSON is stored/compared differently — don't query inside it</td>\n<td>mariadb.md</td>\n</tr>\n<tr>\n<td>Make a plugin translatable</td>\n<td>i18n functions + text-domain = slug + WP 6.7 <code>init</code>-timing rule</td>\n<td>wordpress-apis.md</td>\n</tr>\n<tr>\n<td>Ship a translation for a wp.org-hosted plugin</td>\n<td><code>.pot</code> only in the zip; translate.wordpress.org → language packs (90% gate). <strong>Never</strong> bundle <code>.mo</code> / call <code>load_plugin_textdomain()</code></td>\n<td>wordpress-apis.md</td>\n</tr>\n<tr>\n<td>Style UI text for non-Latin locales</td>\n<td>Caseless scripts + <code>text-transform</code>/<code>letter-spacing</code>; don't size to English string length</td>\n<td>js-css-standards.md</td>\n</tr>\n<tr>\n<td>Test that translations actually load</td>\n<td>Set the site locale before bootstrap; <code>unload_textdomain()</code> in WP-CLI; validate placeholders</td>\n<td>debugging.md</td>\n</tr>\n<tr>\n<td>Deploy / tag a plugin release on wp.org SVN</td>\n<td><code>svn co</code> → copy to <code>trunk/</code> → <code>svn cp trunk tags/X.Y.Z</code> → <code>svn ci</code> (assets/ is a sibling)</td>\n<td>svn/svn.md</td>\n</tr>\n<tr>\n<td>Any SVN task (checkout, commit, branch, merge, conflicts, repo admin)</td>\n<td>Subversion sub-bundle</td>\n<td>svn/svn.md</td>\n</tr>\n<tr>\n<td>WP 6.8 password hashing</td>\n<td><code>wp_check_password()</code> + <code>wp_password_needs_rehash()</code></td>\n<td>php-standards.md</td>\n</tr>\n<tr>\n<td>WP 6.8 app password / key hashing</td>\n<td><code>wp_fast_hash()</code> + <code>wp_verify_fast_hash()</code> (BLAKE2b)</td>\n<td>php-standards.md</td>\n</tr>\n<tr>\n<td>JS standards + enqueue defer/async</td>\n<td>IIFE + WP 6.3+ enqueue API</td>\n<td>js-css-standards.md</td>\n</tr>\n<tr>\n<td>PHP → JS data passing</td>\n<td><code>wp_add_inline_script()</code> with <code>wp_json_encode()</code></td>\n<td>js-css-standards.md</td>\n</tr>\n<tr>\n<td>CSS BEM + design tokens</td>\n<td>Scoped tokens, 8pt spacing, fluid type</td>\n<td>js-css-standards.md</td>\n</tr>\n<tr>\n<td>Custom Elementor widget</td>\n<td><code>Widget_Base</code> + all required methods</td>\n<td>elementor-patterns.md</td>\n</tr>\n<tr>\n<td>Elementor Dynamic Tag</td>\n<td><code>Tag</code> class + <code>elementor/dynamic_tags/register</code></td>\n<td>elementor-patterns.md</td>\n</tr>\n<tr>\n<td>Elementor Loop Grid query</td>\n<td><code>elementor/query/</code> filter</td>\n<td>elementor-patterns.md</td>\n</tr>\n<tr>\n<td>ACF field in Elementor</td>\n<td>Dynamic Tag extending <code>\\Elementor\\Core\\DynamicTags\\Tag</code></td>\n<td>elementor-patterns.md</td>\n</tr>\n<tr>\n<td>Elementor Pro Form action</td>\n<td><code>Action_Base</code> + field iteration</td>\n<td>elementor-patterns.md</td>\n</tr>\n<tr>\n<td>Theme Builder custom condition</td>\n<td><code>Condition_Base</code> + <code>elementor/theme/register_conditions</code></td>\n<td>elementor-patterns.md</td>\n</tr>\n<tr>\n<td>Elementor Pro custom form field</td>\n<td><code>Field_Base</code> + <code>elementor_pro/forms/fields/register</code></td>\n<td>elementor-extending.md</td>\n</tr>\n<tr>\n<td>Theme Builder custom location</td>\n<td><code>register_location</code> + <code>elementor_theme_do_location()</code></td>\n<td>elementor-extending.md</td>\n</tr>\n<tr>\n<td>Add a control to a NATIVE Elementor widget</td>\n<td><code>elementor/element/{el}/{section}/before_section_end</code></td>\n<td>elementor-extending.md</td>\n</tr>\n<tr>\n<td>Filter a native widget's output</td>\n<td><code>elementor/widget/render_content</code> filter</td>\n<td>elementor-extending.md</td>\n</tr>\n<tr>\n<td>Add a TAB to the editor panel (like an SEO plugin)</td>\n<td><code>elementorV2.editorElementsPanel.injectTab()</code> + <code>elementor/editor/before_enqueue_scripts</code></td>\n<td>elementor-extending.md</td>\n</tr>\n<tr>\n<td>WooCommerce HPOS compatibility</td>\n<td><code>FeaturesUtil::declare_compatibility()</code> + <code>wc_get_order()</code></td>\n<td>woocommerce.md</td>\n</tr>\n<tr>\n<td>WooCommerce loop</td>\n<td>Loop Grid + <code>elementor/query/</code> filter</td>\n<td>woocommerce.md</td>\n</tr>\n<tr>\n<td>Custom REST endpoint</td>\n<td><code>register_rest_route()</code> + schema callback in plugin</td>\n<td>rest-api.md</td>\n</tr>\n<tr>\n<td>Off-canvas filter panel</td>\n<td>BEM CSS + ARIA JS + Custom Code block</td>\n<td>offcanvas-ui.md</td>\n</tr>\n<tr>\n<td>Performance checklist</td>\n<td>Frontend + backend checklists</td>\n<td>performance.md</td>\n</tr>\n<tr>\n<td>Accessibility checklist</td>\n<td>WCAG 2.2 AA patterns</td>\n<td>performance.md</td>\n</tr>\n<tr>\n<td>Elementor dependency check</td>\n<td><code>Requires Plugins: elementor</code> header + <code>did_action</code> fallback</td>\n<td>scaffolding.md</td>\n</tr>\n<tr>\n<td>Widget with button/CTA</td>\n<td>Controls + render matching native Button widget</td>\n<td>widget-button.md</td>\n</tr>\n<tr>\n<td>Widget with container/layout</td>\n<td>Controls + render matching native Container</td>\n<td>widget-container.md</td>\n</tr>\n<tr>\n<td>Widget with image</td>\n<td><code>Group_Control_Image_Size</code> + <code>get_attachment_image_html()</code></td>\n<td>widget-image.md</td>\n</tr>\n<tr>\n<td>Widget with heading</td>\n<td>TEXTAREA + header_size tag selector</td>\n<td>widget-heading.md</td>\n</tr>\n<tr>\n<td>Widget with rich text</td>\n<td>WYSIWYG + wp_kses_post output</td>\n<td>widget-text-editor.md</td>\n</tr>\n<tr>\n<td>Widget with video embed</td>\n<td>Source SELECT + overlay + aspect ratio</td>\n<td>widget-video.md</td>\n</tr>\n<tr>\n<td>Widget rendering saved Elementor template</td>\n<td>get_builder_content_for_display() + SELECT2</td>\n<td>widget-elementor-template.md</td>\n</tr>\n<tr>\n<td>Widget with PHP template file</td>\n<td>load_template() + locate_template() strategies A/B/C</td>\n<td>widget-php-template.md</td>\n</tr>\n<tr>\n<td>Widget with divider line</td>\n<td>Style + width + optional text/icon element</td>\n<td>widget-divider.md</td>\n</tr>\n<tr>\n<td>Widget with spacer gap</td>\n<td>Single responsive SLIDER</td>\n<td>widget-spacer.md</td>\n</tr>\n<tr>\n<td>Widget with single icon</td>\n<td>ICONS control + size + color tabs</td>\n<td>widget-icon.md</td>\n</tr>\n<tr>\n<td>Widget with icon + text box</td>\n<td>Icon + title + description pattern</td>\n<td>widget-icon-box.md</td>\n</tr>\n<tr>\n<td>Widget with image + text box</td>\n<td>Image + title + description pattern</td>\n<td>widget-image-box.md</td>\n</tr>\n<tr>\n<td>Widget with image grid</td>\n<td>GALLERY control + Group_Control_Image_Size</td>\n<td>widget-image-gallery.md</td>\n</tr>\n<tr>\n<td>Widget with image slider</td>\n<td>GALLERY + Swiper + navigation controls</td>\n<td>widget-image-carousel.md</td>\n</tr>\n<tr>\n<td>Widget with icon bullet list</td>\n<td>REPEATER + icon + text + optional link</td>\n<td>widget-icon-list.md</td>\n</tr>\n<tr>\n<td>Widget with animated counter</td>\n<td>Number + prefix/suffix + duration</td>\n<td>widget-counter.md</td>\n</tr>\n<tr>\n<td>Widget with progress bar</td>\n<td>Percentage SLIDER + bar styling</td>\n<td>widget-progress.md</td>\n</tr>\n<tr>\n<td>Widget with testimonial quote</td>\n<td>Content + image + name + job title</td>\n<td>widget-testimonial.md</td>\n</tr>\n<tr>\n<td>Widget with tabbed panels</td>\n<td>REPEATER tabs + horizontal/vertical type</td>\n<td>widget-tabs.md</td>\n</tr>\n<tr>\n<td>Widget with accordion</td>\n<td>REPEATER + single-open collapse pattern</td>\n<td>widget-accordion.md</td>\n</tr>\n<tr>\n<td>Widget with toggle panels</td>\n<td>REPEATER + multi-open toggle pattern</td>\n<td>widget-toggle.md</td>\n</tr>\n<tr>\n<td>Widget with social icons</td>\n<td>REPEATER + brand icons + links</td>\n<td>widget-social-icons.md</td>\n</tr>\n<tr>\n<td>Widget with alert/notice box</td>\n<td>Type SELECT + title + description + dismiss</td>\n<td>widget-alert.md</td>\n</tr>\n<tr>\n<td>Widget with audio player</td>\n<td>SoundCloud URL + autoplay options</td>\n<td>widget-audio.md</td>\n</tr>\n<tr>\n<td>Widget outputting shortcode</td>\n<td>TEXTAREA + do_shortcode()</td>\n<td>widget-shortcode.md</td>\n</tr>\n<tr>\n<td>Widget with raw HTML embed</td>\n<td>CODE control + unescaped output</td>\n<td>widget-html.md</td>\n</tr>\n<tr>\n<td>Widget as named anchor</td>\n<td>TEXT ID + sanitize_html_class()</td>\n<td>widget-menu-anchor.md</td>\n</tr>\n<tr>\n<td>Widget outputting sidebar</td>\n<td>Registered sidebar SELECT + dynamic_sidebar()</td>\n<td>widget-sidebar.md</td>\n</tr>\n<tr>\n<td>Widget with read more tag</td>\n<td>No controls — WordPress $more global</td>\n<td>widget-read-more.md</td>\n</tr>\n<tr>\n<td>Widget with Google Maps</td>\n<td>Address TEXT + zoom SLIDER + iframe</td>\n<td>widget-google-maps.md</td>\n</tr>\n<tr>\n<td>Widget with star rating display</td>\n<td>Scale + rating number + icon style</td>\n<td>widget-star-rating.md</td>\n</tr>\n<tr>\n<td>Widget with schema rating</td>\n<td>Icon count + fractional rating + gap</td>\n<td>widget-rating.md</td>\n</tr>\n<tr>\n<td>Widget with curved/path text</td>\n<td><code>&lt;svg&gt;</code> + <code>&lt;textPath&gt;</code> + unique path id</td>\n<td>widget-text-path.md</td>\n</tr>\n<tr>\n<td>Nested Tabs / Accordion widget</td>\n<td><code>Widget_Nested_Base</code> + <code>print_child()</code> + container panels</td>\n<td>widget-nested.md</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>5. Mandatory Widget Controls — No Hardcoded Visuals (NEVER VIOLATE)</h2>\n<blockquote>\n<p><strong>This section is MANDATORY for every custom Elementor widget.</strong> Whenever you build a widget,\nevery visual property must be an Elementor control — NEVER a hardcoded CSS value. Users\ncontrol appearance from the editor panel/toolbar, not by editing source code.</p>\n</blockquote>\n<h3>The Rule</h3>\n<p><strong>NEVER hardcode</strong> any of the following in PHP <code>render()</code>, in static CSS, or in\n<code>content_template()</code> output:</p>\n<ul>\n<li>Colors (text, background, border, shadow)</li>\n<li>Typography (font family, size, weight, line-height, letter-spacing, transform)</li>\n<li>Spacing (padding, margin, gap)</li>\n<li>Sizing (width, height, min/max values)</li>\n<li>Borders (style, width, color, radius)</li>\n<li>Shadows (box-shadow, text-shadow)</li>\n<li>Backgrounds (color, gradient, image)</li>\n<li>Alignment / positioning</li>\n<li>Opacity, transitions, hover effects</li>\n</ul>\n<p><strong>ALL</strong> of the above must use Elementor controls with <code>selectors</code> that inject CSS dynamically.\nThe only exceptions are structural CSS (display, position, overflow) required for the widget\nlayout to function at all — and even these should use controls when there is a user-facing\nchoice (e.g. flex-direction toggle).</p>\n<blockquote>\n<p>⚠️ <strong>\"No hardcoded visuals\" does NOT mean \"put a <code>default</code> on every control.\"</strong>\nElementor emits a <code>default</code> exactly like a user-set value, so a colour default paints on every\ninstall and clearing the swatch only restores it — the user cannot turn it off. <strong>Elementor core\ncarries no <code>default</code> on colour controls at all</strong> (<code>includes/widgets/heading.php</code>: <code>title_color</code>\nuses a <code>'global'</code> reference; <code>title_hover_color</code> is entirely empty). Follow that split:</p>\n<ul>\n<li><strong>Sizes, spacing, alignment, toggles, tags, labels</strong> → give a real <code>'default'</code>.</li>\n<li><strong>Colours</strong> → leave empty. Put the resting look in your stylesheet's <code>var(--token, fallback)</code>\nand let the control override it. For <strong>state</strong> colours (hover / active / selected / current)\nalso write a <strong>direct CSS property</strong>, never a custom property, and keep that state\n<strong>colour-free in your CSS</strong> — otherwise \"empty\" still paints the fallback.</li>\n</ul>\n<p>See <code>field-notes.md</code> §4 for the full trap, plus the theme-specificity and <code>accent-color</code> cases.</p>\n</blockquote>\n<h3>Required Controls Checklist — Apply to Every Widget</h3>\n<p>When building a widget, include ALL controls that apply to its visual elements.\nUse this checklist as a mandatory gate:</p>\n<table>\n<thead>\n<tr>\n<th>Visual property</th>\n<th>Required Elementor control</th>\n<th>Tab</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Text content</strong></td>\n<td><code>TEXT</code>, <code>TEXTAREA</code>, or <code>WYSIWYG</code> + <code>'dynamic' =&gt; ['active' =&gt; true]</code></td>\n<td>TAB_CONTENT</td>\n</tr>\n<tr>\n<td><strong>Typography</strong> (any text element)</td>\n<td><code>add_group_control( Group_Control_Typography::get_type() )</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Text color</strong></td>\n<td><code>COLOR</code> control with <code>selectors</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Text alignment</strong></td>\n<td><code>add_responsive_control()</code> with <code>CHOOSE</code> (left/center/right/justify)</td>\n<td>TAB_STYLE or TAB_CONTENT</td>\n</tr>\n<tr>\n<td><strong>Background</strong></td>\n<td><code>add_group_control( Group_Control_Background::get_type() )</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Border</strong></td>\n<td><code>add_group_control( Group_Control_Border::get_type() )</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Border radius</strong></td>\n<td><code>add_responsive_control()</code> with <code>DIMENSIONS</code> + <code>'selectors'</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Box shadow</strong></td>\n<td><code>add_group_control( Group_Control_Box_Shadow::get_type() )</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Text shadow</strong></td>\n<td><code>add_group_control( Group_Control_Text_Shadow::get_type() )</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Padding</strong></td>\n<td><code>add_responsive_control()</code> with <code>DIMENSIONS</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Margin</strong></td>\n<td><code>add_responsive_control()</code> with <code>DIMENSIONS</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Width / Height</strong></td>\n<td><code>add_responsive_control()</code> with <code>SLIDER</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Spacing / Gap</strong></td>\n<td><code>add_responsive_control()</code> with <code>SLIDER</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Image</strong></td>\n<td><code>MEDIA</code> + <code>add_group_control( Group_Control_Image_Size::get_type() )</code></td>\n<td>TAB_CONTENT</td>\n</tr>\n<tr>\n<td><strong>CSS Filters</strong> (if image/element)</td>\n<td><code>add_group_control( Group_Control_Css_Filter::get_type() )</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Hover state</strong></td>\n<td>Duplicate color/background/shadow controls inside <code>'section_style_hover'</code> with <code>selectors</code> targeting <code>:hover</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Transition duration</strong></td>\n<td><code>SLIDER</code> (seconds) with <code>selectors =&gt; ['transition-duration']</code></td>\n<td>TAB_STYLE</td>\n</tr>\n<tr>\n<td><strong>Link</strong></td>\n<td><code>URL</code> control with <code>'dynamic' =&gt; ['active' =&gt; true]</code></td>\n<td>TAB_CONTENT</td>\n</tr>\n<tr>\n<td><strong>Icon</strong></td>\n<td><code>ICONS</code> control with <code>fa4compatibility</code></td>\n<td>TAB_CONTENT</td>\n</tr>\n<tr>\n<td><strong>HTML tag</strong></td>\n<td><code>SELECT</code> (h1–h6, div, span, p)</td>\n<td>TAB_CONTENT</td>\n</tr>\n</tbody>\n</table>\n<h3>Example — Correct vs Incorrect</h3>\n<pre><code>// ❌ WRONG — hardcoded color and font-size\nprotected function render(): void {\n    $settings = $this-&gt;get_settings_for_display();\n    echo '&lt;h2 style=\"color: #e94560; font-size: 24px;\"&gt;'\n        . esc_html( $settings['title'] ) . '&lt;/h2&gt;';\n}\n\n// ✅ CORRECT — all visuals controlled from the panel via selectors\nprotected function register_controls(): void {\n    // ... Content section with title TEXT control ...\n\n    $this-&gt;start_controls_section( 'section_title_style', [\n        'label' =&gt; esc_html__( 'Title Style', 'myplugin' ),\n        'tab'   =&gt; \\Elementor\\Controls_Manager::TAB_STYLE,\n    ] );\n\n    $this-&gt;add_control( 'title_color', [\n        'label'     =&gt; esc_html__( 'Color', 'myplugin' ),\n        'type'      =&gt; \\Elementor\\Controls_Manager::COLOR,\n        'selectors' =&gt; [\n            '{{WRAPPER}} .myplugin-widget__title' =&gt; 'color: {{VALUE}};',\n        ],\n    ] );\n\n    $this-&gt;add_group_control(\n        \\Elementor\\Group_Control_Typography::get_type(),\n        [\n            'name'     =&gt; 'title_typography',\n            'selector' =&gt; '{{WRAPPER}} .myplugin-widget__title',\n        ]\n    );\n\n    $this-&gt;add_responsive_control( 'title_align', [\n        'label'   =&gt; esc_html__( 'Alignment', 'myplugin' ),\n        'type'    =&gt; \\Elementor\\Controls_Manager::CHOOSE,\n        'options' =&gt; [\n            'left'   =&gt; [ 'title' =&gt; esc_html__( 'Left',   'myplugin' ), 'icon' =&gt; 'eicon-text-align-left' ],\n            'center' =&gt; [ 'title' =&gt; esc_html__( 'Center', 'myplugin' ), 'icon' =&gt; 'eicon-text-align-center' ],\n            'right'  =&gt; [ 'title' =&gt; esc_html__( 'Right',  'myplugin' ), 'icon' =&gt; 'eicon-text-align-right' ],\n        ],\n        'selectors' =&gt; [\n            '{{WRAPPER}} .myplugin-widget__title' =&gt; 'text-align: {{VALUE}};',\n        ],\n    ] );\n\n    $this-&gt;add_responsive_control( 'title_spacing', [\n        'label'      =&gt; esc_html__( 'Bottom Spacing', 'myplugin' ),\n        'type'       =&gt; \\Elementor\\Controls_Manager::SLIDER,\n        'size_units' =&gt; [ 'px', 'em', 'rem' ],\n        'range'      =&gt; [ 'px' =&gt; [ 'min' =&gt; 0, 'max' =&gt; 100 ] ],\n        'selectors'  =&gt; [\n            '{{WRAPPER}} .myplugin-widget__title' =&gt; 'margin-bottom: {{SIZE}}{{UNIT}};',\n        ],\n    ] );\n\n    $this-&gt;end_controls_section();\n}\n\nprotected function render(): void {\n    $settings = $this-&gt;get_settings_for_display();\n    // ✅ No inline styles — all visuals come from Elementor's selectors\n    $this-&gt;add_render_attribute( 'title', 'class', 'myplugin-widget__title' );\n    $this-&gt;add_inline_editing_attributes( 'title' );\n    echo '&lt;h2 ' . $this-&gt;get_render_attribute_string( 'title' ) . '&gt;'\n        . esc_html( $settings['title'] ) . '&lt;/h2&gt;';\n}\n</code></pre>\n<h3><code>add_render_attribute()</code> and <code>add_inline_editing_attributes()</code> — Always Use</h3>\n<p>The official Elementor API for building HTML attributes is <code>$this-&gt;add_render_attribute()</code>.\n<strong>Always use it</strong> instead of manually concatenating class/id/aria attributes in <code>render()</code>.\nPair with <code>$this-&gt;add_inline_editing_attributes()</code> for any text field that supports live\nediting in the Elementor editor panel.</p>\n<p><strong>Outputting the built attributes — pick by context:</strong> use\n<code>get_render_attribute_string( 'key' )</code> when you are concatenating into a string (as in the\n<code>echo '&lt;h2 ' . ... . '&gt;'</code> example above), and <code>print_render_attribute_string( 'key' )</code> when\nyou are echoing directly inside a <code>?&gt; … &lt;?php</code> HTML block (e.g.\n<code>&lt;h2 &lt;?php $this-&gt;print_render_attribute_string( 'title' ); ?&gt;&gt;</code>). Both are correct Elementor\nAPIs — <code>print_*</code> simply echoes what <code>get_*</code> returns. The widget sub-files use the <code>print_*</code>\nform inside their HTML templates.</p>\n<p>Source: developers.elementor.com/docs/widgets/rendering-html-attribute/\nSource: developers.elementor.com/docs/widgets/rendering-inline-editing/</p>\n","files":[{"path":"debugging.md","sizeBytes":19786,"isText":true},{"path":"elementor-extending.md","sizeBytes":19311,"isText":true},{"path":"elementor-patterns.md","sizeBytes":29147,"isText":true},{"path":"field-notes.md","sizeBytes":59846,"isText":true},{"path":"js-css-standards.md","sizeBytes":8773,"isText":true},{"path":"mariadb.md","sizeBytes":16005,"isText":true},{"path":"offcanvas-ui.md","sizeBytes":8996,"isText":true},{"path":"performance.md","sizeBytes":7038,"isText":true},{"path":"php-standards.md","sizeBytes":10014,"isText":true},{"path":"rest-api.md","sizeBytes":5948,"isText":true},{"path":"scaffolding.md","sizeBytes":11533,"isText":true},{"path":"SKILL.md","sizeBytes":38627,"isText":true},{"path":"svn/references/branching-tagging-merging.md","sizeBytes":12571,"isText":true},{"path":"svn/references/properties-and-metadata.md","sizeBytes":12511,"isText":true},{"path":"svn/references/repository-admin.md","sizeBytes":8190,"isText":true},{"path":"svn/references/troubleshooting.md","sizeBytes":8530,"isText":true},{"path":"svn/svn.md","sizeBytes":15848,"isText":true},{"path":"widget-accordion.md","sizeBytes":14895,"isText":true},{"path":"widget-alert.md","sizeBytes":8111,"isText":true},{"path":"widget-audio.md","sizeBytes":3562,"isText":true},{"path":"widget-button.md","sizeBytes":18229,"isText":true},{"path":"widget-container.md","sizeBytes":19613,"isText":true},{"path":"widget-counter.md","sizeBytes":10048,"isText":true},{"path":"widget-divider.md","sizeBytes":9530,"isText":true},{"path":"widget-elementor-template.md","sizeBytes":11514,"isText":true},{"path":"widget-google-maps.md","sizeBytes":5420,"isText":true},{"path":"widget-heading.md","sizeBytes":8674,"isText":true},{"path":"widget-html.md","sizeBytes":2482,"isText":true},{"path":"widget-icon-box.md","sizeBytes":11459,"isText":true},{"path":"widget-icon-list.md","sizeBytes":10970,"isText":true},{"path":"widget-icon.md","sizeBytes":8112,"isText":true},{"path":"widget-image-box.md","sizeBytes":10971,"isText":true},{"path":"widget-image-carousel.md","sizeBytes":14601,"isText":true},{"path":"widget-image-gallery.md","sizeBytes":7030,"isText":true},{"path":"widget-image.md","sizeBytes":17440,"isText":true},{"path":"widget-menu-anchor.md","sizeBytes":2362,"isText":true},{"path":"widget-nested.md","sizeBytes":13758,"isText":true},{"path":"widget-php-template.md","sizeBytes":12444,"isText":true},{"path":"widget-progress.md","sizeBytes":6532,"isText":true},{"path":"widget-rating.md","sizeBytes":8828,"isText":true},{"path":"widget-read-more.md","sizeBytes":2085,"isText":true},{"path":"widget-shortcode.md","sizeBytes":2843,"isText":true},{"path":"widget-sidebar.md","sizeBytes":2270,"isText":true},{"path":"widget-social-icons.md","sizeBytes":10379,"isText":true},{"path":"widget-spacer.md","sizeBytes":1927,"isText":true},{"path":"widget-star-rating.md","sizeBytes":9770,"isText":true},{"path":"widget-tabs.md","sizeBytes":10400,"isText":true},{"path":"widget-testimonial.md","sizeBytes":12039,"isText":true},{"path":"widget-text-editor.md","sizeBytes":4679,"isText":true},{"path":"widget-text-path.md","sizeBytes":13407,"isText":true},{"path":"widget-toggle.md","sizeBytes":12827,"isText":true},{"path":"widget-video.md","sizeBytes":10675,"isText":true},{"path":"woocommerce.md","sizeBytes":8698,"isText":true},{"path":"wordpress-apis.md","sizeBytes":16786,"isText":true},{"path":"wp-org-guidelines.md","sizeBytes":12306,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-14T20:45:45.143879Z","sha256":"4A055269B8992BF026A353D602FD54C53A974FC72BEAA55E0E7AD1ABB71D5B3E","sizeBytes":234814},"review":null,"source":{"repositoryUrl":"https://github.com/guramzhgamadze/WordPress-Elementor-Skill","path":"wp-elementor-skill","license":"MIT","commit":"3ad244d4b03f9a201a5f8294221649f6f11d61a3","subtreeSha":"E725C04D2196673231D8DCCDB79F2D2547005E5854FD9EF81D845E17298BB3A8","lastSyncedAt":"2026-09-18T13:50:50.962498Z"},"reviewedAt":"2026-09-14T20:49:55.1855Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/guramzhgamadze/WordPress-Elementor-Skill/tree/main/wp-elementor-skill"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install guramzhgamadze-wordpress-elementor-skill@llmmart"},{"target":"git","command":"git clone https://github.com/guramzhgamadze/WordPress-Elementor-Skill.git"}]}