<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: WPVibes</title>
    <description>The latest articles on DEV Community by WPVibes (@wpvibes).</description>
    <link>https://dev.to/wpvibes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978746%2F4a5ecd1b-f11b-4194-b1fb-1a490fef4667.jpg</url>
      <title>DEV Community: WPVibes</title>
      <link>https://dev.to/wpvibes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wpvibes"/>
    <language>en</language>
    <item>
      <title>Complete Guide to Elementor v4 Components</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Wed, 30 Sep 2026 07:24:51 +0000</pubDate>
      <link>https://dev.to/wpvibes/complete-guide-to-elementor-v4-components-2o44</link>
      <guid>https://dev.to/wpvibes/complete-guide-to-elementor-v4-components-2o44</guid>
      <description>&lt;p&gt;Think about the last card, testimonial, or CTA block you built that turned out exactly right, then needed to appear on a second page, a third, and a fourth. Did you copy and paste it every time, rebuild it from scratch, and hope nobody quietly changed the padding on one copy while leaving the rest untouched?&lt;/p&gt;

&lt;p&gt;Elementor v4 Components exist for exactly this moment. Build the block once, drop it anywhere on the site, and when the &lt;strong&gt;original design&lt;/strong&gt; changes, every copy updates with it. For that one copy that genuinely needs to break the pattern, say a product that's gone out of stock, you can detach just that single copy and edit it freely, without disturbing anything else.&lt;/p&gt;

&lt;p&gt;This guide walks through building your first Component, letting each copy show different content, reusing it across the site, and knowing when to detach a copy instead of forcing it to stay in sync. The running example throughout is a Verdure product card, built once and reused across the site, with one copy detached for an out-of-stock product.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Before You Start&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor v4.0 or later.&lt;/strong&gt; Components are a v4 feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://elementor.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Elementor Pro&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt; Components require Pro. The free version does not include this feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Admin permissions.&lt;/strong&gt; Only admins can create or edit Components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Atomic elements only.&lt;/strong&gt; V3 (classic) widgets cannot go inside a Component.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; We assumes that you already have Variables and Classes set up. If you haven't, start with the &lt;a rel="noreferrer noopener" href="https://wpbuilderaddons.com/tutorial/elementor-v4-variables/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Complete Guide to Elementor v4 Variables&lt;/a&gt; and the &lt;a rel="noreferrer noopener" href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Complete Guide to Elementor v4 Classes&lt;/a&gt; first. Components sit on top of everything you build there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Component, and How Is It Different from a Global Widget?
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://elementor.com/help/components-2/" rel="noopener noreferrer"&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/a&gt; is a reusable design block. Its structure and styling stay synced everywhere you use it, but specific content fields inside it, like a product name or an image, can be exposed so each copy shows something different. When one copy genuinely needs to break the pattern, you can detach it and edit it freely without affecting any of the others.&lt;/p&gt;

&lt;p&gt;Elementor already had something similar called a Global Widget. Here's how the two compare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Global Widget&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Design&lt;/td&gt;
&lt;td&gt;Same everywhere&lt;/td&gt;
&lt;td&gt;Same everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content&lt;/td&gt;
&lt;td&gt;Same everywhere&lt;/td&gt;
&lt;td&gt;Different per copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want to change something on just one copy?&lt;/td&gt;
&lt;td&gt;You have to unlink it, which breaks the connection to the others forever, even for a small text change&lt;/td&gt;
&lt;td&gt;Small content changes (text, image, price) are built in, no unlinking needed. For a bigger change (like disabling a button), you can detach just that one copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Things that should always look and say the exact same thing, like a floating CTA banner&lt;/td&gt;
&lt;td&gt;Cards, testimonials, anything that shares a design but needs different content&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most real work, product cards, service cards, testimonials, Components are the better fit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwzzuwv68iq4kknrhwh5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwzzuwv68iq4kknrhwh5.webp" alt="The Verdure homepage showing several product cards, all clearly using the same design" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Build Your First Component?
&lt;/h2&gt;

&lt;p&gt;Building a Component happens in two parts: you design it like any normal element, then you mark the fields that should vary from copy to copy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start on any page. Add a Div block or Flexbox container as the outer wrapper, since every Component needs one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inside it, add your atomic elements. For the Verdure card that's an Image element for the product photo, a Div block for the body, and inside that a Paragraph for the rating, a Heading for the product name, another Paragraph for the price, and a Button for Add to Cart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Style it exactly the way you want every copy of this card to look.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9n3joy28hslgsc0sacpt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9n3joy28hslgsc0sacpt.webp" alt="The finished Verdure product card on the canvas" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Turn it into a Component
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Right-click the wrapper Div block and choose &lt;strong&gt;Create component&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name it something meaningful, for Verdure that's &lt;code&gt;product-card&lt;/code&gt;, and click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Elementor processes this for a moment, then drops you into Component editing mode, a special view for working on the original design.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out the video of &lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-components/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;converting design into components&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Tips:&lt;/strong&gt; If your site also runs Contact Form 7, Elementor Forms, Gravity Forms, or Ninja Forms, enable all of them at once. All submissions appear in the same Form Vibes dashboard - no need to switch between plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Expose Editable Properties?
&lt;/h2&gt;

&lt;p&gt;Right now, every copy of this Component would show identical content: same photo, same name, same price. To let each copy differ, you expose specific fields as properties.&lt;/p&gt;

&lt;p&gt;While still in Component editing mode, click the element you want to make editable, say the product image, and look for a small diamond icon next to its setting in the panel.&lt;/p&gt;

&lt;p&gt;Click it, give the property a clear name, and confirm. That field is now exposed, and every future copy will let editors change it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out the video of &lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-components/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;exposing editable properties&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A typical Verdure product card exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Product image&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Product name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Current price (and old price, if you show a strikethrough)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Star rating&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Button link&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Badge text ("Bestseller," "New," and so on)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else, padding, colors, borders, hover animation, stays locked to the original&amp;nbsp;design.&lt;/p&gt;

&lt;p&gt;Once you're done, click outside the editing overlay to return to your normal page editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbx2g4u4cmpbjdu3at2n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbx2g4u4cmpbjdu3at2n.webp" alt="The Verdure product card in Component editing mode with several diamond-marked properties visible" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Reuse a Component Across Your Site?
&lt;/h2&gt;

&lt;p&gt;Once created, your Component shows up in its own &lt;strong&gt;Components tab&lt;/strong&gt; in the widget panel, right next to the regular Elements tab.&lt;/p&gt;

&lt;p&gt;To place more copies, drag the Component from that tab wherever you need it.&lt;/p&gt;

&lt;p&gt;Click on a copy, and the settings panel now shows only the properties you exposed, not the full list of every style setting, so editors can't accidentally break the design while filling in content.&lt;/p&gt;

&lt;p&gt;Fill in the product photo, name, price, rating, link, and badge for each copy, and repeat for as many products as you need.&lt;/p&gt;

&lt;p&gt;Every copy looks visually identical but can show completely different content. That is the entire point.&lt;/p&gt;

&lt;p&gt;All of these copies stay synced to the original design. Go back into Component editing mode and change the border radius or padding, and every copy updates instantly. Content stays per-copy. Structure and style stay shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  When and How Do You Detach a Component?
&lt;/h2&gt;

&lt;p&gt;Here's a real scenario. You need another product card on the homepage, but this product is out of stock. It should look like the other cards but show a disabled "Out of Stock" button instead of a clickable "Add to Cart" one, greyed out, non-clickable, different text.&lt;/p&gt;

&lt;p&gt;This isn't a content change you can solve by editing a property. Whether a button is clickable at all is a structural difference, and it only applies to this one card. The fix is to detach just this one instance from the original design.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Important: &lt;/strong&gt;Detaching is permanent. Once you detach a copy, it stops receiving any future changes made to the original design, and there is no way to reconnect it. Only detach a copy once you're sure you want it to stop following the original forever.&lt;/p&gt;

&lt;p&gt;Detaching breaks the sync between that one copy and the original design, permanently. The copy keeps its current look, but becomes a normal set of elements you can edit freely from then on. Any future changes to the original design will simply not reach it anymore.&lt;/p&gt;

&lt;p&gt;To detach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Right-click the copy you want to break away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose &lt;strong&gt;Detach from Component&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm the action.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F603vquhmqihj0owjkjp5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F603vquhmqihj0owjkjp5.webp" alt="Style the Elementor Component After Detaching Pattern" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there it behaves like any other element on the page: change the button text, swap its background, remove the link so it isn't clickable, and add anything extra you need, like a "Notify me when back in stock" link.&lt;/p&gt;

&lt;p&gt;Increase the padding on the original design later, and every synced card picks up the change automatically. The detached out-of-stock card stays exactly as it was, because it no longer listens to the master.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Expose a Property or Detach?
&lt;/h2&gt;

&lt;p&gt;The most common mistake with Components is reaching for the wrong tool between these two.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Expose a property when&lt;/th&gt;
&lt;th&gt;Detach when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The variation is only content: text, an image, a link&lt;/td&gt;
&lt;td&gt;The variation is structural: adding or removing elements, changing behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;It's likely to repeat across several copies&lt;/td&gt;
&lt;td&gt;It's a genuine one-off, not a repeating pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want future changes to the original design to keep reaching this copy&lt;/td&gt;
&lt;td&gt;You're fine with this copy no longer receiving those changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Example: a different product name and price per card&lt;/td&gt;
&lt;td&gt;Example: the out-of-stock card with a disabled button&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Get this wrong in either direction and it costs you. Detach too easily and your site fills up with copies that quietly miss every future design update. Refuse to detach when you should and you end up with one overloaded Component full of conditional properties trying to do the job a clean detach would have solved in one click. Most copies on a real site should stay synced. Detach is the exception, not the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Mistakes Should You Avoid?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Building Components before your design system is stable.&lt;/strong&gt; If your Classes and Variables are still changing, every design tweak becomes a Component update too. Settle those first (see the &lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-design-system/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Complete Guide to Elementor v4 Design System&lt;/a&gt;), then build Components on top.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Placing copies before exposing properties.&lt;/strong&gt; If you drop several copies on the site before exposing any properties on the original design, all of them show identical content. Finish the original design&amp;nbsp;first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detaching when you should have exposed a property.&lt;/strong&gt; If you find yourself detaching several copies just to change the same field, that field should have been an exposed property instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Forgetting what detaching costs you.&lt;/strong&gt; Once detached, a copy stops receiving those changes. If a redesign happens later, every detached copy will visibly fall behind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using V3 widgets inside a Component.&lt;/strong&gt; Components only support V4 atomic elements. A classic widget dropped inside simply won't work. Rebuild it with the atomic equivalent instead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;Do Elementor v4 Components work in Elementor Free?&lt;/p&gt;

&lt;p&gt;No. Components require Elementor Pro.&lt;/p&gt;

&lt;p&gt;Can I use Components with V3 (classic) widgets?&lt;/p&gt;

&lt;p&gt;No. Components only support V4 atomic elements. Your existing V3 widgets keep working elsewhere on the site, but they can't be placed inside a Component.&lt;/p&gt;

&lt;p&gt;What is the difference between a Component and a Global Widget?&lt;/p&gt;

&lt;p&gt;A Global Widget shows the same design and the same content everywhere, and changing content on one copy means unlinking it, which permanently breaks the sync. A Component shows the same design everywhere but lets you expose specific content fields so copies can differ, without ever breaking the sync.&lt;/p&gt;

&lt;p&gt;How do I know if a Component has been detached?&lt;/p&gt;

&lt;p&gt;You can't tell just by looking. A detached copy becomes a regular set of elements with no visible marker that it was ever a Component. On larger sites with several editors, it's worth keeping your own note of what's been detached.&lt;/p&gt;

&lt;p&gt;Can I re-attach a detached copy to its Component?&lt;/p&gt;

&lt;p&gt;No. Detaching is one-way. If you want a copy back in sync with the original design, delete it and drop in a fresh copy of the Component instead.&lt;/p&gt;

&lt;p&gt;How many properties can I expose on one Component?&lt;/p&gt;

&lt;p&gt;There's no hard limit, but keep it practical. A Component with too many exposed properties becomes confusing to fill in. If you're exposing a lot, it may be worth splitting it into two smaller Components instead.&lt;/p&gt;

&lt;p&gt;Do&amp;nbsp;original design updates reach copies immediately?&lt;/p&gt;

&lt;p&gt;Yes, for every synced copy across the site. Detached copies are the one exception, they stop receiving updates the moment they're detached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Variables give you tokens. Classes give you reusable styling bundles. The Design System panel gives you visibility over both. Components give you reusable design blocks, the piece that turns all of it from theory into something you actually build with. Design a card once, reuse it everywhere, update the original design when the brand changes, and detach the rare one-off that genuinely needs to break the pattern.&lt;/p&gt;

&lt;p&gt;The takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Components are reusable design blocks with per-copy content.&lt;/strong&gt; The natural next step after Variables and Classes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expose properties for content that varies:&lt;/strong&gt; text, images, links, prices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detach only for structural, one-off changes,&lt;/strong&gt; knowing you give up future changes to the &lt;strong&gt;original design&lt;/strong&gt;&amp;nbsp;in exchange.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Most copies should stay synced.&lt;/strong&gt; Detach is the exception, not the default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Components complete the Elementor v4 design system:&lt;/strong&gt; Variables, Classes, the Design System panel, and Components.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>elementor</category>
      <category>elementorpro</category>
      <category>components</category>
    </item>
    <item>
      <title>Complete Guide to Elementor v4 Design System</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 24 Sep 2026 06:44:39 +0000</pubDate>
      <link>https://dev.to/wpvibes/complete-guide-to-elementor-v4-design-system-7po</link>
      <guid>https://dev.to/wpvibes/complete-guide-to-elementor-v4-design-system-7po</guid>
      <description>&lt;p&gt;As your website grows, keeping its design consistent can become difficult.&lt;/p&gt;

&lt;p&gt;The same colors, fonts, spacing, buttons, and other styles may be used across many pages. If these styles are managed separately, even a small design change can require updating multiple elements manually.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;design system&lt;/strong&gt; helps solve this problem by keeping commonly used design values and styles organized and reusable.&lt;/p&gt;

&lt;p&gt;In Elementor v4, &lt;strong&gt;Variables and Classes&lt;/strong&gt; help you build this system, while the &lt;strong&gt;Design System panel&lt;/strong&gt; gives you one place to view and manage them.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll look at how the Elementor v4 Design System works and how you can use it to manage your website more easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do You Need Before Using the Design System?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor v4.0 or later,&lt;/strong&gt;&amp;nbsp;the Design System panel is a v4 feature&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor v4.1 or later&lt;/strong&gt; for the unified Design System panel this article focuses on. Classes and Variables existed from v4.0 onward, but they lived in two separate managers until &lt;a href="https://github.com/orgs/elementor/discussions/35846" rel="noopener noreferrer"&gt;&lt;strong&gt;v4.1 combined them into one panel&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor Free or Pro,&lt;/strong&gt;&amp;nbsp;the panel is available in both, but Pro is needed to view Variables (Free users see Classes only)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Any WordPress theme&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Use a Design System in Elementor?
&lt;/h2&gt;

&lt;p&gt;On a small website, manually managing styles may work fine. But as you add more pages and elements, it becomes easier for the design to become inconsistent.&lt;/p&gt;

&lt;p&gt;For example, you may end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Different shades of the same brand color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slightly different button styles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inconsistent heading sizes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Different spacing between similar sections&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A design system gives you reusable values and styles that you can use throughout the website.&lt;/p&gt;

&lt;p&gt;This helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep the design consistent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reuse existing styles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make site-wide changes more easily&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep design settings organized as the website grows&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of styling every element separately, you create common design values and styles once and reuse them wherever needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does the Elementor v4 Design System Work?
&lt;/h2&gt;

&lt;p&gt;Elementor v4 mainly uses &lt;strong&gt;Variables and Classes&lt;/strong&gt; to create a reusable design system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;Variables store reusable values such as colors, typography, and sizes.&lt;/p&gt;

&lt;p&gt;For example, you can create a Variable called &lt;code&gt;color-primary&lt;/code&gt; for your main brand color and use it across buttons, headings, backgrounds, and other elements.&lt;/p&gt;

&lt;p&gt;If you change the Variable later, elements using it can use the updated value.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a detailed walkthrough, read our &lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-variables/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Complete Guide to Elementor v4 Variables&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Classes
&lt;/h3&gt;

&lt;p&gt;Classes store reusable groups of styles.&lt;/p&gt;

&lt;p&gt;For example, a &lt;code&gt;btn-primary&lt;/code&gt; Class can contain the background color, text color, padding, border radius, and hover styles for your primary buttons.&lt;/p&gt;

&lt;p&gt;You can apply the same Class to multiple buttons instead of styling each one separately.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn more in our &lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Complete Guide to Elementor v4 Classes&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Together, Variables and Classes form the foundation of your &lt;strong&gt;Elementor design system&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Elementor v4 Design System Panel?
&lt;/h2&gt;

&lt;p&gt;The Design System panel is a dedicated view inside Elementor v4 that displays every Variable and every global Class registered on your site in one screen. It does not add new capabilities. It gives you visibility and control over the system you have already built.&lt;/p&gt;

&lt;p&gt;Three things become possible once you use it regularly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;See everything in one place:&lt;/strong&gt;&amp;nbsp;No need to open the Variables Manager, then separately open the Class Manager, just to find a color you set up weeks ago. It is all visible together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edit any part from one place:&lt;/strong&gt;&amp;nbsp;change a color, adjust a Class, update a size, all without leaving the panel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Present the system as a whole:&lt;/strong&gt;&amp;nbsp;to clients, teammates, or future you looking back on the project six months later.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8pu6o0qxvc4fvjnnk0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8pu6o0qxvc4fvjnnk0s.png" alt="Elementor v4 Design System Panel" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Find the Design System Panel
&lt;/h2&gt;

&lt;p&gt;Inside the Elementor editor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Look at the Top Bar and click the &lt;strong&gt;Design System icon&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The panel opens directly, showing your entire system on one screen.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16ks282ked3p1edtxhhx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16ks282ked3p1edtxhhx.png" alt="Open Elementor Design System" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; This panel is a v4.1 addition. If you are on an earlier v4.0 build, you will still have Classes and Variables individually, through the Class Manager and Variables Manager, but not this combined view. Update to v4.1 or later to get the unified panel described in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can You Manage in the Design System?
&lt;/h2&gt;

&lt;p&gt;The panel organizes your design settings into different sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Colors
&lt;/h3&gt;

&lt;p&gt;The Colors section contains the color Variables used on your website.&amp;nbsp;Using named Variables makes it easier to understand where each color should be used.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-primary&lt;/code&gt;, deep forest green (&lt;code&gt;#0F2F23&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-primary-dark&lt;/code&gt;&amp;nbsp;, hover state (&lt;code&gt;#08201A&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-accent&lt;/code&gt;&amp;nbsp;, warm gold (&lt;code&gt;#C99A3D&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-text&lt;/code&gt;&amp;nbsp;, body text (&lt;code&gt;#24332D&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-text-muted&lt;/code&gt;&amp;nbsp;, secondary text (&lt;code&gt;#6B7770&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-bg&lt;/code&gt;&amp;nbsp;, page background (&lt;code&gt;#FFFFFF&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color-bg-alt&lt;/code&gt;&amp;nbsp;, cream background (&lt;code&gt;#FAF6EE&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodabnb59r4e3emo08gv3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodabnb59r4e3emo08gv3.webp" alt="Elementor Colors Variables" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Typography
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Typography section contains reusable typography values, such as your heading and body fonts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;font-display&lt;/code&gt; (Cormorant Garamond), used for headings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;font-body&lt;/code&gt; (Inter), used for body text&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type-h1&lt;/code&gt; through &lt;code&gt;type-small&lt;/code&gt;&amp;nbsp;, the full type scale&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsud0kgvxfuxskcalzx64.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsud0kgvxfuxskcalzx64.webp" alt="Elementor Typography Variables" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sizes
&lt;/h3&gt;

&lt;p&gt;All size-based Variables: spacing, container widths, image dimensions, radius values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Spacing: &lt;code&gt;space-xs&lt;/code&gt; through &lt;code&gt;space-section&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sizes: &lt;code&gt;hero-image-size&lt;/code&gt;, &lt;code&gt;product-image-height&lt;/code&gt;, &lt;code&gt;container-width&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Radius: &lt;code&gt;radius-sm&lt;/code&gt; through &lt;code&gt;radius-pill&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3118v57sq9fh6qdds9en.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3118v57sq9fh6qdds9en.webp" alt="Elementor Sizes Variables" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Classes
&lt;/h3&gt;

&lt;p&gt;Every global Class you have created, with properties visible on click.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Button: &lt;code&gt;btn&lt;/code&gt;, &lt;code&gt;btn-primary&lt;/code&gt;, &lt;code&gt;btn-accent&lt;/code&gt;, &lt;code&gt;btn-outline&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Card: &lt;code&gt;card&lt;/code&gt;, &lt;code&gt;card-product&lt;/code&gt;, &lt;code&gt;card-category&lt;/code&gt;, &lt;code&gt;card-benefit&lt;/code&gt;, &lt;code&gt;card-blog&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Section: &lt;code&gt;section&lt;/code&gt;, &lt;code&gt;section-alt&lt;/code&gt;, &lt;code&gt;section-dark&lt;/code&gt;, &lt;code&gt;section-header&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Layout: &lt;code&gt;container&lt;/code&gt;, &lt;code&gt;grid-2&lt;/code&gt;, &lt;code&gt;grid-3&lt;/code&gt;, &lt;code&gt;grid-4&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Heading: &lt;code&gt;heading&lt;/code&gt;, &lt;code&gt;heading-h1&lt;/code&gt;, &lt;code&gt;heading-h2&lt;/code&gt;, &lt;code&gt;heading-h3&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Text: &lt;code&gt;eyebrow&lt;/code&gt;, &lt;code&gt;text-muted&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9c5n3mbgo3cs1ar4b65n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9c5n3mbgo3cs1ar4b65n.webp" alt="Elementor Classes" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Important: &lt;/strong&gt;The layered Class pattern covered in the &lt;a rel="noopener noreferrer nofollow" href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/"&gt;Classes article&lt;/a&gt; is visible here:&amp;nbsp;&lt;code&gt;btn&lt;/code&gt; handles structure, &lt;code&gt;btn-primary&lt;/code&gt; handles appearance. Applied together, they produce a fully styled primary CTA.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use the Design System Panel on a Real Website
&lt;/h2&gt;

&lt;p&gt;The Design System becomes especially useful when you need to make changes or review the styles used across your website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 1: Update Your Brand Everywhere in Seconds
&lt;/h3&gt;

&lt;p&gt;Suppose you want to change your primary brand color.&lt;/p&gt;

&lt;p&gt;If your website uses a &lt;code&gt;color-brand&lt;/code&gt; Variable, you can update that Variable instead of manually changing the color on multiple elements.&amp;nbsp;With the Design System panel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open Design System&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;code&gt;color-brand&lt;/code&gt; swatch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change the hex value&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every button using &lt;code&gt;btn-primary&lt;/code&gt;, every dark section, every heading using &lt;code&gt;color-brand&lt;/code&gt;, every accent across every page on the site updates instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 2: Keep Your Classes Organized
&lt;/h3&gt;

&lt;p&gt;As you build more pages, you may create more Classes.&amp;nbsp;Over time, similar Classes can appear, such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;btn-primary&lt;/code&gt; and&amp;nbsp;btn-primary-new.&lt;/p&gt;

&lt;p&gt;Reviewing your Classes helps you find duplicates and reuse existing styles instead of creating unnecessary new ones.&lt;/p&gt;

&lt;p&gt;Try to use clear names so you can easily understand what each Class is for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 3: Present the System to a Client
&lt;/h3&gt;

&lt;p&gt;When handing a project off to a client or presenting to a designer, screen-share the Design System panel instead of opening several different settings screens. It answers the "what does the brand use?" question in seconds.&lt;/p&gt;

&lt;p&gt;For agencies, this might be the single most useful thing about the panel. Design reviews become shorter. Client conversations become clearer. The design system stops being a hidden technical detail and becomes a visible deliverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Mistakes Should You Avoid?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treating the panel as read-only:&lt;/strong&gt; The Design System panel is fully editable. Changes save immediately and propagate everywhere. Use it as your primary editing surface for design system work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not auditing regularly&lt;/strong&gt;:&amp;nbsp;Class libraries grow. Open the panel on a regular schedule, delete unused Classes, consolidate duplicates. A short, regular check prevents a much bigger cleanup later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Building pages without setting up the system first&lt;/strong&gt;:&amp;nbsp;Some teams start designing and hope to extract Variables later. It rarely works cleanly. Set up your core Variables and a few base Classes before the first page. The rest of the system grows naturally from there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skipping the panel during client handoff&lt;/strong&gt;:&amp;nbsp;The Design System panel answers most "what does the brand use?" questions in one screen. Not showing it during handoff means fielding those same questions over email for weeks afterward.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;What is the Elementor v4 Design System?&lt;/p&gt;

&lt;p&gt;The Elementor v4 Design System brings reusable design values and styles together so they are easier to view and manage.&lt;/p&gt;

&lt;p&gt;Does the Elementor v4 Design System panel work in Elementor Free?&lt;/p&gt;

&lt;p&gt;Yes, with limits. Free users see the Classes section fully but see no Variables (Variables require Pro). Pro users see everything.&lt;/p&gt;

&lt;p&gt;Can I edit Variables and Classes directly from the panel?&lt;/p&gt;

&lt;p&gt;Yes. Clicking any Variable or Class opens it for editing inside the panel. Changes save immediately and propagate site-wide.&lt;/p&gt;

&lt;p&gt;What is the difference between Variables and Classes?&lt;/p&gt;

&lt;p&gt;Variables store reusable values such as colors, fonts, and sizes. Classes store reusable groups of styles that can be applied to elements.&lt;/p&gt;

&lt;p&gt;How is the Design System panel different from the Variables Manager and Class Manager?&lt;/p&gt;

&lt;p&gt;The Variables Manager shows only Variables. The Class Manager shows only Classes. The Design System panel shows both together in one organized view. Same underlying data - different way of looking at it.&lt;/p&gt;

&lt;p&gt;Do I need a design system for a small site?&lt;/p&gt;

&lt;p&gt;Not necessarily. A small website may be easy to manage manually. A design system becomes more useful as your website grows and the same styles are reused across multiple pages and elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The Elementor v4 Design System gives you a more organized way to manage repeated design values and styles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variables&lt;/strong&gt; help you reuse values such as colors, typography, and sizes, while &lt;strong&gt;Classes&lt;/strong&gt; help you reuse complete groups of styles.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Design System panel&lt;/strong&gt; brings them together so you can easily see and manage the styles available across your website.&lt;/p&gt;

&lt;p&gt;You don’t need to build a large design system from the beginning. Start with the colors, typography, spacing, and styles you use most often, and expand the system as your website grows.&lt;/p&gt;

&lt;p&gt;This keeps your design consistent while making future changes easier to manage.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>elementorpro</category>
      <category>webdesign</category>
      <category>elementordesign</category>
    </item>
    <item>
      <title>How Do Elementor v4 Classes Work? A Complete Guide</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Tue, 22 Sep 2026 07:18:51 +0000</pubDate>
      <link>https://dev.to/wpvibes/how-do-elementor-v4-classes-work-a-complete-guide-12c2</link>
      <guid>https://dev.to/wpvibes/how-do-elementor-v4-classes-work-a-complete-guide-12c2</guid>
      <description>&lt;p&gt;Styling every button on a website the old way meant setting the same padding, font size, border radius, and hover color on each button, one by one. If you wanted to change how buttons looked later, you had to open every single button and update it by hand. Miss even one, and the site looked inconsistent.&lt;/p&gt;

&lt;p&gt;Elementor v4 Classes solve this problem. You set a button's styling once inside a Class, apply that Class to every button on the site, and every button looks the same automatically. Change the Class one time, and every button using it updates everywhere, instantly.&lt;/p&gt;

&lt;p&gt;Here's something most tutorials don't explain clearly: in Elementor v4, every single element already has a Class attached to it, whether you set one yourself or not. So you're never really starting from zero. The real question is just which Classes are worth turning into reusable ones, and which ones are fine staying as they are.&lt;/p&gt;

&lt;p&gt;This guide covers how Elementor v4 Classes actually work, step by step: how to turn one element's style into something reusable, a simple button pattern you can apply across a whole site, and how to break out of a Class for the rare one-off element that needs to look different.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; If you're on Elementor Pro, we strongly recommend reading the &lt;a rel="noopener noreferrer nofollow" href="https://wpbuilderaddons.com/tutorial/elementor-v4-variables/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;Complete Guide to Elementor v4 Variables&lt;/strong&gt;&lt;/a&gt; first. Classes become significantly more powerful when their property values reference Variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do You Need Before Working With Classes?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor v4.0 or later.&lt;/strong&gt; Classes are a v4 feature, so your site needs to be running v4 with the Atomic Editor active. Older, non-atomic elements don't support Classes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor Free or Pro.&lt;/strong&gt; Classes work in both versions, so you don't need Pro to follow this article start to finish. Pro users get more out of Classes because they can reference Variables inside a Class's properties. For example, a &lt;code&gt;btn-primary&lt;/code&gt; Class can use &lt;code&gt;color-primary&lt;/code&gt; as its background, so changing that one Variable updates every button using the Class.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Any WordPress theme.&lt;/strong&gt; Classes work regardless of which theme is active.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Elementor familiarity.&lt;/strong&gt; You should already know how to open the editor, add elements, and adjust properties.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Are Elementor v4 Classes?
&lt;/h2&gt;

&lt;p&gt;A Class in Elementor v4 is a reusable style bundle you can apply to multiple elements. Instead of setting background color, padding, border radius, and hover state on every button one at a time, you define those styles once inside a Class named &lt;code&gt;btn-primary&lt;/code&gt; and apply it wherever you need it.&lt;/p&gt;

&lt;p&gt;Here's the part most tutorials get wrong: &lt;strong&gt;every element in Elementor v4 automatically has a Class.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you drop a button onto the canvas, Elementor creates a Class named &lt;code&gt;local&lt;/code&gt; and attaches it to that button automatically. Every property you set, background, padding, hover state, sits inside that &lt;code&gt;local&lt;/code&gt; Class. Nothing is truly inline anymore, the way it used to be in older versions.&lt;/p&gt;

&lt;p&gt;This changes how you should think about styling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Classes&lt;/strong&gt; (shown in &lt;strong&gt;pink&lt;/strong&gt; in the Classes field) apply to one specific element only, and always take priority over any Global Class applied to the same element.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Global Classes&lt;/strong&gt; (shown in &lt;strong&gt;green&lt;/strong&gt; in the Classes field) apply to every element that uses them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;The workflow: &lt;/strong&gt;Style locally first, then promote to global once you know you'll reuse it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8fdp5zgvdiz64v2vn6s5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8fdp5zgvdiz64v2vn6s5.webp" alt="The Classes field on a selected element, showing a local class and green global class" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Create Your First Class?
&lt;/h2&gt;

&lt;p&gt;There are actually two ways to create a Class in Elementor v4. The first is faster, the second is better once you already have a styled element you want to reuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  The quick way: type a new Class name directly
&lt;/h3&gt;

&lt;p&gt;Select any element, open the Classes field in the Style tab, and simply type a new name, for example &lt;code&gt;btn&lt;/code&gt;, then press Enter. Elementor creates a brand-new Global Class on the spot (shown in green), ready for you to style. This is the fastest way to start a new reusable Class from scratch.&lt;/p&gt;

&lt;p&gt;You'll use this same process when we [structure Classes for a real site]in this guide, so it's worth getting comfortable with it here first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The reuse way: style locally, then convert to global
&lt;/h3&gt;

&lt;p&gt;Sometimes you style something first and only decide to reuse it afterward. Here's that workflow using a button as the example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add and style an element&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop a button onto the canvas. Notice the Classes field already shows &lt;code&gt;local&lt;/code&gt; attached to it, in pink. Style the button:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Padding: 14px 32px&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typography: 16px, weight 600&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Border radius: 10px&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Background color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Text color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hover background color&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every property sits inside the &lt;code&gt;local&lt;/code&gt; Class right now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51stuivdqwts70qfhsgs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51stuivdqwts70qfhsgs.webp" alt="A styled button with the local class visible in the Classes field" width="799" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Convert the local Class to global&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you want to reuse the styling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click the three-dot menu next to the &lt;code&gt;local&lt;/code&gt; Class name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Convert to global class&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rename it to something meaningful, like &lt;code&gt;btn-accent&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Class name turns from pink to green: it's now global&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#convert-local-class-to-global?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From this moment on, &lt;code&gt;btn-accent&lt;/code&gt; is available site-wide. Apply it to any button and that button inherits the entire styling bundle instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply the Global Class to Another Element
&lt;/h3&gt;

&lt;p&gt;Drop a new button anywhere else. In the Classes field, type &lt;code&gt;btn-accent&lt;/code&gt;, or select it from the dropdown. The new button matches the styling of the original without you touching a single property.&lt;/p&gt;

&lt;p&gt;Now select either button, click on &lt;code&gt;btn-accent&lt;/code&gt; inside the Classes field to make it the active Class, and change its padding from 14px to 16px. Every button using &lt;code&gt;btn-accent&lt;/code&gt; updates automatically across every page, since you just edited the Class itself, not the individual button.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#apply-global-class-element?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How Should You Structure Classes on a Real Site?
&lt;/h2&gt;

&lt;p&gt;Here's a pattern that scales across a production site: the button hierarchy example. Most tutorials show you one Class per element type. Real sites need a bit more structure than that.&lt;/p&gt;

&lt;p&gt;Suppose a homepage hero section has two buttons, a primary CTA and a secondary CTA. Most people would create two Classes: &lt;code&gt;btn-primary&lt;/code&gt; and &lt;code&gt;btn-secondary&lt;/code&gt;. That works, but it duplicates a lot of styling, since both buttons share the same padding, typography, border radius, and hover behavior. Only the colors actually differ.&lt;/p&gt;

&lt;p&gt;A better pattern is a layered Class approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  The btn Class: structural styling
&lt;/h3&gt;

&lt;p&gt;Type &lt;code&gt;btn&lt;/code&gt; into the Classes field and press Enter to create it, the same way you created your first Class earlier. This Class will hold every button's shared structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Padding: 15px 25px&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typography: 14px, weight 600&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Border radius: 20px&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Border width: 2px&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This Class handles the "button-ness" of every button. Nothing color-related lives here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#create-btn-classs?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The btn-primary Class: appearance only
&lt;/h3&gt;

&lt;p&gt;Create a second Class the same way, called &lt;code&gt;btn-primary&lt;/code&gt;, that only sets appearance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Background color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Text color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hover background color&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply both Classes to a primary button: &lt;code&gt;btn&lt;/code&gt; handles structure, &lt;code&gt;btn-primary&lt;/code&gt; handles appearance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#create-btn-primary-class?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The btn-secondary Class: a different appearance
&lt;/h3&gt;

&lt;p&gt;ame idea. A third Class called &lt;code&gt;btn-secondary&lt;/code&gt; that only sets appearance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Background color (transparent or lighter)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Border color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Text color&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hover appearance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply &lt;code&gt;btn&lt;/code&gt; and &lt;code&gt;btn-secondary&lt;/code&gt; to a secondary button.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#create-btn-secondary-class?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result: change the shared padding across every button on the site by updating &lt;code&gt;btn&lt;/code&gt; once. Change the primary brand color by updating &lt;code&gt;btn-primary&lt;/code&gt; once. No duplicated work, no styles quietly drifting apart from each other.&lt;/p&gt;

&lt;p&gt;This layered approach, a structural Class plus an appearance Class, is how design systems stay manageable as a site grows past a handful of buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Override a Class for a One-Off Element?
&lt;/h2&gt;

&lt;p&gt;Sometimes you need a button that follows the same structure and appearance as every other primary button, but needs one thing to look different, say a unique background color for a special CTA on a promotional section. You don't want to create a whole new Class for one element.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Elementor v4's answer: use the &lt;code&gt;local&lt;/code&gt; Class every element already has, and let it override just the one property you need to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the workflow using the button example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Apply both &lt;code&gt;btn&lt;/code&gt; and &lt;code&gt;btn-primary&lt;/code&gt; to the special button, so it inherits all the usual styling: padding, typography, border radius, hover behavior, and the standard primary background color.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Classes field, click on &lt;code&gt;local&lt;/code&gt;, the class every element already has by default, to make it your active editing target.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set your custom background color there.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though &lt;code&gt;btn-primary&lt;/code&gt; already defines a background color for this button, the &lt;code&gt;local&lt;/code&gt; Class always takes the highest priority. Setting the background color on &lt;code&gt;local&lt;/code&gt; overrides whatever &lt;code&gt;btn-primary&lt;/code&gt; says, without touching &lt;code&gt;btn-primary&lt;/code&gt; itself or any other button using it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#override-class?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can do this for any property on any element. The rule of thumb: inherit the Class for everything you want consistent, override locally only what needs to be different.&lt;/p&gt;

&lt;p&gt;This is how design systems stay maintainable without becoming rigid. You get consistency by default and flexibility when you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When You Change a Class?
&lt;/h2&gt;

&lt;p&gt;Here's the moment the whole system pays off.&lt;/p&gt;

&lt;p&gt;Say you built a homepage with several buttons using the &lt;code&gt;btn&lt;/code&gt; and &lt;code&gt;btn-primary&lt;/code&gt; combination, spread across the hero section, the services grid, the case study section, the pricing cards, and the final CTA.&lt;/p&gt;

&lt;p&gt;Your marketing team decides the primary brand color needs to shift from a dark green to a lighter, brighter green.&lt;/p&gt;

&lt;p&gt;Without Classes, that means opening every one of those buttons across the homepage, and every other page they appear on, and manually changing each background color, hover color, and any related border colors.&lt;/p&gt;

&lt;p&gt;With the layered Class system, you do this instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select any button that already has &lt;code&gt;btn-primary&lt;/code&gt; applied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Classes field, click on &lt;code&gt;btn-primary&lt;/code&gt; to make it the active class you're editing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change the background color property.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every button using &lt;code&gt;btn-primary&lt;/code&gt;, across every page of the site, updates instantly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/elementor-v4-classes/#change-settings-in-class?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Check out the Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pro users get even more from this: if &lt;code&gt;btn-primary&lt;/code&gt;'s background references the &lt;code&gt;color-primary&lt;/code&gt; Variable instead of a hardcoded hex value, you don't even need to update the Class. Update the Variable once, and both the Class and every button using it change together.&lt;/p&gt;

&lt;p&gt;That's the Variables and Classes combination in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a Good Starter Class Set to Use?
&lt;/h2&gt;

&lt;p&gt;The button hierarchy is one example. Here's a starter set that covers what most business sites actually need, without over-engineering things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Button&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;btn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared structural styling: padding, typography, border radius, transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;btn-primary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary appearance: brand-color background, white text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;btn-secondary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Secondary appearance: outlined or lighter variant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;btn-accent&lt;/code&gt; (optional)&lt;/td&gt;
&lt;td&gt;Attention-grabbing variant using the secondary brand color&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Card Classes&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;card&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base card style: background, padding, border, hover shadow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;card-icon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Icon wrapper inside cards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;card-featured&lt;/code&gt; (optional)&lt;/td&gt;
&lt;td&gt;Highlighted card variant with primary color accent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Section Classes&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;section&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base section wrapper with standard vertical padding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;section-alt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alternate background for tonal variety&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;section-dark&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dark background for final CTAs and testimonials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;section-header&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centered header block above section content&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Layout Classes&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Content wrapper with max-width and horizontal padding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grid-3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Three-column grid for card sections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grid-4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Four-column grid for metrics and features&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why this specific set?&lt;/strong&gt; It covers what a typical business site needs without turning into a maintenance headache. Start here, and add more Classes only when a real, repeating pattern actually demands one. A sprawling Class library is harder to maintain than a small, well-named one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Naming Conventions Should You Follow?
&lt;/h2&gt;

&lt;p&gt;Class naming feels trivial on day one and becomes important once you're maintaining a growing library. Three principles that hold up over time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name by purpose, not appearance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;code&gt;btn-primary&lt;/code&gt;, since it stays correct even if the brand color changes tomorrow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid&lt;/strong&gt;: &lt;code&gt;btn-blue&lt;/code&gt;, since it becomes wrong the day the brand rebrands&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use consistent prefixes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Group related Classes with the same prefix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Buttons: &lt;code&gt;btn&lt;/code&gt;, &lt;code&gt;btn-primary&lt;/code&gt;, &lt;code&gt;btn-secondary&lt;/code&gt;, &lt;code&gt;btn-accent&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cards: &lt;code&gt;card&lt;/code&gt;, &lt;code&gt;card-icon&lt;/code&gt;, &lt;code&gt;card-featured&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sections: &lt;code&gt;section&lt;/code&gt;, &lt;code&gt;section-alt&lt;/code&gt;, &lt;code&gt;section-dark&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Grid layouts: &lt;code&gt;grid-2&lt;/code&gt;, &lt;code&gt;grid-3&lt;/code&gt;, &lt;code&gt;grid-4&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefixes make Classes easier to find in the Class Manager and reduce naming collisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use hyphen-separated lowercase&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;code&gt;btn-primary-outline&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid&lt;/strong&gt;: &lt;code&gt;BtnPrimaryOutline&lt;/code&gt;, &lt;code&gt;btn_primary_outline&lt;/code&gt;, &lt;code&gt;btnPrimaryOutline&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consistency here matters more than which specific convention you pick. Choose one style and stick with it across every Class.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Mistakes Should You Avoid With Classes?
&lt;/h2&gt;

&lt;p&gt;A few habits worth avoiding before they cost you time later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never promoting local Classes to global.&lt;/strong&gt; If every element has its own unique &lt;code&gt;local&lt;/code&gt; Class, you have zero reusability. Every button, card, and section becomes a one-off. The Class Manager exists specifically to enable reuse. Use it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Promoting too early.&lt;/strong&gt; Not every styled element needs to become a global Class. One-off decorative elements can stay local. Promote only once you're confident you'll reuse the styling elsewhere.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Duplicating structure across appearance Classes.&lt;/strong&gt;&amp;nbsp;If your &lt;code&gt;btn-primary&lt;/code&gt; and &lt;code&gt;btn-secondary&lt;/code&gt; both include padding, typography, and border radius, you're duplicating work. Split into a shared &lt;code&gt;btn&lt;/code&gt; Class for structure, plus appearance-only Classes (&lt;code&gt;btn-primary&lt;/code&gt;, &lt;code&gt;btn-secondary&lt;/code&gt;). One source of truth for structure, one for each appearance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not using the local override option.&lt;/strong&gt;&amp;nbsp;When you need a one-off variation, don't create a whole new Class for it. Apply the base Class for consistency, then switch just the properties that need to change back to local. This keeps your Class library lean and your special cases flexible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not using Variables inside Classes (Pro users).&lt;/strong&gt;&amp;nbsp;If you're on Pro and hardcoding hex values inside Classes, you're missing the point. A &lt;code&gt;btn-primary&lt;/code&gt; Class should reference &lt;code&gt;color-primary&lt;/code&gt;, a Variable, as its background, rather than &lt;code&gt;#0F4C81&lt;/code&gt; directly. That way, changing your brand color updates both the Variable and every Class using it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;Do Elementor v4 Classes work in Elementor Free?&lt;/p&gt;

&lt;p&gt;Yes. Classes are a Free-and-Pro feature. Pro adds the ability to reference Variables inside Class properties, but the core Class system works identically in both versions.&lt;/p&gt;

&lt;p&gt;What's the difference between a local and a global Class?&lt;/p&gt;

&lt;p&gt;A local Class (pink) applies to one specific element only. A global Class (green) can be applied to any element site-wide, and changing the global Class updates every element using it. Every element starts with a local Class called &lt;code&gt;local&lt;/code&gt;&amp;nbsp;. You promote it to global when you want to reuse the styling.&lt;/p&gt;

&lt;p&gt;Can I apply multiple Classes to the same element?&lt;/p&gt;

&lt;p&gt;Yes. This is exactly how the layered Class pattern works. Apply btn for structure and btn-primary for appearance to the same button. Elementor merges the properties from all applied Classes, with later Classes taking priority when properties conflict.&lt;/p&gt;

&lt;p&gt;How do I override a Class for a specific element without creating a new Class?&lt;/p&gt;

&lt;p&gt;Apply the Class you want to inherit from, then switch specific properties back to local using the toggle next to the property in the panel. The element inherits everything from the Class except the properties you overrode locally.&lt;/p&gt;

&lt;p&gt;Can I convert a global Class back to local?&lt;/p&gt;

&lt;p&gt;Yes, but be careful. This breaks the connection between all elements that were using that global Class. They keep the styling but each becomes a separate local Class going forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many Classes should I create for a typical site?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's no fixed number to hit. A small, well-organized set is usually enough: the starter set in this guide (buttons, cards, sections, and layout) is a good place to start. Add more only when a real, repeating pattern actually demands it.&lt;/p&gt;

&lt;p&gt;What happens if I delete a global Class that elements are using?&lt;/p&gt;

&lt;p&gt;Those elements revert to their default, unstyled state. Elementor warns you before deletion, but the change is hard to undo. You'd need to reapply the Class or restyle each element by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Elementor v4 Classes replace the old "style every element from scratch" workflow with a reusable system. Set up your Classes once, and every new page inherits consistent styling automatically. Update a Class later, and every element using it updates instantly across the entire site.&lt;/p&gt;

&lt;p&gt;The takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every element already has a Class.&lt;/strong&gt; You're not creating from scratch, you're deciding scope: local or global.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Classes stay unique to one element. Global Classes reuse across the site,&lt;/strong&gt; and a Local Class always overrides a Global one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You can create a Global Class two ways:&lt;/strong&gt; type a new name directly, or style locally first and convert it once you know you'll reuse it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Layer your Classes.&lt;/strong&gt; Split structure (&lt;code&gt;btn&lt;/code&gt;) from appearance (&lt;code&gt;btn-primary&lt;/code&gt;) to avoid duplication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use the local override option&lt;/strong&gt; for one-off variations instead of creating single-use Classes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combine Classes with Variables (Pro)&lt;/strong&gt; for the most powerful, maintainable design system.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>elementorclasses</category>
      <category>reusablestyle</category>
      <category>webdesign</category>
    </item>
    <item>
      <title>Elementor v4 Variables: How to Create and Use Them</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 17 Sep 2026 07:23:50 +0000</pubDate>
      <link>https://dev.to/wpvibes/elementor-v4-variables-how-to-create-and-use-them-oh</link>
      <guid>https://dev.to/wpvibes/elementor-v4-variables-how-to-create-and-use-them-oh</guid>
      <description>&lt;p&gt;When the same color, font, or spacing value is used across multiple pages, changing it manually can take a lot of time. You have to find every place where that value is used and update it individually.&lt;/p&gt;

&lt;p&gt;Elementor v4 Variables make this easier. You can define a value once, use it across your site, and update it later from one place. Any element using that Variable updates automatically.&lt;/p&gt;

&lt;p&gt;In this guide, you'll learn how Elementor v4 Variables work, how to create your first Variable, and how to use Variables on actual Elementor elements. We'll then build a simple set of color, typography, and spacing Variables that you can reuse throughout your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Need Before Setting Up Variables
&lt;/h2&gt;

&lt;p&gt;Before following the steps in this guide, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor Pro:&lt;/strong&gt;&amp;nbsp;Variables require Elementor Pro.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elementor v4.0 or later:&lt;/strong&gt;&amp;nbsp;Variables are part of Elementor's v4 design system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Elementor familiarity:&lt;/strong&gt;&amp;nbsp;you should know how to open the Elementor editor and select an element.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Elementor v4 uses the Atomic Editor, which is where Variables are available. Existing non-atomic elements can still work alongside the newer v4 features, so you don't need to rebuild an existing site just to start using Variables.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; If your site also runs Contact Form 7, Elementor Forms, Gravity Forms, or Ninja Forms, enable all of them at once. All submissions appear in the same Form Vibes dashboard — no need to switch between plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Elementor v4 Variables?
&lt;/h2&gt;

&lt;p&gt;A Variable is a named value defined once and referenced everywhere. Instead of typing &lt;code&gt;#3B82F6&lt;/code&gt; into 40 different color pickers across a site, create a Variable called &lt;code&gt;color-primary-dark&lt;/code&gt; and set its value to &lt;code&gt;#3B82F6&lt;/code&gt;. Every element that needs that color references the Variable instead of the hex code.&lt;/p&gt;

&lt;p&gt;When the color needs to change, update the Variable value from &lt;code&gt;#3B82F6&lt;/code&gt; to &lt;code&gt;#2563EB&lt;/code&gt;. Every element referencing that Variable updates automatically. No hunting, no missed elements, no manual find-and-replace.&lt;/p&gt;

&lt;p&gt;Design system professionals call these &lt;strong&gt;design tokens&lt;/strong&gt;. Elementor calls them Variables. Same concept, more approachable name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elementor v4 supports three types of Variables:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Colors:&lt;/strong&gt; hex values used in any color picker (backgrounds, text, borders, shadows)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Typography:&lt;/strong&gt; font families used in any font-family field&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sizes:&lt;/strong&gt; numeric values with units, used for spacing, font sizes, borders&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you one place to manage commonly reused design values instead of setting them manually on individual elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiefkvpauv8ng8vwqdmn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiefkvpauv8ng8vwqdmn.webp" alt="Elementor Variable Manager to Create Variables" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Set Up Your First Variables
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a single color Variable and using it on an Elementor element. This will help you understand how the complete Variables workflow works before you create a larger set for your site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Open the Variables Manager
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inside the Elementor editor:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the top toolbar, click the &lt;strong&gt;Design System icon&lt;/strong&gt; (the droplet-shaped icon on the right).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clicking this icon opens the &lt;strong&gt;Variables Manager&lt;/strong&gt;, a dedicated panel where every Variable used across your site lives in one place. From here you can create new Variables, rename or edit existing ones, and adjust their values.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fju1ohaql22oa9uxeuces.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fju1ohaql22oa9uxeuces.webp" alt="Elementor Design Icon to Open Variable Manager" width="799" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create Color Variables
&lt;/h3&gt;

&lt;p&gt;Colors are the most-used Variable type. Start with a six-Variable core set that covers most brand needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;color-primary-dark:&lt;/strong&gt;&amp;nbsp;main brand color, buttons, links, accents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;color-primary-light:&lt;/strong&gt;&amp;nbsp;lighter variant for hover states, subtle backgrounds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;color-secondary-dark:&lt;/strong&gt;&amp;nbsp;secondary brand color, complementary accents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;color-secondary-light:&lt;/strong&gt;&amp;nbsp;lighter secondary variant&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;text-dark:&lt;/strong&gt;&amp;nbsp;primary text color (usually dark gray, not pure black)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;text-light:&lt;/strong&gt;&amp;nbsp;text color on dark backgrounds (usually white or near-white)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create each Variable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;+&lt;/strong&gt; (add) button inside the Variables Manager.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Color&lt;/strong&gt; as the type, since we're creating color Variables in this case, and enter a clear, descriptive Variable name, for example &lt;code&gt;color-primary-dark&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the exact hex value using the color picker, either by typing the hex code or dragging the picker to color, click &lt;strong&gt;Save&lt;/strong&gt;. Repeat this same process for all six Variables.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1y1euncfspf1jrvnnbr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1y1euncfspf1jrvnnbr.webp" alt="Create Color Variable in Elementor" width="799" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create Typography Variables
&lt;/h3&gt;

&lt;p&gt;Typography Variables let a font family change site-wide from one place. Three Variables cover most needs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;font-heading&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;font family for all headings (h1 through h6)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;font-body&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;font family for body text and paragraphs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;text-base-size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;base body font size (usually 16px or 18px)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create these using the same workflow you just followed for the color Variables. Click the &lt;strong&gt;+ button&lt;/strong&gt; inside the Variables Manager, but this time select &lt;strong&gt;Typography&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter a clear, descriptive name for each Variable. Set the corresponding value (a font family or weight), then click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat this for every Typography Variable you need.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff2xf5ak9dlck438smn16.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff2xf5ak9dlck438smn16.webp" alt="Create Typography Variable in Elementor" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Create Size Variables
&lt;/h3&gt;

&lt;p&gt;Size Variables are useful for values that you repeatedly use for spacing or text sizes. Four spacing Variables handle it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;space-section&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;vertical padding between major sections (80-120px on desktop)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-block&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;space between content groups within a section (40-60px)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-element&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;space between individual elements (16-24px)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-small&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;tight spacing for icon gaps, tag margins (8-12px)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start creating a Size Variables by selecting the &lt;strong&gt;Size&lt;/strong&gt; as the type from the options shown.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter a clear, descriptive name for the Variable, for example &lt;code&gt;spacing-section-lg&lt;/code&gt; or &lt;code&gt;radius-card&lt;/code&gt;, following the same naming pattern as your other Variables so it stays easy to find later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the value in pixels (or rem, depending on what you're defining), then click &lt;strong&gt;Save&lt;/strong&gt;. Repeat this for each Size Variable you need.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekb75kdkorhizk7fbkdr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekb75kdkorhizk7fbkdr.webp" alt="Create Size Variable in Elementor" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use Variables on Elementor Atomic Elements
&lt;/h2&gt;

&lt;p&gt;Creating Variables is only the first part. Now you need to connect them to the properties of your Elementor elements.&lt;/p&gt;

&lt;p&gt;The general process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Select an element in the Elementor editor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the property you want to style.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;Variables icon&lt;/strong&gt; next to the property field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the Variable you want to use.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The property will now reference the Variable instead of relying on a manually entered value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a Color Variable on a Button
&lt;/h3&gt;

&lt;p&gt;Suppose you want all your primary buttons to use &lt;code&gt;color-primary-dark&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Select your button and open its background color setting.&lt;/p&gt;

&lt;p&gt;Instead of entering a HEX value manually, select:&amp;nbsp;color-primary-dark.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The button background is now connected to that Variable. You can use the same Variable for other primary buttons across your website.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsr8stx5qdke3h9yjgvn3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsr8stx5qdke3h9yjgvn3.webp" alt="Apply Variable Colors to Atomic Button Element in Elementor" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; If your site also runs Contact Form 7, Elementor Forms, Gravity Forms, or Ninja Forms, enable all of them at once. All submissions appear in the same Form Vibes dashboard — no need to switch between plugins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Hero Section with Variables
&lt;/h3&gt;

&lt;p&gt;Here's the demo scenario - a hero section built entirely using Variables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Section background&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;color-secondary-light&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main heading text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text-dark&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Button background&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;color-primary-dark&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Button text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text-light&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Button hover background&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;color-primary-light&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every color decision on that hero section references a Variable instead of a hardcoded hex value. Nothing is inline. Nothing is one-off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7z06rmaon32dtltf9maa.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7z06rmaon32dtltf9maa.webp" alt="Creating Hero Section in Elementor and Use Variables" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repeat this pattern for every section on the page. Buttons use &lt;code&gt;color-primary-dark&lt;/code&gt;. Headings use &lt;code&gt;text-dark&lt;/code&gt;. Section backgrounds alternate between &lt;code&gt;color-bg&lt;/code&gt; and &lt;code&gt;color-secondary-light&lt;/code&gt;. Nothing is hardcoded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When You Change a Variable?
&lt;/h2&gt;

&lt;p&gt;This is where Variables become especially useful.&lt;/p&gt;

&lt;p&gt;Open the Variables Manager. Change &lt;code&gt;color-primary-dark&lt;/code&gt; from its current hex value to a different color and save.&lt;/p&gt;

&lt;p&gt;Every element on the page that references &lt;code&gt;color-primary-dark&lt;/code&gt;&amp;nbsp;- the button, the heading accents, the icon colors, the link hover states - updates instantly. No page reloads. No hunting. No missed elements.&lt;/p&gt;

&lt;p&gt;Now multiply this across a 20-page site. A brand color change&amp;nbsp;that used to take hours now takes seconds.&lt;/p&gt;

&lt;p&gt;That is why Variables matter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fls3csly7eq472pt6zt7q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fls3csly7eq472pt6zt7q.webp" alt="Before Changing the Variable Value" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7z04esu9prvfsowlz77.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7z04esu9prvfsowlz77.webp" alt="After Changing the Variable Value" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Starter Variable Set
&lt;/h2&gt;

&lt;p&gt;The Variables used above cover the basics. Here's a fuller starter list for a real site, enough for most business websites without going overboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colors&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;color-primary&lt;/td&gt;
&lt;td&gt;Main brand color - buttons, links, primary accents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-primary-dark&lt;/td&gt;
&lt;td&gt;Darker variant - hover states, active states&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-primary-light&lt;/td&gt;
&lt;td&gt;Lighter variant - subtle backgrounds, highlights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-secondary&lt;/td&gt;
&lt;td&gt;Secondary brand color - complementary accents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-text&lt;/td&gt;
&lt;td&gt;Main text color - usually dark gray like #1F2937&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-text-light&lt;/td&gt;
&lt;td&gt;Secondary text - captions, metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-bg&lt;/td&gt;
&lt;td&gt;Page background - usually white or near-white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-bg-alt&lt;/td&gt;
&lt;td&gt;Alternate section background - slight tonal difference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-border&lt;/td&gt;
&lt;td&gt;Borders and dividers - light gray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color-white&lt;/td&gt;
&lt;td&gt;Pure white - for text on dark backgrounds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Typography&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;font-heading&lt;/td&gt;
&lt;td&gt;Font family for headings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;font-body&lt;/td&gt;
&lt;td&gt;Font family for body text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;text-base-size&lt;/td&gt;
&lt;td&gt;Base body font size - 16 or 18px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Spacing&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;space-section&lt;/td&gt;
&lt;td&gt;Section vertical padding - 80-120px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;space-block&lt;/td&gt;
&lt;td&gt;Content group spacing - 40-60px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;space-element&lt;/td&gt;
&lt;td&gt;Individual element spacing - 16-24px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;space-small&lt;/td&gt;
&lt;td&gt;Tight spacing - 8-12px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;Five mistakes worth knowing before they cost time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Naming Variables by appearance instead of purpose. A Variable called blue-primary becomes wrong the moment the brand changes to green. color-primary is always correct regardless of the actual color. Name by function, not appearance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating too many Variables upfront. It's tempting to plan a huge Variable library on day one. Resist. Start with 15-20 Variables, add more only when a real repeating pattern demands them. Fewer, well-named Variables beat 50 half-remembered ones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mixing two naming styles. Some people name Variables by purpose, like color-primary. Others name them by value, like blue-600. Both work, but pick one style and stick with it. Mixing the two gets confusing fast.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Forgetting Variables need Pro. The single most common frustration for Free users following v4 tutorials is discovering Variables aren't available in their version. If a project is Free-only, Global Classes work but Variables don't. Plan for this before onboarding a client on Free.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not using Variables inside Classes. Variables and Global Classes are designed to work together. A button Class should reference color-primary as its background value, not a hardcoded hex. This two-layer system is what makes the v4 design system powerful.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do Variables work in Elementor Free?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Variables require Elementor Pro. The free version supports Global Classes but not Variables. This is the most common frustration among Free users trying to follow v4 tutorials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens to my old v3 Global Colors when I migrate to v4?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your old v3 Global Colors and Fonts keep working as they are. v4 also lets you link your new Variables back to those old colors and fonts, so both can work side by side while you switch over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many Variables should I create for a typical site?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enough to cover your colors, a font or two, and a few spacing sizes, without repeating yourself. The starter list earlier in this guide is a good template. Add more only when you need to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between Elementor Variables and v3 Global Colors?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both allow you to reuse values, but Variables are part of Elementor v4's broader design system. They are designed to work alongside features such as Global Classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I rename a Variable after creating it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Elementor updates references automatically when a Variable is renamed. There's no orphaning of elements that were using the old name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Variables inside custom CSS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Elementor outputs Variables as standard CSS custom properties &lt;code&gt;(--e-color-primary-dark&lt;/code&gt;, for example). Custom CSS can reference them directly with &lt;code&gt;var(--e-color-primary-dark)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do Variables work with the free Atomic Editor?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Atomic Editor is available in Free, but Variables are Pro-only. Free users can build atomic pages with Global Classes; they can't use Variables inside those Classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Elementor v4 Variables replace the old way of hunting through pages to update one color. Setup takes less time than you'd think, and it pays off more with every page you build.&lt;/p&gt;

&lt;p&gt;The takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start with a small set of color Variables: primary and secondary colors, each in a light and dark version, plus text colors for light and dark backgrounds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add font and spacing Variables for a full starter se&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name Variables by what they do, not what they look like&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The real value is that one change updates everything&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Variables come first. Everything else builds on them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>elementor</category>
      <category>design</category>
      <category>websitedesign</category>
    </item>
    <item>
      <title>Best Elementor Popup Ideas to Engage Visitors and Increase Conversions</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 10 Sep 2026 06:41:27 +0000</pubDate>
      <link>https://dev.to/wpvibes/best-elementor-popup-ideas-to-engage-visitors-and-increase-conversions-25ac</link>
      <guid>https://dev.to/wpvibes/best-elementor-popup-ideas-to-engage-visitors-and-increase-conversions-25ac</guid>
      <description>&lt;p&gt;Popups can do much more than collect email addresses. You can use them to promote an offer, answer common questions, highlight free shipping, collect quote requests, re-engage visitors, or show the next step after someone submits a form.&lt;/p&gt;

&lt;p&gt;But creating the popup is only one part of the process.&lt;/p&gt;

&lt;p&gt;The bigger question is: when should it appear?&lt;/p&gt;

&lt;p&gt;A newsletter offer might work well a few seconds after someone lands on your website. A free shipping offer may be more relevant after they reach your products. And a quote form probably shouldn't appear automatically at all.&lt;/p&gt;

&lt;p&gt;Elementor Popup Builder gives you several ways to control this. The challenge isn't finding a trigger, it's choosing the one that fits the purpose of your popup and the visitor's current action.&lt;/p&gt;

&lt;p&gt;In this guide, we'll look at seven practical Elementor popup examples, the trigger that makes sense for each one, and where you might position the popup so it gets attention without unnecessarily interrupting the visitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What You'll Need&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Elementor Popup Builder is available with Elementor Pro. This guide assumes you already know how to create and design a basic popup, so we'll focus mainly on choosing the right trigger, position, and setup for each use case.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; If you haven't created a popup before, our complete guide covers that first:&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://wpbuilderaddons.com/tutorial/elementor-popup-builder-guide/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Elementor Popup Builder: How to Create Popups That Don't Annoy Visitors&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Different Ways to Trigger a Popup in Elementor?
&lt;/h2&gt;

&lt;p&gt;Elementor gives you several ways to decide when a popup should appear.&lt;/p&gt;

&lt;p&gt;You can automatically show a popup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;after the page has been open for a specified amount of time,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;after the visitor scrolls through part of the page,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;when they reach a particular element,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;after a certain number of clicks,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;after they stop interacting with the page, or&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;when they appear ready to leave.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also open a popup after a more specific visitor action, such as clicking a particular button or link, or successfully submitting a form.&lt;/p&gt;

&lt;p&gt;The important part isn't simply knowing that these options exist. It's understanding which one fits the popup you're creating.&lt;/p&gt;

&lt;p&gt;For example, showing a newsletter offer on Page Load can make sense because it's relevant to most visitors. But a Request a Quote form should usually wait until someone actually asks for a quote.&lt;/p&gt;

&lt;p&gt;We'll see both types of situations in the examples below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are All the Elementor Popup Triggers?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;When the Popup Opens&lt;/th&gt;
&lt;th&gt;Often Useful For&lt;/th&gt;
&lt;th&gt;Suggested Position&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;On Page Load&lt;/td&gt;
&lt;td&gt;After the page loads and the specified delay passes&lt;/td&gt;
&lt;td&gt;Newsletter signup, welcome offer&lt;/td&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On Scroll&lt;/td&gt;
&lt;td&gt;After the visitor scrolls a specified amount&lt;/td&gt;
&lt;td&gt;FAQ, help prompt, secondary CTA&lt;/td&gt;
&lt;td&gt;Bottom-right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On Scroll to Element&lt;/td&gt;
&lt;td&gt;When the visitor reaches a specific element&lt;/td&gt;
&lt;td&gt;Contextual offers, product promotions&lt;/td&gt;
&lt;td&gt;Right-middle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On Click&lt;/td&gt;
&lt;td&gt;After a specified number of clicks&lt;/td&gt;
&lt;td&gt;Engagement-based offers&lt;/td&gt;
&lt;td&gt;Depends on the popup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After Inactivity&lt;/td&gt;
&lt;td&gt;After the visitor stops interacting for a specified time&lt;/td&gt;
&lt;td&gt;Reminder, re-engagement offer&lt;/td&gt;
&lt;td&gt;Bottom-left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On Page Exit Intent&lt;/td&gt;
&lt;td&gt;When mouse activity suggests the visitor may leave&lt;/td&gt;
&lt;td&gt;Last-chance message, re-engagement offer&lt;/td&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AdBlock Detection&lt;/td&gt;
&lt;td&gt;When the visitor's browser is detected using an ad blocker, within a set number of seconds&lt;/td&gt;
&lt;td&gt;Ad-blocker notices, requests to whitelist the site&lt;/td&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These positions aren't fixed rules. They are practical starting points based on how much attention the message needs and how much it should interrupt the visitor.&lt;/p&gt;

&lt;p&gt;A centered popup demands more attention, while a popup in the corner lets visitors continue viewing most of the page. The right combination depends on what you're showing and how important the message is.&lt;/p&gt;

&lt;p&gt;In the examples below, we'll look at different popup scenarios and choose the trigger, opening method, and position that best fit each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before You Choose a Trigger: Conditions, Triggers, and Advanced Rules
&lt;/h2&gt;

&lt;p&gt;There are three Elementor settings you'll regularly see when publishing a popup: Conditions, Triggers, and Advanced Rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conditions: where can the popup appear?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, you might make a popup available across your entire site, only on the homepage, or on specific pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Triggers: what visitor behavior makes it appear?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This could be Page Load, Scroll, Inactivity, or Exit Intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Rules: are there additional requirements?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, you might limit how often the popup appears or show it only after someone has viewed a certain number of pages.&lt;/p&gt;

&lt;p&gt;So a popup setup might look like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Condition&lt;/strong&gt;: Homepage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;: On Page Load after a delay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Rule&lt;/strong&gt;: Limit how often it appears.&lt;/p&gt;

&lt;p&gt;With that distinction clear, let's look at some actual popup scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Newsletter Discount Popup: On Page Load
&lt;/h2&gt;

&lt;p&gt;Suppose you're running a clothing store and want to encourage first-time visitors to join your email list.&lt;/p&gt;

&lt;p&gt;In return, you're offering 10% off their first order.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Get 10% Off Your First Order&lt;/p&gt;

&lt;p&gt;Join our list for new arrivals, offers, and style updates.&lt;/p&gt;

&lt;p&gt;[Email Address]&lt;/p&gt;

&lt;p&gt;GET 10% OFF&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;: On Page Load with a delay.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position&lt;/strong&gt;: Center.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabpvgtbw273ct8nz6m2k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabpvgtbw273ct8nz6m2k.jpg" alt="Newsletter Elementor Popup"&gt;&lt;/a&gt;   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use On Page Load?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The newsletter offer isn't tied to a particular product or section of the page. It's something that could be relevant to almost any visitor.&lt;/p&gt;

&lt;p&gt;That makes On Page Load a good starting point. However, Page Load doesn't mean the popup needs to appear the moment the page opens.&lt;/p&gt;

&lt;p&gt;Instead, give visitors a little time to look around: visitor lands on page, browses briefly, newsletter popup appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Should You Position It?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this kind of offer, a centered popup works well because the discount is the primary message and requires an action from the visitor.&lt;/p&gt;

&lt;p&gt;Keep the design focused. A short headline, one clear benefit, an email field, and a CTA are usually enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set the Trigger in Elementor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once your popup is ready, click Publish and configure the Display Conditions for the pages where you want it to be available.&lt;/p&gt;

&lt;p&gt;Then go to Triggers and enable &lt;strong&gt;On Page Load&lt;/strong&gt;. Set the number of seconds you want Elementor to wait before showing the popup.&lt;/p&gt;

&lt;p&gt;You can then use Advanced Rules if you want more control over how frequently the popup appears.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwa3noqdt0htybhri4nrq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwa3noqdt0htybhri4nrq.webp" alt="Elementor Popup on Page Load Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When This Combination Works Well&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Page Load with a short delay is worth considering for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Newsletter signup offers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;First-order discounts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Welcome offers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Site-wide promotions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the message only becomes relevant after someone starts exploring the page, a scroll-based trigger can be more useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. FAQ Help Popup: On Scroll
&lt;/h2&gt;

&lt;p&gt;Visitors browsing an online store often have quick questions about shipping, returns, sizing, or orders. Instead of making them leave the page and search for an FAQ section, you can bring a few common answers directly to them.&lt;/p&gt;

&lt;p&gt;For this example, we'll use a chatbot-style FAQ popup that appears after the visitor has scrolled through 50% of the page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can we help?&lt;/p&gt;

&lt;p&gt;Find quick answers about shipping, returns, sizing, and orders.&lt;/p&gt;

&lt;p&gt;EXPAND A QUESTION → GET AN ANSWER → CONTACT US IF YOU NEED MORE HELP&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;: On Scroll.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position&lt;/strong&gt;: Bottom-right.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58f31ibxi9hgfgy9p4ld.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58f31ibxi9hgfgy9p4ld.jpg" alt="Elementor Popup On Scroll"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use On Scroll?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Someone who has just landed on the page probably doesn't need an FAQ prompt yet.&lt;/p&gt;

&lt;p&gt;But once they've scrolled through part of the page, they've shown some level of engagement. At that point, offering additional help feels more natural: visitor starts browsing, scrolls through the page, FAQ popup appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Bottom-right Instead of Center?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The FAQ prompt is useful, but it isn't the main purpose of the page.&lt;/p&gt;

&lt;p&gt;Putting it in the bottom-right corner allows the visitor to notice the help option while continuing to see the content behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the Scroll Trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Publish the popup and open Triggers. Enable On Scroll.&lt;/p&gt;

&lt;p&gt;For someone scrolling down the page, choose the appropriate direction and decide how much of the page they should explore before the popup appears.&lt;/p&gt;

&lt;p&gt;The exact scroll amount depends on your page. Test the trigger on the actual page instead of choosing a percentage in isolation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7goqmjgyf341vzsv641d.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7goqmjgyf341vzsv641d.webp" alt="Elementor Popup On Scroll Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When This Combination Works Well&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On Scroll is useful for FAQ prompts, help options, related content, secondary CTAs, and offers on long pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Free Shipping Offer: On Scroll to Element
&lt;/h2&gt;

&lt;p&gt;Sometimes a popup is related to a specific section, not a percentage of the page.&lt;/p&gt;

&lt;p&gt;Suppose our clothing store homepage has a New Arrivals section. When visitors reach those products, we want to tell them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Free Shipping on Orders Over $75&lt;/p&gt;

&lt;p&gt;Found something you like? Get free shipping when your order reaches $75.&lt;/p&gt;

&lt;p&gt;SHOP NEW ARRIVALS&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;: On Scroll to Element.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position&lt;/strong&gt;: Right-middle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbsjdjec3ps41o2aucjsg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbsjdjec3ps41o2aucjsg.jpg" alt="Elementor Popup On Scroll to Element"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use On Scroll to Element?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When someone reaches New Arrivals, they're actively looking at products. That's a better time to introduce a purchase-related incentive.&lt;/p&gt;

&lt;p&gt;Instead of "show the popup after the visitor scrolls 50%," this becomes "show the popup when the visitor reaches the New Arrivals section." The flow: visitor browses homepage, reaches New Arrivals, Free Shipping popup appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Position It on the Right-middle?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We placed the popup on the right-middle side so the shipping offer remains visible alongside the product section without taking over the entire screen.&lt;/p&gt;

&lt;p&gt;Unlike a centered promotional popup, it lets visitors continue looking through the New Arrivals while still noticing the offer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set Up the Target Element&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit the page containing the New Arrivals section and add an identifier under its&amp;nbsp;&lt;strong&gt;Advanced settings -&amp;gt; CSS Classes&lt;/strong&gt;, for example&amp;nbsp;&lt;code&gt;new-arrivals&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fetq1headrv3zy5r3hxpd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fetq1headrv3zy5r3hxpd.webp" alt="Set Selector to Element"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then return to the popup and enable On Scroll to Element. Add the selector for the element you want Elementor to watch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foz0kwlbhx4t8rieska6n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foz0kwlbhx4t8rieska6n.webp" alt="Elementor Popup On Scroll to Element Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  On Scroll vs. On Scroll to Element
&lt;/h3&gt;

&lt;p&gt;Use On Scroll when the idea is "show this after the visitor has explored enough of the page." Use On Scroll to Element when the idea is "show this when the visitor reaches this specific content." Elementor's own settings documentation confirms these are distinct trigger options.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Request a Quote Popup: Open It on a Specific CTA Click
&lt;/h2&gt;

&lt;p&gt;Not every popup needs to appear automatically. Sometimes it should open only when a visitor specifically asks for it.&lt;/p&gt;

&lt;p&gt;For this example, we have a&amp;nbsp;&lt;strong&gt;Request a Quote&lt;/strong&gt;&amp;nbsp;button in the Custom Clothing section. When a visitor clicks this button, a quote request form opens on the left side of the page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Request a Quote&lt;/p&gt;

&lt;p&gt;Tell us what you need and we’ll help you find the right option for your order.&lt;/p&gt;

&lt;p&gt;Name + Email → Product Type → Quantity + Required Date → Additional Details → REQUEST A QUOTE&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opening method:&lt;/strong&gt; Popup Dynamic Tag&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position:&lt;/strong&gt; Left or right, depending on the page layout&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcaq6k6qvhxd1gickw0ad.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcaq6k6qvhxd1gickw0ad.jpg" alt="Elementor Popup Showing on Button's Click"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Open It on a Specific CTA Click?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A quote form is useful only for visitors who are interested in placing a custom or bulk order. Showing it automatically while someone is casually browsing would interrupt visitors who may not need it.&lt;/p&gt;

&lt;p&gt;Connecting the popup directly to the&amp;nbsp;&lt;strong&gt;Request a Quote&lt;/strong&gt;&amp;nbsp;CTA keeps the interaction intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Position It on the Left Side?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We designed the quote form as a larger side popup because it contains several fields and needs more space than a small promotional message.&lt;/p&gt;

&lt;p&gt;For this example, we placed it on the &lt;strong&gt;left side&lt;/strong&gt;, leaving part of the original page visible in the background. However, the best side depends on your page layout and where the popup is triggered from.&lt;/p&gt;

&lt;p&gt;For example, if the &lt;strong&gt;Request a Quote&lt;/strong&gt; button is on the left side of your layout, you can open the popup from the &lt;strong&gt;right&lt;/strong&gt;. If the button or important content is on the right, opening the popup from the &lt;strong&gt;left&lt;/strong&gt; can work better.&lt;/p&gt;

&lt;p&gt;The idea is to position the popup where it covers as little important content as possible, especially the CTA or section the visitor just interacted with.&lt;/p&gt;

&lt;p&gt;So instead of treating left or right as a fixed rule, choose the side that works best with your page layout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect the Request a Quote Button to Popup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this example, you don't need to enable Elementor's regular&amp;nbsp;&lt;strong&gt;On Click&lt;/strong&gt;&amp;nbsp;trigger.&lt;/p&gt;

&lt;p&gt;Instead, edit the&amp;nbsp;&lt;strong&gt;Request a Quote&lt;/strong&gt;&amp;nbsp;button and click the&amp;nbsp;&lt;strong&gt;Dynamic Tags&lt;/strong&gt;&amp;nbsp;icon next to its Link field. Select&amp;nbsp;&lt;strong&gt;Popup&lt;/strong&gt;, then open its settings and configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt;&amp;nbsp;Open Popup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Popup:&lt;/strong&gt;&amp;nbsp;Request a Quote&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcmgio7k6dy4zu6wits4a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcmgio7k6dy4zu6wits4a.webp" alt="Connect Button to Popup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the popup opens specifically when that button is clicked.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; Elementor's built-in On Click trigger and clicking a specific CTA are different. The On Click trigger waits for a configured number of clicks on the page, while the Popup Dynamic Tag lets you connect a particular button or link directly to a popup.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Re-engagement Offer: After Inactivity
&lt;/h2&gt;

&lt;p&gt;A visitor who stops interacting with the page hasn't necessarily decided to leave. They may be comparing products, reading something, or simply taking a moment before continuing.&lt;/p&gt;

&lt;p&gt;For this example, we'll use a small re-engagement popup that appears after the visitor has been inactive for a while.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;STILL BROWSING&lt;/p&gt;

&lt;p&gt;Found Something You Like?&lt;/p&gt;

&lt;p&gt;Take another look at our latest styles before you go.&lt;/p&gt;

&lt;p&gt;CONTINUE SHOPPING →&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger:&lt;/strong&gt;&amp;nbsp;After Inactivity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position:&lt;/strong&gt;&amp;nbsp;Bottom-left&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3hwmdj3cqzffd0ajbs21.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3hwmdj3cqzffd0ajbs21.jpg" alt="Elementor Popup After Inactivity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use After Inactivity?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After Inactivity works well when a visitor hasn't scrolled, clicked, or otherwise interacted with the page for a specified period.&lt;/p&gt;

&lt;p&gt;They haven't necessarily decided to leave, so a strong last-chance message may be unnecessary. A small reminder gives them a reason to continue browsing without interrupting the entire page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visitor stops interacting → Inactivity timer passes → Re-engagement popup appears&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Position It at the Bottom-left?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We placed this popup in the&amp;nbsp;&lt;strong&gt;bottom-left corner&lt;/strong&gt;&amp;nbsp;so it acts as a gentle reminder rather than taking over the screen.&lt;/p&gt;

&lt;p&gt;The visitor can notice the message and choose to continue shopping while the page they're browsing remains visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the After Inactivity Trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the popup's&amp;nbsp;&lt;strong&gt;Triggers&lt;/strong&gt;&amp;nbsp;settings and enable&amp;nbsp;&lt;strong&gt;After Inactivity&lt;/strong&gt;. Then set how long the visitor should remain inactive before the popup appears.&lt;/p&gt;

&lt;p&gt;Choose a delay that gives visitors enough time to browse naturally before the popup appears.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo3yz4x1kztsumnxupbi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo3yz4x1kztsumnxupbi.webp" alt="Elementor Popup After Inactivity Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. New Arrivals Reminder: On Page Exit Intent
&lt;/h2&gt;

&lt;p&gt;Inactivity isn't the only sign that a visitor may need another reason to engage. Sometimes they're actively browsing but then move toward leaving the page.&lt;/p&gt;

&lt;p&gt;For this situation, we'll use an&amp;nbsp;&lt;strong&gt;Exit Intent popup&lt;/strong&gt;&amp;nbsp;to give visitors one last opportunity to explore the store's latest products.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BEFORE YOU GO…&lt;/p&gt;

&lt;p&gt;Don’t Miss These New Arrivals&lt;/p&gt;

&lt;p&gt;One last look? Explore our newest styles before you go.&lt;/p&gt;

&lt;p&gt;VIEW NEW ARRIVALS →&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger:&lt;/strong&gt;&amp;nbsp;On Page Exit Intent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position:&lt;/strong&gt;&amp;nbsp;Center&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm18p6sqcnc9zp8shoewg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm18p6sqcnc9zp8shoewg.jpg" alt="Elementor Popup On Exit Intent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use Exit Intent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This popup is meant for someone who appears ready to leave, so the message can be more direct than the inactivity popup.&lt;/p&gt;

&lt;p&gt;Instead of showing another generic discount, we're giving the visitor one more opportunity to explore the&amp;nbsp;&lt;strong&gt;New Arrivals&lt;/strong&gt;&amp;nbsp;they may have missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visitor browses the store → Moves toward leaving the page → Exit Intent is detected → New Arrivals popup appears&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Position It in the Center?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike the subtle inactivity reminder, an exit-intent popup has only a short opportunity to get the visitor's attention.&lt;/p&gt;

&lt;p&gt;A&amp;nbsp;centered popup&amp;nbsp;makes the final message more noticeable and gives the CTA a clear focus before the visitor leaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the Exit Intent Trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open&amp;nbsp;&lt;strong&gt;Triggers&lt;/strong&gt;&amp;nbsp;and enable&amp;nbsp;&lt;strong&gt;On Page Exit Intent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once enabled, Elementor can display the popup when the visitor's mouse movement indicates that they may be about to leave the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp2iebb1v2dtcvaqajqk7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp2iebb1v2dtcvaqajqk7.webp" alt="Elementor Popup On Exit Intent Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Welcome Discount Popup: After Form Submission
&lt;/h2&gt;

&lt;p&gt;Sometimes a popup should appear only&amp;nbsp;after a visitor completes an action.&lt;/p&gt;

&lt;p&gt;For this example, our newsletter popup promises&amp;nbsp;&lt;strong&gt;10% off the first order&lt;/strong&gt;. Instead of showing the discount code before signup, we'll reveal it in a second popup after the visitor successfully submits the form.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;YOU’RE IN!&lt;/p&gt;

&lt;p&gt;Here’s Your 10% Off&lt;/p&gt;

&lt;p&gt;Thanks for signing up! Use this code on your first order.&lt;/p&gt;

&lt;p&gt;WELCOME10&lt;/p&gt;

&lt;p&gt;START SHOPPING →&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opening method:&lt;/strong&gt;&amp;nbsp;Actions After Submit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Position:&lt;/strong&gt;&amp;nbsp;Center&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqs3nq4hini7147v9uz7t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqs3nq4hini7147v9uz7t.jpg" alt="Elementor Popup Showing After Submit Form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Show the Popup After Form Submission?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first popup gives visitors a reason to subscribe, while the second delivers what was promised.&lt;/p&gt;

&lt;p&gt;This creates a simple flow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get 10% Off Popup → Enter Email → Submit → Welcome Discount Popup → WELCOME10&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The discount code isn't shown to everyone who sees the signup form. It appears after the visitor completes the signup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use a Separate Popup?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You could display a simple success message inside the form, but a separate popup gives the discount code more visibility and provides a clear next step.&lt;/p&gt;

&lt;p&gt;In our design, the visitor sees the&amp;nbsp;&lt;strong&gt;WELCOME10&lt;/strong&gt;&amp;nbsp;code along with a&amp;nbsp;&lt;strong&gt;Start Shopping&lt;/strong&gt;&amp;nbsp;button, so they can immediately return to browsing products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Position It in the Center?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike the FAQ or inactivity popups, this message is the direct result of an action the visitor just completed.&lt;/p&gt;

&lt;p&gt;A&amp;nbsp;centered popup&amp;nbsp;works well here because the visitor is already expecting feedback after submitting the form. It also keeps the discount code and next CTA clearly visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open the Popup After the Form Is Submitted&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit your newsletter form with Elementor. Select the Form widget and go to &lt;strong&gt;Content &amp;gt; Actions After Submit&lt;/strong&gt;. Add Popup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9jztchw6v6f2aezwbxs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9jztchw6v6f2aezwbxs.webp" alt="Select Popup as Action After Submit in Form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, under the Popup settings,&amp;nbsp;choose &lt;strong&gt;Action &amp;gt; Open Popup&lt;/strong&gt;, and select the Welcome Discount Popup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkiy2ruguhudj5uye4oe.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkiy2ruguhudj5uye4oe.webp" alt="Select the Popup to Open"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elementor's own documentation covers the exact steps here:&amp;nbsp;&lt;a href="https://elementor.com/help/open-a-popup-when-a-user-submits-a-form/" rel="noopener noreferrer"&gt;Open a Popup When a User Submits a Form&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Test the complete experience: enter email, submit, Welcome Discount popup opens, WELCOME10 is shown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You Can Close a Popup After Submission Too&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the form itself is already inside a popup and you simply want to dismiss it after a successful submission, choose Action &amp;gt; Close Popup. For example: newsletter popup, submit form, close popup. A similar two-step effect can also be built without Elementor Pro at all, using the free Modal Popup widget from Addon Elements for Elementor, covered in our guide on&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/tutorial/create-two-step-modal-popup/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;creating a two-step modal popup&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Popup Positions You Can Consider
&lt;/h2&gt;

&lt;p&gt;The examples above use several common popup positions, but you can adjust the placement based on the type of message you're showing.&lt;/p&gt;

&lt;p&gt;For example, the&amp;nbsp;&lt;strong&gt;bottom-center&lt;/strong&gt;&amp;nbsp;position can work well for compact, time-sensitive messages such as a countdown timer, limited-time sale, or shipping deadline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sale Ends In: 02 : 14 : 36 → SHOP THE SALE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keeping this type of popup compact lets the message remain noticeable while visitors continue browsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Elementor Popup Trigger Should You Use?
&lt;/h2&gt;

&lt;p&gt;Instead of asking "which Elementor trigger is best," ask "what should the visitor be doing when this popup becomes useful?"&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Popup&lt;/th&gt;
&lt;th&gt;Recommended Trigger / Approach&lt;/th&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;WHY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Newsletter Discount&lt;/td&gt;
&lt;td&gt;On Page Load with delay&lt;/td&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;td&gt;Introduces the signup offer early without showing it immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FAQ Help&lt;/td&gt;
&lt;td&gt;On Scroll at 50%&lt;/td&gt;
&lt;td&gt;Bottom-right&lt;/td&gt;
&lt;td&gt;Makes help available after the visitor has explored part of the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Shipping Offer&lt;/td&gt;
&lt;td&gt;On Scroll to Element&lt;/td&gt;
&lt;td&gt;Right middle&lt;/td&gt;
&lt;td&gt;Shows the shipping incentive when the visitor reaches relevant products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request a Quote&lt;/td&gt;
&lt;td&gt;Specific CTA click&lt;/td&gt;
&lt;td&gt;Left&lt;/td&gt;
&lt;td&gt;Opens the quote form only when the visitor requests it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-engagement&lt;/td&gt;
&lt;td&gt;After Inactivity&lt;/td&gt;
&lt;td&gt;Bottom left&lt;/td&gt;
&lt;td&gt;Gently brings attention back when the visitor stops interacting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Arrivals Reminder&lt;/td&gt;
&lt;td&gt;On Page Exit Intent&lt;/td&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;td&gt;Gives visitors one more reason to explore before leaving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Welcome Discount&lt;/td&gt;
&lt;td&gt;After Form Submission&lt;/td&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;td&gt;Reveals the promised discount after the visitor completes signup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even similar offers can need completely different timing.&lt;/p&gt;

&lt;p&gt;Before signup: "Get 10% Off" triggers On Page Load. After signup: "Here's Your 10% Discount" triggers After Form Submission.&lt;/p&gt;

&lt;p&gt;That's why context matters more than the popup type itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Things to Keep in Mind When Choosing a Popup Trigger
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't Interrupt Visitors Earlier Than Necessary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a popup isn't immediately relevant, wait. An FAQ prompt can wait until the visitor scrolls. A product-related offer can wait until they reach the products. A quote form can wait until they request it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match the Position to the Importance of the Message&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use more prominent positions for actions that deserve full attention. Center: signup offers, quote forms, post-submit messages, exit offers. Use less intrusive positions for supporting messages. Side or corner: FAQs, contextual offers, gentle reminders. These are recommendations rather than fixed rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't Make Visitors See the Same Popup Constantly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use Advanced Rules where appropriate to control how often a popup is displayed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the Popup Focused&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keep the message simple, use a clear CTA, and make the popup easy to close. A visitor should understand why the popup appeared and what they're expected to do next without having to read a long explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the best trigger for an Elementor popup?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There isn't one trigger that's best for every popup. Choose based on when the content becomes useful. Page Load can work for a broad welcome offer, Scroll for secondary prompts, Scroll to Element for contextual offers, and Exit Intent for a last-chance message.&lt;/p&gt;

&lt;p&gt;Should I show a popup immediately on Page Load?&lt;/p&gt;

&lt;p&gt;Not necessarily. If you're using On Page Load, consider adding a short delay so visitors have time to see the page before the popup appears.&lt;/p&gt;

&lt;p&gt;What is the difference between On Scroll and On Scroll to Element?&lt;/p&gt;

&lt;p&gt;On Scroll is based on how far the visitor has moved through the page. On Scroll to Element waits until they reach a particular element or section.&lt;/p&gt;

&lt;p&gt;How do I open an Elementor popup from a specific button?&lt;/p&gt;

&lt;p&gt;For an Elementor element with a Link field, use Dynamic Tags &amp;gt; Actions &amp;gt; Popup, select Open Popup, and choose the popup you want to display.&lt;/p&gt;

&lt;p&gt;Is Elementor's On Click trigger the same as opening a popup from a button?&lt;/p&gt;

&lt;p&gt;No. Elementor's standard On Click trigger is based on a specified number of clicks. If one specific button or link should open a particular popup, connecting that element directly through the Popup Dynamic Tag or using Open By Selector gives you more precise control.&lt;/p&gt;

&lt;p&gt;Can I open an Elementor popup from a navigation menu?&lt;/p&gt;

&lt;p&gt;Yes. You can use Open By Selector to connect a matching navigation menu link to a popup.&lt;/p&gt;

&lt;p&gt;Can I show a popup after an Elementor form is submitted?&lt;/p&gt;

&lt;p&gt;Yes. Add Popup under the Form widget's Actions After Submit, then choose Open Popup and select the popup you want to show. You can also use Close Popup if you want to close the current popup after a successful submission.&lt;/p&gt;

&lt;p&gt;Where should I position my Elementor popup?&lt;/p&gt;

&lt;p&gt;It depends on the importance of the message. Centered popups work well when you need focused attention, while side and corner positions are generally less intrusive for supporting messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Choosing an Elementor popup trigger isn't really about finding the most advanced setting. It's about showing the right message at the moment it becomes useful.&lt;/p&gt;

&lt;p&gt;A newsletter offer can appear after a visitor has had a little time to explore. An FAQ prompt can wait until they've started scrolling. A free shipping incentive can appear when they reach your products. A quote form can stay hidden until they request it. And a promised discount can appear only after they successfully subscribe.&lt;/p&gt;

&lt;p&gt;So before configuring your next popup, ask what the visitor should be doing when this message becomes relevant, then choose the trigger and position around that moment. That's a much better starting point than simply making every popup appear as soon as possible.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Create an Effective Popup Using Elementor Popup Builder</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Mon, 07 Sep 2026 06:58:03 +0000</pubDate>
      <link>https://dev.to/wpvibes/how-to-create-an-effective-popup-using-elementor-popup-builder-3hme</link>
      <guid>https://dev.to/wpvibes/how-to-create-an-effective-popup-using-elementor-popup-builder-3hme</guid>
      <description>&lt;p&gt;Popups can be useful for collecting email addresses, promoting an offer, sharing an important announcement, or directing visitors to relevant content. But when they appear too quickly, cover the entire screen, or keep showing up repeatedly, they can easily become frustrating.&lt;/p&gt;

&lt;p&gt;With&amp;nbsp;&lt;strong&gt;Elementor Pro's Popup Builder&lt;/strong&gt;, you have more control than simply deciding what your popup looks like. You can choose where it appears, when it opens, which pages display it, and the conditions under which visitors see it.&lt;/p&gt;

&lt;p&gt;That means you can create popups that support the visitor's experience instead of interrupting it.&lt;/p&gt;

&lt;p&gt;In this guide, we'll create a simple&amp;nbsp;&lt;strong&gt;newsletter popup in Elementor&lt;/strong&gt;&amp;nbsp;and configure its display conditions and triggers so that it appears at a more appropriate point in the visitor's journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What You'll Need&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Elementor's Popup Builder is part of &lt;a href="https://elementor.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Elementor Pro&lt;/strong&gt;&lt;/a&gt;. It isn't available in the free version of Elementor. You'll also need Elementor Pro active on a WordPress site with at least one page or post to attach the popup to. No coding is required for anything covered here.&lt;/p&gt;

&lt;p&gt;Don't have Elementor Pro? The&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/addon-elements-elementor/modal-popup-elementor/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=demo"&gt;&lt;strong&gt;Modal Popup widget&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;from &lt;a href="https://wpbuilderaddons.com/addon-elements-elementor/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=plugin_landing"&gt;&lt;strong&gt;Addon Elements for Elementor&lt;/strong&gt;&lt;/a&gt; is a free way to build a popup, triggered by a button, icon, or image click, using text, images, or saved layouts, without needing Elementor Pro at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Elementor Popup Builder?
&lt;/h2&gt;

&lt;p&gt;Elementor's Popup Builder is Elementor's own feature for designing overlay content, including center modals, slide-in banners, and top or bottom bars, directly inside the Elementor editor. It replaces the need for a separate popup plugin for most common use cases, since anything built with standard Elementor widgets can go inside a popup.&lt;/p&gt;

&lt;p&gt;Because a popup built this way uses the same widgets, fonts, and colors as the rest of an Elementor site, it tends to look native to the page rather than like an ad wrapped around the content. That changes how a visitor reacts to it before a single trigger or targeting rule comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Create a Popup in Elementor?
&lt;/h2&gt;

&lt;p&gt;Let's create the popup first, then we'll configure where and when it should appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a New Popup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to Elementor in WP Admin, then navigate to&amp;nbsp;&lt;strong&gt;Editor &amp;gt;&lt;/strong&gt;&amp;nbsp;&lt;strong&gt;Templates.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Add New Template.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose&amp;nbsp;&lt;strong&gt;Popup&lt;/strong&gt;&amp;nbsp;as the template type, enter a name such as&amp;nbsp;&lt;strong&gt;Newsletter Popup&lt;/strong&gt;, and click&amp;nbsp;&lt;strong&gt;Create Template&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Elementor will open the template library, where you can choose an existing popup template.&amp;nbsp;For this example, we'll close the template library and build the popup from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6b3g1qylilerra8b67l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6b3g1qylilerra8b67l.webp" alt="Create New Popup Template in Elementor" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; Already have a popup template? You can&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://wpbuilderaddons.com/tutorial/import-export-elementor-templates/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;import it into Elementor&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;and customize it instead of creating a new popup from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Design Your Popup
&lt;/h3&gt;

&lt;p&gt;Once the Elementor editor opens, add the content you want visitors to see.&amp;nbsp;For our newsletter popup, we'll keep things simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heading:&lt;/strong&gt; Enjoying this guide?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Get practical WordPress tutorials delivered to your inbox.&lt;/p&gt;

&lt;p&gt;Next, add a Form widget, where we will add only an email field and a subscribe button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Adjust the Popup Layout
&lt;/h3&gt;

&lt;p&gt;Next, open the popup settings from the Elementor editor and go to&amp;nbsp;&lt;strong&gt;Layout&lt;/strong&gt;. Adjust the width so that it doesn't occupy too much of the screen.&lt;/p&gt;

&lt;p&gt;A width around&amp;nbsp;&lt;strong&gt;380–400px&lt;/strong&gt;&amp;nbsp;can be a useful starting point on desktop, although the exact value will depend on your content and website design.&lt;/p&gt;

&lt;p&gt;The idea isn't to make the popup as large as possible. It only needs enough space for the message and form to remain comfortable to read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqs97kwakmw32uehpw5dr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqs97kwakmw32uehpw5dr.webp" alt="Create and Design Popup in Elementor" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Check the Popup on Mobile
&lt;/h3&gt;

&lt;p&gt;Before publishing, switch to Elementor's mobile preview.&lt;/p&gt;

&lt;p&gt;A 400px-wide popup that looks small on a desktop monitor can take up most of a phone screen.&lt;/p&gt;

&lt;p&gt;Check whether you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Reduce the width or padding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust the heading size&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stack content vertically&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove unnecessary decorative elements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increase the close button's clickable area&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is the same on every device: keep the popup noticeable without making it difficult to use the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm91m8qohrdb5x3wqceoh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm91m8qohrdb5x3wqceoh.webp" alt="Elementor Popup Settings for Mobile" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Publish the Popup
&lt;/h3&gt;

&lt;p&gt;Once you're happy with the design, click&amp;nbsp;&lt;strong&gt;Publish&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before Elementor activates the popup, you'll see&amp;nbsp;&lt;strong&gt;Publish Settings&lt;/strong&gt;. This is where you control where the popup appears and what needs to happen before it opens.&lt;/p&gt;

&lt;p&gt;You'll configure three areas:&amp;nbsp;&lt;strong&gt;Conditions, Triggers, and Advanced Rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Set Conditions to choose where the popup appears. It can be limited to specific pages, specific posts, an entire category, or the whole site, and a popup promoting a specific product makes more sense tied to that product's page than shown site-wide.&lt;/p&gt;

&lt;p&gt;A popup shown everywhere to everyone is the version most likely to feel like noise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopfvrye30ph61cks3tb5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopfvrye30ph61cks3tb5.webp" alt="Elementor Popup Conditions Settings" width="799" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Next&lt;/strong&gt;, then set&amp;nbsp;&lt;strong&gt;Triggers&lt;/strong&gt;&amp;nbsp;to choose when the popup appears based on what the visitor does. An on-load trigger with no delay is the single most common reason popups feel annoying, since it fires before a visitor has had any chance to read the page, so a short delay already helps.&lt;/p&gt;

&lt;p&gt;A scroll trigger only fires once a visitor reaches a set point on the page. An exit-intent trigger fires when Elementor detects the visitor's cursor moving toward the browser's close or back button, one of the few moments a popup can appear without interrupting anything, since the visitor was already leaving.&lt;/p&gt;

&lt;p&gt;A click trigger only opens the popup when the visitor clicks a specific button or link, leaving them fully in control of whether it appears at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw0eq377q0d18apno2i9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw0eq377q0d18apno2i9.webp" alt="Elementor Popup Triggers Settings" width="799" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Next&lt;/strong&gt;, then set&amp;nbsp;&lt;strong&gt;Advanced Rules&lt;/strong&gt;&amp;nbsp;for anything not tied to a direct visitor action, including device type, how many times the visitor has been to the site, and the URL they arrived from. Device rules matter because a popup built for a wide desktop layout can end up covering the entire visible screen on mobile if it isn't restricted for smaller viewports.&lt;/p&gt;

&lt;p&gt;Visit-count rules matter because a popup that reappears on every single visit, rather than being limited after a visitor has already seen it, reads as the site not listening.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3fpmh11to3uu3hetus1u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3fpmh11to3uu3hetus1u.webp" alt="Elementor Popup Advanced Rules Settings" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once everything is configured, click&amp;nbsp;&lt;strong&gt;Save &amp;amp; Close&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv8ktdvb3n7jdpc74pd4w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv8ktdvb3n7jdpc74pd4w.webp" alt="Elementor Popup Display on Frontend" width="756" height="703"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt;&amp;nbsp;A popup with a visible, easy-to-find close button is not optional. Elementor adds one by default, and it should also be dismissible with the keyboard, not just a mouse click. Removing or hiding the close button, or making a popup impossible to escape without a mouse, is one of the fastest ways to turn a popup into the reason someone leaves a page. See&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://wpbuilderaddons.com/tutorial/website-accessibility-elementor/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;How to Improve Elementor Website Accessibility in WordPress&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Triggers Keep a Popup From Feeling Intrusive?
&lt;/h2&gt;

&lt;p&gt;An on-load trigger with no delay is the single most common reason popups get labeled annoying, since it fires before a visitor has had any chance to read the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  On-load vs. delayed triggers
&lt;/h3&gt;

&lt;p&gt;An on-load trigger fires the moment a page finishes loading. Adding even a short delay changes that first impression, since the popup then appears after the visitor has already started reading rather than instead of letting them start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scroll and exit-intent triggers
&lt;/h3&gt;

&lt;p&gt;A scroll trigger fires once a visitor reaches a set point on the page, so it only appears for people already engaged with the content. An exit-intent trigger fires when Elementor detects the visitor's cursor moving toward the browser's close or back button, one of the few moments a popup can appear without interrupting anything, since the visitor was already leaving.&lt;/p&gt;

&lt;h3&gt;
  
  
  Click-triggered popups
&lt;/h3&gt;

&lt;p&gt;A click trigger only opens the popup when a visitor clicks a specific button or link, putting the visitor fully in control of whether it appears at all. This is the lowest-friction option, and it works well for anything the popup offers as a bonus rather than a requirement.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Tips:&lt;/strong&gt; If a popup needs different content on every product page rather than one fixed message, that's a genuine dynamic-content situation. Dynific Addons for Elementor pulls ACF field data, including text, images, and repeaters, directly into any Elementor widget, popups included, without custom code. See how it works:&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://wpbuilderaddons.com/tutorial/acf-fields-elementor-without-pro/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;How to Display ACF Fields in Elementor Without Elementor Pro&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Ways to Make Elementor Popups Less Annoying
&lt;/h3&gt;

&lt;p&gt;Elementor gives you the technical controls, but a few design and content decisions can make just as much difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't Ask Too Soon
&lt;/h3&gt;

&lt;p&gt;Give visitors a chance to understand what your site offers before asking them to subscribe, download something, or make a purchase.&lt;/p&gt;

&lt;p&gt;Scroll, click, and exit-intent triggers can often provide more context than showing every popup immediately on page load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the Message Short
&lt;/h3&gt;

&lt;p&gt;A popup isn't the place for several paragraphs of copy.&lt;/p&gt;

&lt;p&gt;A visitor should quickly understand what you're offering and what they need to do next.&lt;/p&gt;

&lt;p&gt;For our example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enjoying this guide?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get practical WordPress tutorials delivered to your inbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subscribe →&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's enough to communicate the offer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use One Clear CTA
&lt;/h3&gt;

&lt;p&gt;If the goal is newsletter signup, make subscribing the primary action.&lt;/p&gt;

&lt;p&gt;Avoid adding several buttons that compete for attention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make Closing Easy
&lt;/h3&gt;

&lt;p&gt;Don't hide the close button or make visitors wait before they're allowed to dismiss the popup.&lt;/p&gt;

&lt;p&gt;A popup should offer an opportunity, not trap someone into interacting with it.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; Not every attention-getting element needs to be a popup. A&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://wpbuilderaddons.com/tutorial/news-ticker-in-elementor/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;scrolling news ticker&lt;/a&gt;&amp;nbsp;can highlight an announcement or offer within the page itself, without covering any content or requiring a visitor to dismiss anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Elementor's Popup Builder require Elementor Pro?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Elementor's Popup Builder is part of Elementor Pro and isn't available in the free version of Elementor. The free plugin doesn't include a popup creation tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the best trigger for a popup so it doesn't feel intrusive?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There isn't one universal best trigger, since it depends on the goal. Exit-intent and scroll triggers tend to feel least intrusive because they respond to something the visitor already did, rather than interrupting before they've engaged with the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I show an Elementor popup only on certain pages?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes.&amp;nbsp;&lt;strong&gt;Display Conditions&lt;/strong&gt;&amp;nbsp;let you determine where the popup can appear. You can include or exclude different parts of your website depending on where the popup is relevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I stop a popup from showing every time a visitor reloads the page?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Elementor's Advanced Rules step, set after Conditions and Triggers when publishing a popup, includes options based on how many times a visitor has been to the site. The exact field names for limiting repeat views should be checked directly in the live Advanced Rules panel, since this needs confirming against the current version of the interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Elementor's Popup Builder let a popup target specific devices?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Device targeting is set under Advanced Rules, not under Conditions. Conditions controls which pages or posts a popup is tied to, while Advanced Rules covers device type, visit count, and referrer URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a popup that visitors tolerate comes down to four decisions: which popup type fits the message, where it's set to appear through Conditions, what triggers it through user action, and what Advanced Rules filter out repeat or mismatched displays. Elementor's Popup Builder walks through all four in sequence every time a popup is published, they just aren't set to the least intrusive option by default.&lt;/p&gt;

&lt;p&gt;Before publishing a new popup, preview it on an actual mobile device and confirm the close button works without a mouse. If a scrolling ticker or targeted banner would say the same thing without a popup at all, that's usually the better choice.&lt;/p&gt;

</description>
      <category>wordpress</category>
    </item>
    <item>
      <title>Build Custom Gutenberg Blocks Using ACF Blocks (No React Required)</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Mon, 31 Aug 2026 08:20:53 +0000</pubDate>
      <link>https://dev.to/wpvibes/build-custom-gutenberg-blocks-using-acf-blocks-no-react-required-55eb</link>
      <guid>https://dev.to/wpvibes/build-custom-gutenberg-blocks-using-acf-blocks-no-react-required-55eb</guid>
      <description>&lt;p&gt;For years, building custom Gutenberg blocks meant learning React, setting up a build pipeline, and wrestling with JavaScript tooling most PHP developers never signed up for. ACF Blocks changes that. If you already know PHP and use Advanced Custom Fields on your projects, you can build production-ready custom Gutenberg blocks today - with the same familiar&amp;nbsp;get_field()&amp;nbsp;calls, no React, no npm, no build step.&lt;/p&gt;

&lt;p&gt;This guide walks through building a real plugin with&amp;nbsp;three custom Gutenberg blocks&amp;nbsp;using ACF Blocks, progressing from simple to advanced.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;Three production-ready blocks packaged as a single plugin (&lt;code&gt;wpvibes-acf-blocks&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testimonial Block&lt;/strong&gt;: a customer quote with author name, title, and photo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature CTA Block&lt;/strong&gt;: a feature card with image, heading, description, and button link&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Team Members Block&lt;/strong&gt;: a responsive grid of team members using ACF's Repeater field, with configurable column count&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each block lives in its own folder inside the plugin. Adding a new block later means creating one new folder - no changes to any existing file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WordPress 6.7 or later&lt;/strong&gt;&amp;nbsp;(any version supporting ACF Blocks V3)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://wpbuilderaddons.com/tutorial/acf-vs-pods/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;ACF Pro&lt;/strong&gt;&lt;/a&gt;: required for building custom blocks. The free version of ACF does not support this. ACF Pro is $49/year for a single site.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local development environment&lt;/strong&gt;&amp;nbsp;(LocalWP, WordPress Studio, wp-env, or similar)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Any WordPress theme&lt;/strong&gt;: block themes (Twenty Twenty-Four) or classic themes both work. No child theme required since we're using a plugin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic PHP knowledge&lt;/strong&gt;: you'll write PHP templates using standard ACF functions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; This tutorial uses a separate plugin (not a child theme) so the blocks work regardless of which theme is active. This is the difference between our approach and &lt;a rel="noreferrer noopener" href="https://www.advancedcustomfields.com/resources/create-your-first-acf-block/"&gt;&lt;strong&gt;ACF's official tutorial&lt;/strong&gt;&lt;/a&gt;, which uses a child theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set Up the Plugin
&lt;/h2&gt;

&lt;p&gt;The plugin contains all three blocks in one folder structure, organized so adding new blocks later is trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 - File structure
&lt;/h3&gt;

&lt;p&gt;Create this folder inside&amp;nbsp;&lt;code&gt;wp-content/plugins/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wpvibes-acf-blocks/
├── wpvibes-acf-blocks.php
└── blocks/
    ├── testimonial/
    │   ├── block.json
    │   ├── render.php
    │   └── style.css
    ├── feature-cta/
    │   ├── block.json
    │   ├── render.php
    │   └── style.css
    └── team-members/
        ├── block.json
        ├── render.php
        └── style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this structure?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One block = one folder&lt;/strong&gt;&amp;nbsp;- every block owns its&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;,&amp;nbsp;&lt;code&gt;render.php&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;style.css&lt;/code&gt;&amp;nbsp;in the same place. Delete the folder and the block is gone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-registration&lt;/strong&gt;&amp;nbsp;- the main plugin file scans&amp;nbsp;&lt;code&gt;blocks/&lt;/code&gt;&amp;nbsp;and registers every folder it finds. Adding a new block never requires touching this file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Per-block CSS&lt;/strong&gt;&amp;nbsp;- each block's styles are in its own file, loaded automatically when the block appears on a page.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same pattern used in our&amp;nbsp;&lt;a href="https://wpvibes.com/blog/php-only-blocks-wordpress/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;PHP-only blocks guide&lt;/a&gt;&amp;nbsp;- one block, one folder, self-contained.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 - The main plugin file
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;wpvibes-acf-blocks.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: WPVibes ACF Blocks
 * Description: Custom Gutenberg blocks built with Advanced Custom Fields — Testimonial, Feature CTA, and Team Members.
 * Version:     1.0.0
 * Author:      WPVibes
 * Requires at least: 6.7
 * Requires PHP: 7.4
 * Text Domain: wpvibes-acf-blocks
 */&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Register all ACF blocks by scanning the blocks/ folder.
 * Adding a new block later = one new folder, zero changes here.
 */&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_acf_register_blocks'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpvibes_acf_register_blocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Only run if ACF Pro is active.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'acf_register_block_type'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$blocks_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plugin_dir_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'blocks/'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$blocks_dir&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Register every block folder inside blocks/.&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$blocks_dir&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;GLOB_ONLYDIR&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$block_folder&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;register_block_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block_folder&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does in plain words:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The plugin header identifies this folder as a WordPress plugin&lt;/p&gt;

&lt;p&gt;wpvibes_acf_register_blocks()&amp;nbsp;runs on WordPress's&amp;nbsp;init&amp;nbsp;hook&lt;/p&gt;

&lt;p&gt;ACF Pro check first - if ACF Pro isn't active, the function exits silently (no fatal errors)&lt;/p&gt;

&lt;p&gt;glob()&amp;nbsp;finds every folder inside&amp;nbsp;blocks/&lt;/p&gt;

&lt;p&gt;register_block_type( $block_folder )&amp;nbsp;tells WordPress: "there's a block here, read&amp;nbsp;block.json&amp;nbsp;for details"&lt;/p&gt;

&lt;p&gt;Once this loop exists, you never touch this file again.&amp;nbsp;Every new block you add as a folder inside&amp;nbsp;blocks/&amp;nbsp;registers automatically.&lt;/p&gt;

&lt;p&gt;Activate the plugin from&amp;nbsp;Plugins → Installed Plugins. Nothing visible yet - we haven't built any blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build the Testimonial Block
&lt;/h2&gt;

&lt;p&gt;The Testimonial block is the simplest of the three. It shows a customer quote with author details and photo. This block introduces the complete ACF Blocks workflow - &lt;code&gt;block.json&lt;/code&gt;, field group setup, PHP template, and CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36d49on6kg99742mrwcn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36d49on6kg99742mrwcn.webp" alt="Testimonial block rendered on the frontend" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 - Create&amp;nbsp;block.json
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/testimonial/block.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://schemas.wp.org/trunk/block.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"apiVersion"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"wpvibes/testimonial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Testimonial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Display a customer testimonial with author details and photo."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"icon"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"format-quote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"keywords"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"testimonial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"supports"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"align"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wide"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s2"&gt;"anchor"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"jsx"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"acf"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"mode"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"renderTemplate"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"render.php"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"style"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"file:./style.css"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Line by line, in plain words:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;apiVersion: 3&lt;/code&gt;&amp;nbsp;- required for ACF Blocks V3. Always use v3 for new blocks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;name: "wpvibes/testimonial"&lt;/code&gt;&amp;nbsp;- the block's unique identifier. Format:&amp;nbsp;&lt;code&gt;{namespace}/{block-slug}&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;title&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;description&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;category&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;icon&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;keywords&lt;/code&gt;&amp;nbsp;- how the block appears in the block inserter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;supports.align&lt;/code&gt;&amp;nbsp;- allows wide and full-width alignment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;supports.anchor&lt;/code&gt;&amp;nbsp;- lets users add an HTML anchor (for jump-to links).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;supports.jsx: true&lt;/code&gt;&amp;nbsp;- required for ACF Blocks V3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;acf.mode: "preview"&lt;/code&gt;&amp;nbsp;- show the rendered block in the editor (not a form view).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;acf.renderTemplate&lt;/code&gt;&amp;nbsp;- the PHP file that renders the block. Relative to&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;style&lt;/code&gt;&amp;nbsp;- the stylesheet, loaded automatically in both the editor and frontend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.2 - Create the ACF Field Group
&lt;/h3&gt;

&lt;p&gt;The Testimonial block needs four fields. Create them through the ACF admin UI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to&amp;nbsp;&lt;strong&gt;wp-admin → ACF → Field Groups → Add New&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Field group title:&amp;nbsp;&lt;strong&gt;Testimonial Block Fields&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following four fields:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Label&lt;/th&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Field Type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Testimonial Text&lt;/td&gt;
&lt;td&gt;testimonial_text&lt;/td&gt;
&lt;td&gt;Textarea&lt;/td&gt;
&lt;td&gt;Required, Character Limit: 500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Author Name&lt;/td&gt;
&lt;td&gt;author_name&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Author Title&lt;/td&gt;
&lt;td&gt;author_title&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Optional, e.g., "CEO, Acme Corp"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Author Image&lt;/td&gt;
&lt;td&gt;author_image&lt;/td&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;Return Format:&amp;nbsp;&lt;strong&gt;Image Array&lt;/strong&gt;, Preview Size: Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4segb8k6lgj900ibif4k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4segb8k6lgj900ibif4k.webp" alt="Create ACF Field Group for Testimonial Block" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scroll down to&amp;nbsp;&lt;strong&gt;Settings → Location Rules&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the rule:&amp;nbsp;&lt;strong&gt;Show this field group if → Block → is equal to → Testimonial&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Publish&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7hlrzyoiakrtm3v5pcph.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7hlrzyoiakrtm3v5pcph.webp" alt="Set ACF Field Group Settings" width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The location rule (&lt;strong&gt;Block = Testimonial&lt;/strong&gt;) is what tells ACF that these fields belong to this specific block. Without this exact rule,&amp;nbsp;&lt;code&gt;get_field()&lt;/code&gt;&amp;nbsp;returns null and your block renders empty. This is the single most common mistake when building ACF Blocks. Confirm this rule is set correctly before moving on.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 - Create&amp;nbsp;render.php
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/testimonial/render.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Testimonial block render template.
 *
 * @var array  $block      The block settings and attributes.
 * @var string $content    The block inner HTML (empty for ACF Blocks).
 * @var bool   $is_preview True during backend preview render.
 */&lt;/span&gt;

&lt;span class="c1"&gt;// Get field values.&lt;/span&gt;
&lt;span class="nv"&gt;$testimonial_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'testimonial_text'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$author_name&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'author_name'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$author_title&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'author_title'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$author_image&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'author_image'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Build CSS classes — always include the base class + any WordPress-added classes.&lt;/span&gt;
&lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-testimonial'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'className'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'className'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'align'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' align'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'align'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Support the anchor attribute.&lt;/span&gt;
&lt;span class="nv"&gt;$anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'anchor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'id="'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'anchor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'" '&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$anchor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$testimonial_text&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;blockquote&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wpvibes-testimonial__quote"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$testimonial_text&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/blockquote&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wpvibes-testimonial__author"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_image&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
                &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wpvibes-testimonial__image"&lt;/span&gt;
                &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sizes'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'thumbnail'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'alt'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;
                &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;
            &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wpvibes-testimonial__info"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wpvibes-testimonial__name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_name&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;

            &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_title&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wpvibes-testimonial__title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$author_title&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;get_field()&lt;/code&gt;&amp;nbsp;- ACF's standard function for retrieving field values. Works exactly the same in ACF Blocks as it does anywhere else in ACF.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$block['className']&lt;/code&gt;&amp;nbsp;- WordPress passes any classes the user added via the sidebar's Advanced section.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$block['align']&lt;/code&gt;&amp;nbsp;- the alignment the user picked (wide, full).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$block['anchor']&lt;/code&gt;&amp;nbsp;- the HTML ID the user set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every field wrapped in&lt;/strong&gt;&amp;nbsp;&lt;code&gt;if ( $field )&lt;/code&gt;&amp;nbsp;- optional fields don't render empty tags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;esc_html()&lt;/code&gt;&amp;nbsp;&lt;strong&gt;and&lt;/strong&gt;&amp;nbsp;&lt;code&gt;esc_url()&lt;/code&gt;&amp;nbsp;- always escape output. Standard WordPress security.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The&lt;/strong&gt;&amp;nbsp;&lt;code&gt;$author_image&lt;/code&gt;&amp;nbsp;&lt;strong&gt;array structure&lt;/strong&gt;&amp;nbsp;(when Return Format is "Image Array"):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$author_image['url']&lt;/code&gt;&amp;nbsp;- full-size URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$author_image['sizes']['thumbnail']&lt;/code&gt;&amp;nbsp;- thumbnail URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$author_image['sizes']['medium']&lt;/code&gt;&amp;nbsp;- medium URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$author_image['alt']&lt;/code&gt;&amp;nbsp;- alt text&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.4 - Create&amp;nbsp;style.css
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/testimonial/style.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.wpvibes-testimonial&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f9fafb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5em&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-testimonial__quote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1f2937&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-testimonial__author&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-testimonial__image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-testimonial__name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-testimonial__title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6b7280&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The full CSS (with hover states, quote decoration, responsive tweaks) is in the plugin repository at&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/testimonial/style.css"&gt;assets/blocks/testimonial/style.css&lt;/a&gt;. Copy the complete file into the plugin's&amp;nbsp;&lt;code&gt;blocks/testimonial/&lt;/code&gt;&amp;nbsp;folder before testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.5 - See it working
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a new page:&amp;nbsp;&lt;strong&gt;Pages → Add New&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;+&lt;/strong&gt;&amp;nbsp;in the block inserter and search for&amp;nbsp;&lt;strong&gt;Testimonial&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insert the block&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fill in the fields on the right sidebar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Testimonial Text:&amp;nbsp;&lt;code&gt;"WPVibes plugins saved us weeks of development time. The support is fantastic."&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Author Name:&amp;nbsp;&lt;code&gt;Sarah Chen&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Author Title:&amp;nbsp;&lt;code&gt;Lead Developer, TechCorp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Author Image: upload a headshot&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Watch the preview update in real time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preview the frontend&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your first ACF block is done - no React, no build step, just PHP and CSS you already know.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffu43owuvxw40kswvjwba.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffu43owuvxw40kswvjwba.webp" alt="Testimonial field group with all 4 fields and location rule visible" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build the Feature CTA Block
&lt;/h2&gt;

&lt;p&gt;The Feature CTA block introduces two new patterns - the&amp;nbsp;&lt;strong&gt;Link field&lt;/strong&gt;&amp;nbsp;(URL + label + target in one field) and using images at different sizes. Same block structure as Testimonial, just different fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5bfrbay83bvk6zg4kvf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5bfrbay83bvk6zg4kvf.webp" alt="Feature CTA block rendered on the frontend" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 - Create&amp;nbsp;block.json
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/feature-cta/block.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "wpvibes/feature-cta",
    "title": "Feature CTA",
    "description": "A feature card with image, heading, description, and call-to-action button.",
    "category": "design",
    "icon": "megaphone",
    "keywords": ["feature", "cta", "call to action", "card"],
    "supports": {
        "align": ["wide", "full"],
        "anchor": true,
        "jsx": true
    },
    "acf": {
        "mode": "preview",
        "renderTemplate": "render.php"
    },
    "style": "file:./style.css"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 - Create the ACF Field Group
&lt;/h3&gt;

&lt;p&gt;Create a new field group called&amp;nbsp;&lt;strong&gt;Feature CTA Block Fields&lt;/strong&gt;&amp;nbsp;with these four fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Label&lt;/th&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Field Type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feature Image&lt;/td&gt;
&lt;td&gt;feature_image&lt;/td&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;Return Format:&amp;nbsp;&lt;strong&gt;Image Array&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heading&lt;/td&gt;
&lt;td&gt;heading&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;description&lt;/td&gt;
&lt;td&gt;Textarea&lt;/td&gt;
&lt;td&gt;Rows: 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Button Link&lt;/td&gt;
&lt;td&gt;button_link&lt;/td&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;Return Format:&amp;nbsp;Link Array&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Set the location rule:&lt;/strong&gt;&amp;nbsp;Show this field group &lt;strong&gt;if → Block → is equal to → Feature CTA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; As with the Testimonial block, this&amp;nbsp;&lt;strong&gt;Block = Feature CTA&lt;/strong&gt;&amp;nbsp;location rule is what connects the fields to the block. Missing this rule means&amp;nbsp;&lt;code&gt;get_field()&lt;/code&gt;&amp;nbsp;returns null and the block renders empty. Confirm before continuing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 - Create&amp;nbsp;render.php
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/feature-cta/render.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Feature CTA block render template.
 *
 * @var array $block The block settings and attributes.
 */&lt;/span&gt;

&lt;span class="nv"&gt;$feature_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'feature_image'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$heading&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'heading'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$description&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'description'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$button_link&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'button_link'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-feature-cta'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'className'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'className'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'align'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' align'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'align'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full render template, with the image and heading markup included, is here: &lt;a href="https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/feature-cta/render.php" rel="noopener noreferrer"&gt;blocks/feature-cta/render.php&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's new here compared to Testimonial:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Link field returns an array&lt;/strong&gt;&amp;nbsp;with three keys:&amp;nbsp;&lt;code&gt;url&lt;/code&gt;,&amp;nbsp;&lt;code&gt;title&lt;/code&gt;,&amp;nbsp;&lt;code&gt;target&lt;/code&gt;. Handle each carefully with the appropriate escape function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;rel="noopener noreferrer"&lt;/code&gt;&amp;nbsp;- security best practice when opening links in a new tab. Without this, the new tab can access the original window through&amp;nbsp;&lt;code&gt;window.opener&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fallback for target&lt;/strong&gt; - if the user didn't set a target, default to&amp;nbsp;&lt;code&gt;_self&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.4 - Create&amp;nbsp;style.css
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/feature-cta/style.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.wpvibes-feature-cta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5em&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-feature-cta__image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-feature-cta__image&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The full CSS (hover effects, transitions, alignment overrides) is in the plugin repository at&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/feature-cta/style.css"&gt;blocks/feature-cta/style.css&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 - See it working
&lt;/h3&gt;

&lt;p&gt;Insert&amp;nbsp;&lt;strong&gt;Feature CTA&lt;/strong&gt;&amp;nbsp;from the Design category. Upload a small icon-style image, add a heading like "Fast Deployment," a description, and a button label with a URL. Preview the frontend.&lt;/p&gt;

&lt;p&gt;Same pattern as Testimonial, more fields, one new field type. The plugin pattern is starting to feel automatic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyfshb74jw05jc2ylb9e5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyfshb74jw05jc2ylb9e5.webp" alt="Feature CTA block filled in the editor" width="799" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Build the Team Members Block (with Repeater)
&lt;/h2&gt;

&lt;p&gt;The Team Members block is where ACF Blocks really shines. Using ACF's&amp;nbsp;&lt;strong&gt;Repeater field&lt;/strong&gt;, users add unlimited team members through the sidebar - no coding required. This is the pattern that makes ACF Pro worth the license fee.&lt;/p&gt;

&lt;p&gt;The block also introduces a&amp;nbsp;&lt;strong&gt;column selector&lt;/strong&gt;&amp;nbsp;so users pick between 2, 3, or 4 columns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1tcrdivgfehqrxxrxabu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1tcrdivgfehqrxxrxabu.webp" alt="Team Members grid rendered on the frontend" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 - Create&amp;nbsp;block.json
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/team-members/block.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "wpvibes/team-members",
    "title": "Team Members",
    "description": "Display a grid of team members using ACF Repeater fields.",
    "category": "design",
    "icon": "groups",
    "keywords": ["team", "members", "people", "staff"],
    "supports": {
        "align": ["wide", "full"],
        "anchor": true,
        "jsx": true
    },
    "acf": {
        "mode": "preview",
        "renderTemplate": "render.php"
    },
    "style": "file:./style.css"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 - Create the ACF Field Group
&lt;/h3&gt;

&lt;p&gt;Create a field group called&amp;nbsp;&lt;strong&gt;Team Members Block Fields&lt;/strong&gt;&amp;nbsp;with these fields:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Block-level fields (things that apply to the whole grid):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Label&lt;/th&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Field Type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Section Heading&lt;/td&gt;
&lt;td&gt;section_heading&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Columns&lt;/td&gt;
&lt;td&gt;columns&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Enter the number of columns you want&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Members&lt;/td&gt;
&lt;td&gt;team_members&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Repeater&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Layout: Block. Button Label: "Add Team Member"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sub-fields inside the&lt;/strong&gt;&amp;nbsp;&lt;code&gt;team_members&lt;/code&gt;&amp;nbsp;&lt;strong&gt;repeater:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sub-field Label&lt;/th&gt;
&lt;th&gt;Sub-field Name&lt;/th&gt;
&lt;th&gt;Sub-field Type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Photo&lt;/td&gt;
&lt;td&gt;member_image&lt;/td&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;Return Format:&amp;nbsp;&lt;strong&gt;Image Array&lt;/strong&gt;, Preview Size: Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;member_name&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role&lt;/td&gt;
&lt;td&gt;member_role&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bio&lt;/td&gt;
&lt;td&gt;member_bio&lt;/td&gt;
&lt;td&gt;Textarea&lt;/td&gt;
&lt;td&gt;Rows: 3, Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Set the location rule:&lt;/strong&gt;&amp;nbsp;Show this field group &lt;strong&gt;if → Block → is equal to → Team Members.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fou5huk822nam4hjswafz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fou5huk822nam4hjswafz.webp" alt="Team Members field group with the repeater and sub-fields visible" width="799" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The&amp;nbsp;&lt;strong&gt;Block = Team Members&lt;/strong&gt;&amp;nbsp;location rule connects the fields to this block. Without it, no fields render in the sidebar and&amp;nbsp;&lt;code&gt;have_rows()&lt;/code&gt;&amp;nbsp;returns false. Confirm the rule before writing render code.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Tips:&lt;/strong&gt; When adding the Repeater field, click&amp;nbsp;&lt;strong&gt;Add Sub Field&lt;/strong&gt;&amp;nbsp;inside the repeater to add each column. Sub-fields live INSIDE the repeater, not next to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 - Create&amp;nbsp;render.php
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/team-members/render.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Team Members block render template.
 *
 * @var array $block The block settings and attributes.
 */&lt;/span&gt;

&lt;span class="c1"&gt;// Block-level fields.&lt;/span&gt;
&lt;span class="nv"&gt;$section_heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'section_heading'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$columns&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'columns'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$columns&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Build CSS classes.&lt;/span&gt;
&lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-team wpvibes-team--cols-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;intval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'className'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'className'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'align'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' align'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'align'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'anchor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'id="'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'anchor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'" '&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full render template, with every sub-field's markup included, is here: &lt;a href="https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/team-members/render.php" rel="noopener noreferrer"&gt;blocks/team-members/render.php&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Three ACF Pro functions doing the heavy lifting:&lt;/p&gt;

&lt;p&gt;have_rows( 'team_members' )&amp;nbsp;- checks if the repeater has any rows. Similar in shape to WordPress's&amp;nbsp;have_posts().&lt;/p&gt;

&lt;p&gt;the_row()&amp;nbsp;- loads the current row's data so&amp;nbsp;get_sub_field()&amp;nbsp;calls work.&lt;/p&gt;

&lt;p&gt;get_sub_field( 'field_name' )&amp;nbsp;- retrieves a sub-field value from the current row.&lt;/p&gt;

&lt;p&gt;Once you understand this three-function loop, you can build any repeater-based block - image galleries, feature lists, pricing tiers, timelines, FAQ accordions. The pattern is always the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 - Create&amp;nbsp;style.css
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/team-members/style.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.wpvibes-team&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2em&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-team__grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-team--cols-2&lt;/span&gt; &lt;span class="nc"&gt;.wpvibes-team__grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-team--cols-3&lt;/span&gt; &lt;span class="nc"&gt;.wpvibes-team__grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-team--cols-4&lt;/span&gt; &lt;span class="nc"&gt;.wpvibes-team__grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-team__member&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The full CSS (with hover effects, responsive breakpoints for mobile, and better typography) is in the plugin repository at&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://github.com/webtechhardik/wpvibes-acf-blocks/blob/main/blocks/team-members/style.css"&gt;blocks/team-members/style.css&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 - See it working
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Insert&amp;nbsp;&lt;strong&gt;Team Members&lt;/strong&gt;&amp;nbsp;from the Design category&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set section heading: "Meet Our Team"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set columns to 3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Add Team Member&lt;/strong&gt;&amp;nbsp;- add several members with photo, name, role, and bio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Watch the grid render in the editor preview&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Try changing columns to 2 or 4 to see the layout adapt&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq5png6el54itwf44w74.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq5png6el54itwf44w74.webp" alt="Team Members block in the editor with 4 members and column selector visible" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three real, production-ready blocks in one plugin - done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Full Plugin
&lt;/h2&gt;

&lt;p&gt;The complete plugin - with all three blocks, full CSS, and README - is on &lt;a href="https://github.com/webtechhardik/wpvibes-acf-blocks" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;To install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd wp-content/plugins/
git clone https://github.com/webtechhardik/wpvibes-acf-blocks.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then activate from&amp;nbsp;&lt;strong&gt;Plugins → Installed Plugins&lt;/strong&gt;. Adding a new block:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new folder inside&amp;nbsp;&lt;code&gt;blocks/&lt;/code&gt;&amp;nbsp;(e.g.,&amp;nbsp;&lt;code&gt;blocks/pricing-table/&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;,&amp;nbsp;&lt;code&gt;render.php&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;style.css&lt;/code&gt;&amp;nbsp;to it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the matching ACF field group with location rule&amp;nbsp;&lt;code&gt;Block = Pricing Table&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No changes to&amp;nbsp;&lt;code&gt;wpvibes-acf-blocks.php&lt;/code&gt;, no manual registration - the loader handles everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ACF Blocks Makes Block Development Easy
&lt;/h2&gt;

&lt;p&gt;Building custom Gutenberg blocks the "official" way means learning React, setting up a JavaScript build pipeline (Webpack, Babel, Node.js), and mastering the block editor's JavaScript APIs. That's a real learning curve - several weeks of investment before shipping the first block.&lt;/p&gt;

&lt;p&gt;ACF Blocks removes almost all of that. If you already know PHP and use ACF on your projects, the entire mental shift is small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You write PHP templates&lt;/strong&gt;&amp;nbsp;- the same syntax you've been writing for years&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You use&lt;/strong&gt;&amp;nbsp;&lt;code&gt;get_field()&lt;/code&gt;&amp;nbsp;- the same function you already use everywhere else&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ACF handles the editor experience&lt;/strong&gt;&amp;nbsp;- field UI, live preview, validation, all automatic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No build step&lt;/strong&gt;&amp;nbsp;- save PHP, refresh the browser&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tradeoff is that ACF Pro costs $49/year for a single site. For agencies and freelancers building client sites, that's a rounding error compared to the time saved on every project.&lt;/p&gt;

&lt;p&gt;For blocks that need advanced editor experiences (drag-and-drop, deep interactivity, custom media handling), React blocks still make sense. But for the vast majority of custom blocks a WordPress site actually needs - testimonials, feature cards, team grids, pricing tables, hero sections - ACF Blocks does the job faster with less friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need ACF Pro, or does the free version work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ACF Pro is required. Custom block registration is a Pro-only feature. The free ACF plugin supports custom fields on posts and pages, but not custom Gutenberg blocks. ACF Pro is $49/year for a single site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use ACF Blocks in a child theme instead of a plugin?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://www.advancedcustomfields.com/resources/create-your-first-acf-block/"&gt;official ACF tutorial&lt;/a&gt;&amp;nbsp;uses this approach. The plugin approach shown in this guide is better for portability - the blocks work regardless of which theme is active.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does&amp;nbsp;&lt;/strong&gt;&lt;code&gt;get_field()&lt;/code&gt;&lt;strong&gt;&amp;nbsp;return null in my ACF Block?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most common cause is the field group's location rule. It must be set exactly to&amp;nbsp;&lt;strong&gt;Block = [Block Name]&lt;/strong&gt;. If the rule says "Post Type" or anything else, the fields don't attach to the block. This trips up nearly every developer at least once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can ACF Blocks use InnerBlocks (nested blocks)?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Add&amp;nbsp;&lt;code&gt;&amp;lt;InnerBlocks /&amp;gt;&lt;/code&gt;&amp;nbsp;to your&amp;nbsp;&lt;code&gt;render.php&lt;/code&gt;&amp;nbsp;template. ACF officially supports this in ACF Blocks V3. This gives you container blocks where users can nest core blocks inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I add a custom icon instead of a Dashicon?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two ways. Use any Dashicon slug in&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;'s&amp;nbsp;&lt;code&gt;icon&lt;/code&gt;&amp;nbsp;field (like&amp;nbsp;&lt;code&gt;format-quote&lt;/code&gt;&amp;nbsp;or&amp;nbsp;&lt;code&gt;groups&lt;/code&gt;). Or, with WordPress 7.1+, register your own SVG icon and reference it. See our&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://wpvibes.com/blog/wordpress-custom-icons/?utm_source=dev.to%0A&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;Custom SVG Icons guide&lt;/a&gt;&amp;nbsp;for the full process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between ACF Blocks and PHP-only blocks (WordPress 7.0+) ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ACF Blocks require ACF Pro but support 30+ field types (repeater, image, gallery, relationship, etc.). WordPress 7.0's&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://wpvibes.com/blog/php-only-blocks-wordpress/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;PHP-only blocks&lt;/a&gt;&amp;nbsp;are free but limited to 4 attribute types (string, integer, boolean, enum). For simple blocks, PHP-only works. For anything with images, repeaters, or complex data, ACF Blocks wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I loop through a repeater on the frontend?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the standard ACF repeater loop:&amp;nbsp;&lt;code&gt;have_rows()&lt;/code&gt;,&amp;nbsp;&lt;code&gt;the_row()&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;get_sub_field()&lt;/code&gt;. This pattern works exactly the same in an ACF Block as it does anywhere else in ACF. The Team Members block in this guide is a full working example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I ship an ACF field group in the plugin so users don't have to create it manually?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, using ACF's&amp;nbsp;&lt;strong&gt;JSON sync&lt;/strong&gt;&amp;nbsp;feature. Create an&amp;nbsp;&lt;code&gt;acf-json/&lt;/code&gt;&amp;nbsp;folder in the plugin root. When users save a field group, ACF writes it to that folder. Include those JSON files in the plugin zip. When a user activates the plugin, ACF loads the field group automatically. This is the approach ACF's&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://www.advancedcustomfields.com/resources/synchronized-json/"&gt;official documentation&lt;/a&gt;&amp;nbsp;recommends for shipping fields with plugins or themes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Custom Gutenberg blocks don't require learning React. If you know PHP and use ACF on your projects, you can start shipping real blocks today - with the same familiar workflow you already use for everything else.&lt;/p&gt;

&lt;p&gt;The takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The pattern is consistent&lt;/strong&gt;&amp;nbsp;- every ACF block is&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;&amp;nbsp;+ field group +&amp;nbsp;&lt;code&gt;render.php&lt;/code&gt;&amp;nbsp;+&amp;nbsp;&lt;code&gt;style.css&lt;/code&gt;&amp;nbsp;in one folder&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The location rule is critical&lt;/strong&gt;&amp;nbsp;- always set the field group to&amp;nbsp;&lt;code&gt;Block = [Block Name]&lt;/code&gt;, or&amp;nbsp;&lt;code&gt;get_field()&lt;/code&gt;&amp;nbsp;returns null&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One block = one folder&lt;/strong&gt;&amp;nbsp;- self-contained, easy to add, easy to remove&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-registration scales&lt;/strong&gt;&amp;nbsp;- the plugin scans&amp;nbsp;&lt;code&gt;blocks/&lt;/code&gt;&amp;nbsp;and registers every folder, so adding a new block requires zero changes to existing files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Repeater field is the payoff&lt;/strong&gt;&amp;nbsp;- three functions (&lt;code&gt;have_rows&lt;/code&gt;,&amp;nbsp;&lt;code&gt;the_row&lt;/code&gt;,&amp;nbsp;&lt;code&gt;get_sub_field&lt;/code&gt;) unlock unlimited grid layouts, team lists, pricing tiers, and more&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>ACF vs Pods: What They Actually Do (and How They're Different)</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 27 Aug 2026 06:40:32 +0000</pubDate>
      <link>https://dev.to/wpvibes/acf-vs-pods-what-they-actually-do-and-how-theyre-different-4o35</link>
      <guid>https://dev.to/wpvibes/acf-vs-pods-what-they-actually-do-and-how-theyre-different-4o35</guid>
      <description>&lt;p&gt;When you need to add structured custom content to WordPress, two names you’ll often come across are&amp;nbsp;&lt;strong&gt;Advanced Custom Fields (ACF)&lt;/strong&gt;&amp;nbsp;and&amp;nbsp;&lt;strong&gt;Pods&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first, they can look quite similar. Both allow you to add custom fields, create custom post types and taxonomies, build relationships between content, and go beyond the standard WordPress editor.&lt;/p&gt;

&lt;p&gt;But they aren't exactly the same.&lt;/p&gt;

&lt;p&gt;ACF focuses heavily on custom fields and structured content workflows, while Pods works more like an all-in-one content development framework. Neither one is built to replace the other in every situation, what fits depends on your content structure and which specific capabilities, like repeatable field groups, flexible layouts, or custom database tables, your project actually needs.&lt;/p&gt;

&lt;p&gt;Here's a quick look on ACF vs Pods before we get into the details:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ACF&lt;/th&gt;
&lt;th&gt;Pods&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Field types (free)&lt;/td&gt;
&lt;td&gt;30+&lt;/td&gt;
&lt;td&gt;20+ (25+ input types across those field types)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom post types, custom fields &amp;amp; taxonomies&lt;/td&gt;
&lt;td&gt;Built in, free&lt;/td&gt;
&lt;td&gt;Built in, free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeating fields&lt;/td&gt;
&lt;td&gt;Repeater field groups multiple fields together, PRO only&lt;/td&gt;
&lt;td&gt;Individual fields can repeat multiple values; repeating a set of fields together isn't a native option&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Options/settings pages&lt;/td&gt;
&lt;td&gt;PRO only&lt;/td&gt;
&lt;td&gt;Built in, free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building modular page layouts&lt;/td&gt;
&lt;td&gt;Flexible Content field lets you define and reuse layout blocks, PRO only&lt;/td&gt;
&lt;td&gt;No equivalent dedicated field for this specific pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-image fields&lt;/td&gt;
&lt;td&gt;Dedicated Gallery field, PRO only&lt;/td&gt;
&lt;td&gt;Free, via the Image field's multiple-upload setting with a gallery display option&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building custom blocks&lt;/td&gt;
&lt;td&gt;ACF Blocks (PHP framework), PRO only&lt;/td&gt;
&lt;td&gt;Pre-built blocks and a PHP Blocks API for custom blocks, both free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Bindings support&lt;/td&gt;
&lt;td&gt;PRO only&lt;/td&gt;
&lt;td&gt;Free, since Pods 3.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data storage&lt;/td&gt;
&lt;td&gt;WordPress's standard postmeta table&lt;/td&gt;
&lt;td&gt;Postmeta by default, with an option to use dedicated custom database tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free tier plus a paid PRO tier&lt;/td&gt;
&lt;td&gt;Free, with optional third-party paid add-ons for niche extras&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support&lt;/td&gt;
&lt;td&gt;
&lt;a href="http://WordPress.org" rel="noopener noreferrer"&gt;WordPress.org&lt;/a&gt; forums, with ticketed support on PRO&lt;/td&gt;
&lt;td&gt;Community Slack and &lt;a href="http://WordPress.org" rel="noopener noreferrer"&gt;WordPress.org&lt;/a&gt; forums&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What Is ACF and What Does It Actually Do?
&lt;/h2&gt;

&lt;p&gt;ACF, short for&amp;nbsp;&lt;a href="https://wordpress.org/plugins/advanced-custom-fields/" rel="noopener noreferrer"&gt;&lt;strong&gt;Advanced Custom Fields&lt;/strong&gt;&lt;/a&gt;, lets you add structured custom fields to posts, pages, users, taxonomy terms, and more. Instead of typing everything into WordPress's default editor, you build a field, a text box, an image, a dropdown, and attach it to whatever screen needs it.&lt;/p&gt;

&lt;p&gt;Take a team directory as an example. In ACF, you'd create a "Team Members" post type, then build a field group with a Text field for the name, another for the job title, an Image field for the photo, and a Text Area for the bio. Every time someone adds a team member, they see those exact fields laid out cleanly, instead of one open box to fill in however they want.&lt;/p&gt;

&lt;p&gt;The free version comes with a wide range of field types and lets you register custom post types and taxonomies right from its own screen, no code needed. ACF is developed by WP Engine, and it's one of the most widely installed plugins in&amp;nbsp;&lt;a href="https://wordpress.org/plugins/advanced-custom-fields/" rel="noopener noreferrer"&gt;WordPress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want a full, beginner-friendly walkthrough of setting this up,&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/tutorial/acf-complete-guide-for-beginners/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;our complete ACF guide&lt;/a&gt;&amp;nbsp;covers creating custom post types, taxonomies, and fields step by step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv7jk3j13npr7tbruvvjk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv7jk3j13npr7tbruvvjk.webp" alt="ACF Field Group Interface" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
If your team frequently needs to tweak ACF field values without opening the WordPress admin, use&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://wpvibes.com/plugin/frontend-editor-acf/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=plugin_landing"&gt;&lt;strong&gt;Frontend Editor ACF&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;plugin that adds a lightweight "Edit ACF" button to the frontend admin bar, letting logged-in editors update ACF field data directly on the page they're viewing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Pods and What Does It Actually Do?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/pods/" rel="noopener noreferrer"&gt;&lt;strong&gt;Pods&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;takes a wider view. Instead of just adding fields to content you've already got, it's a framework for building custom content types from the ground up, including ones that can use their own database tables instead of WordPress's usual structure.&lt;/p&gt;

&lt;p&gt;Where ACF expects you to already know your content structure and just needs fields attached to it, Pods hands you everything, fields, post types, relationships, in one place. Building that same team directory in Pods means setting up the "Team Members" type and its fields in one continuous flow, rather than treating "the post type" and "the fields" as two separate steps.&lt;/p&gt;

&lt;p&gt;Some people find that all-in-one flow faster once they're used to it. Others prefer ACF's more separated setup. Neither is wrong, it just depends what clicks for you.&lt;/p&gt;

&lt;p&gt;Pods' free version includes repeatable fields, relationship fields for connecting different content together, and the ability to build custom settings pages, all without a paid tier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9ufjtrg45x3mg3fmva0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9ufjtrg45x3mg3fmva0.webp" alt="Pods field group builder interface, showing a new field group with several field types added" width="799" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Do ACF and Pods Overlap?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Custom Post Types and Taxonomies
&lt;/h3&gt;

&lt;p&gt;This is the most basic thing both plugins need to do, and they both do it the same way now.&lt;/p&gt;

&lt;p&gt;Before plugins like these existed, creating something like "Team Members" as its own section in WordPress meant writing PHP code with&amp;nbsp;&lt;code&gt;register_post_type()&lt;/code&gt;, or reaching for a separate, single-purpose plugin like CPT UI just to get the admin screen set up. Both ACF and Pods removed that step entirely. Open either plugin's interface, fill in a name, set up a few settings, and "Team Members" appears in your sidebar exactly like Posts or Pages, ready to use.&lt;/p&gt;

&lt;p&gt;Taxonomies work the same way. A taxonomy is just a way of grouping entries, the same idea as Categories on a blog, but for your custom content. For a "Team Members" post type, a "Department" taxonomy with options like Marketing, Engineering, or Sales lets you filter and organize team members later, on the frontend or in the admin, without touching a single field.&lt;/p&gt;

&lt;p&gt;Pods had this visual setup available earlier in its history, while ACF added its own version in their&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/news/acf-6-1-release-updates/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;6.1 version&lt;/strong&gt;&lt;/a&gt;. At this point, both are equally capable here, no code required either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional Logic
&lt;/h3&gt;

&lt;p&gt;Conditional logic just means showing or hiding a field based on what's entered in another field. Both plugins offer this for free, no code required.&lt;/p&gt;

&lt;p&gt;Back to the Team Members example: say you only want to collect a "LinkedIn URL" from team members who want their social links shown publicly. You could add a "Show Social Links" toggle, and set the LinkedIn field to only appear once that toggle is checked. Anyone filling in the form who leaves it unchecked never sees the LinkedIn field at all.&lt;/p&gt;

&lt;p&gt;Both ACF and Pods handle this the same basic way: pick the field, choose the condition, and the rest happens automatically in the admin screen. It's a small feature, but it keeps forms shorter and less confusing for whoever's entering the content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Field Types
&lt;/h3&gt;

&lt;p&gt;Field types are just the building blocks you use to collect specific kinds of information. Going back to the "Team Members" fields from earlier, Name and Job Title are Text fields, Photo is an Image field, and Bio is a Text Area. You could also add a Department field as a dropdown, using a Select field tied to the taxonomy above, or a "Currently Hiring" field using a True/False toggle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxtzntiomvw824s24mi6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxtzntiomvw824s24mi6.webp" alt="Field type picker open in ACF's field group builder, showing the range of available field types" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both plugins give you a strong starting set to build all of that: text boxes, number fields, image and file uploads, dropdowns, checkboxes, and relationship fields, which would let you connect a team member to, say, the projects they've worked on.&lt;/p&gt;

&lt;p&gt;ACF's free version adds a few specialty fields Pods doesn't include out of the box, like a Google Map field, handy if you wanted to pin each team member's office location, and a Color Picker for grabbing a hex code. Pods covers the same everyday needs with its own set of input types.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv49mkrvkoylhgqkun19c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv49mkrvkoylhgqkun19c.webp" alt="Field type picker open in Pods" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Do ACF and Pods Genuinely Differ?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Repeating Fields: One Field vs. a Whole Group
&lt;/h3&gt;

&lt;p&gt;Say your team directory needs to support any number of people, not just one. Each person still needs a name, role, photo, and bio. What you actually need is a way to repeat that whole group, once per person, while keeping each person's details bundled together correctly.&lt;/p&gt;

&lt;p&gt;ACF PRO's Repeater field does exactly this. You set up the group once, name, role, photo, bio, mark it as a Repeater, and from there your editors just click "Add Row" for each new person. Ten team members means ten clean, bundled entries, each one easy to reorder or edit as a unit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7emvqty4c289qw72n4jr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7emvqty4c289qw72n4jr.webp" alt="ACF's Repeater field set up with Name, Role, Photo, and Bio sub-fields, showing the " width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pods' free repeatable setting works differently.&amp;nbsp;&lt;a href="https://docs.pods.io/fields/simple-repeatable-fields/" rel="noopener noreferrer"&gt;It repeats a single field, not a group&lt;/a&gt;. You can make one field hold multiple values, a list of phone numbers, say, but there's no built-in way to bundle several fields together and repeat the whole set the way ACF PRO does. Try building that same team page with only Pods' free repeatable setting, and you'd end up with separate lists for names, roles, photos, and bios, with nothing guaranteeing that the third name lines up with the third photo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxprfigmtjx3oefvmvtab.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxprfigmtjx3oefvmvtab.webp" alt="Pods Repeater field set up" width="799" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Custom Blocks
&lt;/h3&gt;

&lt;p&gt;WordPress's Block Editor (Gutenberg) lets you build entirely new custom block types, and how much of that is free depends on which plugin you're using.&lt;/p&gt;

&lt;p&gt;Pods includes pre-built blocks for free, like a "Pods Field Value" block that lets you drop a field straight into your content with no setup. It also includes its own&amp;nbsp;&lt;a href="https://docs.pods.io/code/blocks-api/" rel="noopener noreferrer"&gt;&lt;strong&gt;Blocks API&lt;/strong&gt;&lt;/a&gt;, letting you build entirely custom block types with PHP if the pre-built ones aren't enough, all still free.&lt;/p&gt;

&lt;p&gt;ACF's free version doesn't include either of these. Its equivalent, ACF Blocks, a PHP-based framework for&amp;nbsp;&lt;a href="https://www.advancedcustomfields.com/resources/create-your-first-acf-block/" rel="noopener noreferrer"&gt;&lt;strong&gt;building custom block types&lt;/strong&gt;&lt;/a&gt;, requires ACF PRO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Block Bindings Support
&lt;/h3&gt;

&lt;p&gt;Block Bindings is a separate WordPress feature (added in&amp;nbsp;&lt;a href="https://make.wordpress.org/core/2024/03/06/new-feature-the-block-bindings-api/" rel="noopener noreferrer"&gt;WordPress 6.5&lt;/a&gt;) that connects an&amp;nbsp;&lt;em&gt;existing&lt;/em&gt;&amp;nbsp;block, like a Paragraph or Heading, directly to your field data, without building a new block at all.&lt;/p&gt;

&lt;p&gt;Pods has supported this for free since Pods 3.2, letting you bind a field's value to a&amp;nbsp;&lt;a href="https://pods.io/2024/03/25/pods-3-2-feature-release-meta-revisions-and-block-bindings/" rel="noopener noreferrer"&gt;&lt;strong&gt;compatible block&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;right from the editor.&lt;/p&gt;

&lt;p&gt;ACF's official support for&amp;nbsp;&lt;a href="https://www.advancedcustomfields.com/resources/block-bindings/" rel="noopener noreferrer"&gt;&lt;strong&gt;Block Bindings requires ACF PRO&lt;/strong&gt;&lt;/a&gt;. ACF's free version doesn't include this integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the Data Actually Lives
&lt;/h3&gt;

&lt;p&gt;ACF stores its field data in WordPress's standard postmeta table, the same table WordPress core uses for its own metadata. That keeps ACF closely aligned with how WordPress normally works, which usually means better compatibility with other plugins and fewer surprises when WordPress updates.&lt;/p&gt;

&lt;p&gt;Pods offers something different. Its Advanced Content Types can store data in their own dedicated database tables instead of postmeta. Most sites will never notice the difference day to day, it's a behind-the-scenes choice, not something your editors will ever see. It starts to matter at very high content volumes, where standard postmeta queries can slow down and a dedicated table helps.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Costs
&lt;/h3&gt;

&lt;p&gt;ACF's core tools, including custom post types and taxonomies, are free. Grouped repeaters, modular layouts, galleries, and settings pages all need ACF PRO, which is an annual license.&lt;/p&gt;

&lt;p&gt;Pods gives you equivalent functionality for several of those, for free, with paid options limited to a handful of small, specialized third-party add-ons rather than a full paid tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Decide Which One Fits?
&lt;/h2&gt;

&lt;p&gt;Instead of a verdict, here's a simple way to think it through:&lt;/p&gt;

&lt;p&gt;If your content has several related pieces of data that need to repeat together as a set, team members, testimonials with a name and quote and photo, product specs with multiple attributes, that points toward ACF PRO's Repeater, since it's built exactly for that.&lt;/p&gt;

&lt;p&gt;If you're building an entirely new content structure from scratch, especially at high volume, a big directory, a large product catalog, Pods' all-in-one setup and optional custom table storage are worth a look.&lt;/p&gt;

&lt;p&gt;If budget is the deciding factor and you don't need grouped repetition, Pods' free tier covers a lot of what would otherwise cost you ACF PRO, including settings pages.&lt;/p&gt;

&lt;p&gt;If you already know WordPress's admin patterns well and want the field-building experience to feel like "normal WordPress," ACF tends to feel more familiar right away, since it's built to mirror how the rest of the admin already works.&lt;/p&gt;

&lt;p&gt;None of this points to one plugin being right for everyone. It's just a starting point for matching the tool to your actual content.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do ACF and Pods Work with Elementor?
&lt;/h2&gt;

&lt;p&gt;Both plugins let you pull their field data straight into Elementor, so widgets can show dynamic text, images, and repeated lists instead of hardcoded content. Dynific Addons for Elementor goes further here, with separate integration built for both ACF and Pods, including dedicated widgets for each.&lt;/p&gt;

&lt;p&gt;If you want to&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/tutorial/acf-fields-elementor-without-pro/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;display ACF fields in Elementor without Elementor Pro&lt;/a&gt;, including Repeater, Flexible Content, and Gallery data, Dynific Addons handles that natively. There's also a&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/tutorial/acf-testimonial-section-elementor/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;dynamic testimonial section walkthrough&lt;/a&gt;&amp;nbsp;that shows two approaches side by side: a Repeater field for a single page of testimonials, or a custom post type for a library that keeps growing. It's a good real-world look at the repeating-fields decision from earlier, applied to a different example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8ht97ugo8j64d9rhcoa.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8ht97ugo8j64d9rhcoa.webp" alt="Dynific Addons Dynamic Tags menu showing ACF field options available" width="604" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
Working with Pods instead? Dynific Addons for Elementor also has&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://wpbuilderaddons.com/docs/dynific-addons-elementor/pods-integration/select-field/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=docs"&gt;&lt;strong&gt;dedicated Pods field integration&lt;/strong&gt;&lt;/a&gt;, so switching your data layer doesn't mean rebuilding your Elementor workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;What is the difference between ACF and Pods in WordPress?&lt;/p&gt;

&lt;p&gt;ACF focuses on adding custom fields to existing content, with custom post types built in alongside it. Pods is a broader framework for building whole custom content structures, including post types with their own database tables, fields, and relationships, all in one place.&lt;/p&gt;

&lt;p&gt;Does Pods have a repeater field like ACF?&lt;/p&gt;

&lt;p&gt;Not quite. Pods can repeat a single field to hold multiple values, but it can't group several fields together and repeat that whole set the way ACF PRO's Repeater does.&lt;/p&gt;

&lt;p&gt;Is Pods really free compared to ACF?&lt;/p&gt;

&lt;p&gt;Yes. Pods' core plugin is entirely free, including features like custom settings pages that require ACF PRO. ACF's basic field-building tools are free too, but grouped repeaters, modular layouts, galleries, and settings pages need its paid PRO tier.&lt;/p&gt;

&lt;p&gt;Does ACF or Pods work better with Elementor?&lt;/p&gt;

&lt;p&gt;Both work well. Dynific Addons for Elementor supports both with dedicated widgets and dynamic tags, so your choice between them doesn't limit what you can build visually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;ACF and Pods solve overlapping problems from different starting points. One extends existing content with structured fields. The other builds custom content structures from scratch. Neither is inherently better, and the right pick comes down to what your project actually needs, grouped repeating data, dedicated database storage, or just staying free.&lt;/p&gt;

&lt;p&gt;Ready to bring either plugin's field data into your Elementor designs?&amp;nbsp;&lt;a href="https://wpbuilderaddons.com/docs/dynific-addons-elementor/how-to/dynamic-data/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=docs"&gt;Dynific Addons for Elementor's dynamic content tools&lt;/a&gt;&amp;nbsp;work with both, so you're not locked into a workflow you'll have to redo later.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>acf</category>
      <category>dynamiccontent</category>
      <category>customfields</category>
    </item>
    <item>
      <title>How to Save Contact Form 7 Submissions to Google Sheets</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 20 Aug 2026 12:36:12 +0000</pubDate>
      <link>https://dev.to/wpvibes/how-to-save-contact-form-7-submissions-to-google-sheets-37be</link>
      <guid>https://dev.to/wpvibes/how-to-save-contact-form-7-submissions-to-google-sheets-37be</guid>
      <description>&lt;p&gt;Every Contact Form 7 submission lives in exactly one place: your inbox. If that email lands in spam, or your SMTP setup breaks for a day, that submission is gone. There's no second copy anywhere, because Contact Form 7 was never built to store data at all.&lt;/p&gt;

&lt;p&gt;This guide fixes both problems at once. Form Vibes Pro connects Contact Form 7 to Google Sheets automatically, and saves every submission to your WordPress database at the same time. No automation platform, no per-task limits, and no more relying on a single email that might not arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Contact Form 7 Save Submissions Anywhere on Its Own?
&lt;/h2&gt;

&lt;p&gt;No. Contact Form 7 sends every submission by email and nothing else. There's no entries table in your WordPress dashboard, no export button, and no built-in backup if that email fails to arrive.&lt;/p&gt;

&lt;p&gt;This is intentional. Contact Form 7 was designed to be lightweight, and email-only delivery is part of that design. But it means the only record of a submission is whatever lands in an inbox. A spam filter, a broken SMTP connection, or someone accidentally deleting an email can permanently erase a lead with no way to recover it.&lt;/p&gt;

&lt;p&gt;This is exactly the gap Form Vibes closes. Every submission gets saved to your WordPress database the moment it comes in, and from there it can also sync straight to a Google Sheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Google Sheets Sync a Free or Pro Feature?
&lt;/h2&gt;

&lt;p&gt;Google Sheets sync is a Form Vibes Pro feature. Here's what's relevant to this guide:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Save Contact Form 7 entries to WordPress database&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export to CSV&lt;/td&gt;
&lt;td&gt;Yes (up to 1,000 rows)&lt;/td&gt;
&lt;td&gt;Yes (unlimited)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sync to Google Sheets&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter which submissions get synced&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to Connect Contact Form 7 to Google Sheets
&lt;/h2&gt;

&lt;p&gt;To keep this concrete, we'll set this up for a simple RSVP form with four fields: your-name, your-email, your-event-date, and your-guest-count. Your own form's field names will be different, but the steps are the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Your Google API Credentials
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.formvibes.com/article/152-how-to-connect-with-google-sheet" rel="noopener noreferrer"&gt;Google requires API credentials&lt;/a&gt;, so Form Vibes can communicate securely with your Google account. These consist of a Client ID and a Client Secret, generated in Google Cloud Console. The full credential creation process is covered in a dedicated guide. Follow that guide to generate your Client ID and Client Secret, then return here to continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect Form Vibes to Google Sheets
&lt;/h3&gt;

&lt;p&gt;Go to&amp;nbsp;&lt;strong&gt;Form Vibes &amp;gt; Settings &amp;gt; Google Sheet&lt;/strong&gt;&amp;nbsp;in your WordPress admin. Paste your Client ID and Client Secret into their respective fields. Click&amp;nbsp;&lt;strong&gt;Authenticate&lt;/strong&gt;&amp;nbsp;and sign in with your Google account when prompted. After approving the requested permissions, the connection status updates to show the account as connected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgyfcgdhwvyfa8q9uyzqm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgyfcgdhwvyfa8q9uyzqm.webp" alt="Connect Form Vibes to Google Sheets" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
During authentication, Google shows an "App is not verified" warning. This is expected for a self-configured OAuth connection, and Google's own documentation confirms this is normal for unverified apps. Click Show Advanced, then Go to App, and approve all requested permissions. The connection completes normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up the Google Sheets Connection for Your CF7 Form
&lt;/h3&gt;

&lt;p&gt;With your Google account connected, go to&amp;nbsp;&lt;strong&gt;Form Vibes &amp;gt; Export Profiles&lt;/strong&gt;&amp;nbsp;and click&amp;nbsp;&lt;strong&gt;Add New&lt;/strong&gt;. Under&amp;nbsp;&lt;strong&gt;Type&lt;/strong&gt;, select&amp;nbsp;&lt;strong&gt;Google Sheet&lt;/strong&gt;&amp;nbsp;instead of Local Download. This tells Form Vibes to push submissions to a spreadsheet rather than generate a downloadable file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9cscip5swlt6hfheqih.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9cscip5swlt6hfheqih.webp" alt="Export Profile Type selector showing Local Download and Google Sheet" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose the Form, Spreadsheet, and Worksheet
&lt;/h3&gt;

&lt;p&gt;On the&amp;nbsp;&lt;strong&gt;Data Source&lt;/strong&gt;&amp;nbsp;tab, select your RSVP form from the Form dropdown. Since Contact Form 7 doesn't have a visual field builder, this dropdown lists the form's admin title, such as "Event RSVP Form," not the individual field tags. Forms appear here once they've received at least one submission.&lt;/p&gt;

&lt;p&gt;Choose the Google Spreadsheet where data should be sent. If the spreadsheet you need does not appear, check that the connected Google account has access to it. To create a new spreadsheet directly from Form Vibes, click the plus icon next to the spreadsheet selector.&lt;/p&gt;

&lt;p&gt;Select the specific worksheet, or tab, within the spreadsheet. Create a new one using the plus icon if needed.&lt;/p&gt;

&lt;p&gt;Then, you will be asked to map the columns to your Google Sheet. Click the &lt;strong&gt;"Click here"&lt;/strong&gt; option to start mapping the columns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfkztnrtgxsmlflyzhja.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfkztnrtgxsmlflyzhja.webp" alt="Configure Data Source Tab Export Profile" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to use custom column headers in the sheet, you can enter the desired names here and map them to the corresponding form fields. For example, you can set the column header &lt;strong&gt;Name&lt;/strong&gt; and map it to the &lt;strong&gt;your-name&lt;/strong&gt; form field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff87g7zxnxzh0p57yurew.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff87g7zxnxzh0p57yurew.webp" alt="Map the Form Fields to Google Sheet" width="800" height="1078"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Data Filter section lets you sync only the submissions that match specific conditions, instead of sending every submission to Google Sheets. This step is optional. Skip it if you want every RSVP synced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzexsvfudriihl1csc2m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzexsvfudriihl1csc2m.webp" alt="Apply Filter Conditions to Export Profile" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Map Your Fields on the Columns Mapping Tab
&lt;/h3&gt;

&lt;p&gt;Switch to the &lt;strong&gt;Columns Mapping&lt;/strong&gt; tab. If you want to change the default column mapping or assign a specific form field to a particular spreadsheet column, you can do it here.&lt;/p&gt;

&lt;p&gt;After that, map each spreadsheet column to the appropriate Contact Form 7 field. Click&amp;nbsp;&lt;strong&gt;Publish&lt;/strong&gt;&amp;nbsp;to save and activate the connection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrgroa1kbnmpen9d0h1h.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrgroa1kbnmpen9d0h1h.webp" alt="Columns Mapping tab with RSVP form fields mapped to spreadsheet columns" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens After Someone Submits Your Contact Form 7 Form?
&lt;/h2&gt;

&lt;p&gt;Once the connection is published, no further action is needed. When a guest submits the RSVP form, Form Vibes saves their name, email, event date, and guest count to your WordPress database, and immediately pushes a new row with those same details to the connected Google Spreadsheet.&lt;/p&gt;

&lt;p&gt;The row appears within moments. Anyone with access to the spreadsheet can see the new RSVP without logging into WordPress. Where before this data only existed in an email you might never open, it now lives in two places at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Aren't Contact Form 7 Submissions Reaching Google Sheets?
&lt;/h2&gt;

&lt;p&gt;The most common cause with Contact Form 7 specifically is a field name mismatch, since CF7 tags are typed by hand rather than picked from a visual list. Beyond that, submissions can also fail to sync because a form isn't yet listed in the dropdown, the connected account can't access the spreadsheet, there's a redirect URI mismatch, or the Google Sheets and Drive APIs aren't enabled. Submit a test RSVP from the frontend first, then check the causes below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data is appearing in the wrong columns, or not at all.&lt;/strong&gt;&amp;nbsp;Open the Export Profile and compare each mapped field against the actual tag names in your Contact Form 7 form editor. A field written as&amp;nbsp;&lt;code&gt;your-eventdate&lt;/code&gt;&amp;nbsp;in the form but mapped as&amp;nbsp;&lt;code&gt;your-event-date&lt;/code&gt;&amp;nbsp;in Form Vibes will fail silently. Retype the mapping to match exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Contact Form 7 form is not showing in the Form dropdown.&lt;/strong&gt;&amp;nbsp;Confirm Contact Form 7 is active. Then confirm the form has received at least one submission on the frontend since Form Vibes was activated. Form Vibes only lists forms that have already received at least one submission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spreadsheet is not showing in the dropdown.&lt;/strong&gt;&amp;nbsp;Check that the Google account connected in Form Vibes Settings has access to the spreadsheet. If the spreadsheet is owned by a different Google account, share it with the connected account before trying again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirect URI mismatch error.&lt;/strong&gt;&amp;nbsp;The redirect URI entered in your Google Cloud Console app settings must match exactly the one Form Vibes provides. Open your Google Cloud Console project, navigate to OAuth credentials, and verify the redirect URI matches the one shown in Form Vibes Settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Sheets and Drive APIs are not enabled.&lt;/strong&gt;&amp;nbsp;Both APIs must be enabled inside your Google Cloud Console project. If sync silently fails and the field mapping checks out, this is usually the cause.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App verification warning during authentication.&lt;/strong&gt;&amp;nbsp;This is expected for self-configured OAuth apps and does not indicate a problem. Click Show Advanced, then Go to App, and approve all permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Form Vibes for Contact Form 7 and Google Sheets?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It starts you from zero, not from a smoother sync.&lt;/strong&gt;&amp;nbsp;Some plugins improve on a feature Contact Form 7 already has. Contact Form 7 has nothing to improve on here. It has no entries table and no storage of any kind, so Form Vibes is the entire foundation, not an upgrade to one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every submission is also saved to your WordPress database.&lt;/strong&gt;&amp;nbsp;So a temporary Google API hiccup, or a sync that hasn't run yet, never means the data is lost. It's already sitting in WordPress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One plugin for nine form builders.&lt;/strong&gt;&amp;nbsp;If you use Contact Form 7 alongside Elementor Forms or WPForms, one Form Vibes install handles all of them. No second connector plugin, no second Google configuration. Setting up&amp;nbsp;&lt;a href="https://formvibes.com/tutorial/elementor-form-to-google-sheets/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content&amp;amp;utm_content=form_vibes"&gt;Elementor forms with Google Sheets&lt;/a&gt;&amp;nbsp;works exactly the same way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notes, submission status, analytics, and a role manager&lt;/strong&gt;&amp;nbsp;included, none of which Contact Form 7 has on its own.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Contact Form 7 was built to be lightweight, and that means it stores nothing on its own. One missed email is the only thing standing between a submission and losing it for good. Form Vibes Pro closes that gap completely: every entry is saved to your WordPress database and synced to Google Sheets automatically, so a single inbox is never the only record you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;Does Contact Form 7 have a built-in Google Sheets integration?&lt;/p&gt;

&lt;p&gt;No. Contact Form 7 sends submissions by email only and has no built-in database, export option, or Google Sheets connection. A plugin like Form Vibes Pro is required to sync submissions to a spreadsheet.&lt;/p&gt;

&lt;p&gt;Do I need Form Vibes Pro to connect Contact Form 7 to Google Sheets?&lt;/p&gt;

&lt;p&gt;Yes. The Google Sheets integration is a Form Vibes Pro feature. The free version still saves every Contact Form 7 submission to your WordPress database and lets you export to CSV on demand. The automatic real-time push to Google Sheets is the Pro upgrade.&lt;/p&gt;

&lt;p&gt;What if my Contact Form 7 field names don't match my sheet's column headers?&lt;/p&gt;

&lt;p&gt;Nothing will sync correctly until they match. Since Contact Form 7 fields are typed manually rather than chosen from a visual list, open the Export Profile's Columns Mapping tab and compare each field tag against your actual form editor. Fields like your-event-date and your-eventdate look similar but won't map to each other automatically.&lt;/p&gt;

&lt;p&gt;Can I sync multiple Contact Form 7 forms to different Google Sheets?&lt;/p&gt;

&lt;p&gt;Yes. Set up one Export Profile per Contact Form 7 form, and point each one to its own spreadsheet or its own worksheet inside the same spreadsheet. There's no limit on how many forms you can connect.&lt;/p&gt;

&lt;p&gt;Can I filter which Contact Form 7 submissions get sent to Google Sheets?&lt;/p&gt;

&lt;p&gt;Yes. The Data Filter section supports Equal, Not Equal, Contain, and Not Contain conditions, and lets you choose whether any or all conditions must match. Filtered-out submissions still get saved to your WordPress database; the filter only affects what reaches Sheets.&lt;/p&gt;

&lt;p&gt;Does the same integration work with my other form plugins?&lt;/p&gt;

&lt;p&gt;Yes. The Google Sheets sync in Form Vibes Pro works with Elementor Forms, WPForms, Gravity Forms, Ninja Forms, WS Form, Bricks Builder forms, Beaver Builder forms, and Everest Forms, alongside Contact Form 7. One plugin, one Google connection, all your forms.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>cf7</category>
      <category>contactform7</category>
      <category>googlesheets</category>
    </item>
    <item>
      <title>What's New in WordPress 7.1: Tab Block, Responsive Design, Pseudo States, and much more</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Mon, 17 Aug 2026 06:31:09 +0000</pubDate>
      <link>https://dev.to/wpvibes/whats-new-in-wordpress-71-tab-block-responsive-design-pseudo-states-and-much-more-4p3j</link>
      <guid>https://dev.to/wpvibes/whats-new-in-wordpress-71-tab-block-responsive-design-pseudo-states-and-much-more-4p3j</guid>
      <description>&lt;p&gt;WordPress 7.1 comes out on August 19, 2026. It's the second major WordPress release this year. Instead of one headline feature, this release focuses on improving the entire editing experience: responsive design controls, interactive state styling, better media handling, new blocks, editor usability improvements, and a few developer-focused APIs.&lt;/p&gt;

&lt;p&gt;Whether you're a site owner, content creator, theme developer, or plugin developer, WordPress 7.1 has something that makes building and managing your site faster and more flexible.&lt;/p&gt;

&lt;p&gt;This guide walks through every major feature coming in WordPress 7.1, what it does, and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How Does WordPress 7.1 Change the Way You Design Pages?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1 lets you style how a block looks on different screen sizes and how it reacts to a mouse or keyboard, directly in the editor, without writing a line of CSS. This is the headline feature of the release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsive Styling Comes to the Block Editor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress 7.1 introduces responsive styling directly inside the editor, allowing you to customize how blocks look across different screen sizes without writing CSS.&lt;/p&gt;

&lt;p&gt;Depending on the block, you'll be able to configure different values for typography, spacing, layout, dimensions, and other design settings across various screen sizes. This gives much greater control over how your content appears on different devices while keeping the editing experience visual and intuitive.&lt;/p&gt;

&lt;p&gt;To use it, select a block, then click the view icon. Choose responsive styling, then pick the device you want to design for: desktop, tablet, or phone. You can make a heading larger on desktop and smaller on mobile, all from the same panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88mfir4tuqqu3kkyejk1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88mfir4tuqqu3kkyejk1.webp" alt="Responsive Styling in WordPress 7.1" width="800" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Tips:&lt;/strong&gt; If you're exploring modern WordPress editing capabilities, check out our guide on&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://wpvibes.com/blog/php-only-blocks-wordpress/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;PHP-only blocks in WordPress 7.0&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;to see how block development is becoming simpler for developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Style Every State Without CSS
&lt;/h3&gt;

&lt;p&gt;Until now, changing how a button or link looks on hover meant writing custom CSS. WordPress 7.1 adds visual controls for these&amp;nbsp;&lt;a href="https://make.wordpress.org/core/2026/08/05/pseudo-and-custom-style-states-in-wordpress-7-1/" rel="noopener noreferrer"&gt;&lt;strong&gt;interactive pseudo states&lt;/strong&gt;&lt;/a&gt;, so you can style them directly in the editor.&lt;/p&gt;

&lt;p&gt;You can switch between states(Hover, Focus, Focus-Visible, and Active) and set a different color, border, background, shadow, or typography for each one. This makes it easier to build engaging interfaces while also improving accessibility.&lt;/p&gt;

&lt;p&gt;A new "State" option lets you set a different style for when someone hovers over a button, tabs into it with a keyboard, or clicks it. The preview updates live as you work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26k94c7fq32atrjpsyen.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26k94c7fq32atrjpsyen.webp" alt="WordPress Pseudo States Styling" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; Hover and focus control is only available on the Button block and the Navigation Link block.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New With the Notes Feature in WordPress 7.1?
&lt;/h2&gt;

&lt;p&gt;Notes in WordPress 7.1 lets you leave feedback on a specific piece of text instead of an entire block, so a comment no longer needs a Slack message to explain which sentence it's about. The feature first appeared in WordPress 6.9, and this release makes it genuinely usable for day-to-day editorial work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46ipawzlwm4hgkk5w6kq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46ipawzlwm4hgkk5w6kq.webp" alt="Open Setting to Add a Note in WordPress" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now attach a note to a specific selection of text instead of an entire block, so feedback points at exactly the phrase you mean. Notes support rich formatting: bold (Ctrl/⌘ + B), italics (Ctrl/⌘ + I), links (Ctrl/⌘ + K), and emojis, so a note can actually explain itself instead of reading like a plain-text scrap.&lt;/p&gt;

&lt;p&gt;Type "@" inside a note and a list of your site's users pops up, letting you tag a specific collaborator, who then gets an email notification linking straight back to the post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhyn8x69ebpjwsg3qd33w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhyn8x69ebpjwsg3qd33w.webp" alt="Add Notes on Particular Line or Whole Para" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also leave more than one note on the same block, which matters if two different people are commenting on two different things in the same paragraph. Long notes collapse by default with a "Show more" toggle, so a detailed note doesn't take over the sidebar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy43fr6t1wy8svfn8cbs4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy43fr6t1wy8svfn8cbs4.webp" alt="Multiple Users Added Notes on Particular Line or Whole Para" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does Client-Side Media Processing Speed Up Your Uploads?
&lt;/h2&gt;

&lt;p&gt;With&amp;nbsp;&lt;a href="https://make.wordpress.org/core/2026/07/22/client-side-media-processing-in-wordpress-7-1/" rel="noopener noreferrer"&gt;client-side media processing&lt;/a&gt;, WordPress 7.1 moves image processing out of your server and into the browser. Previously, when you uploaded an image, your server had to generate every thumbnail size, handle format conversion, and fix rotation using PHP. Now the browser does that work instead, using a fast, established image library called libvips running right inside your browser, and sends the finished files to WordPress.&lt;/p&gt;

&lt;p&gt;The practical upshot: fewer failed uploads on large image files, since you won't hit your host's PHP memory limit mid-upload. Your server also does less work overall, since it's no longer generating every thumbnail size itself. And because the browser's processing produces better-compresse&lt;/p&gt;

&lt;p&gt;Two upload types get a real, visible improvement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Animated GIFs you upload are automatically converted into lightweight video files (using the same kind of technology your browser already uses to play video), so a GIF that used to weigh down your page loads much lighter without you doing anything differently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HEIC photos, the default format iPhones use, are automatically converted to JPG before upload. You no longer need a plugin just to make iPhone photos work on your site.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alongside this, the Media Library grid now scrolls infinitely by default instead of paginating, so you can keep scrolling through your library instead of clicking "next page" repeatedly. If you'd rather have the old paginated view back, there's a toggle for it in your user profile settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faokdsjmuprbtpczi59ov.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faokdsjmuprbtpczi59ov.webp" alt="Enable or Disable Infinite Scroll for Media Library Grid View" width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What New Blocks Does WordPress 7.1 Add?
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1 adds two brand new blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tabs Block
&lt;/h3&gt;

&lt;p&gt;The Tabs block organizes content into clickable panels, letting a visitor click between sections instead of scrolling through a long page. It's a natural fit for FAQs, product comparisons, or any content with a handful of parallel options.&lt;/p&gt;

&lt;p&gt;Each tab panel can hold any other block inside it, not just text. You can drop in images, buttons, columns, galleries, or even another Tabs block, so a tab isn't limited to a paragraph.&lt;/p&gt;

&lt;p&gt;The tabs themselves are fully styleable. You can set the color, typography, and spacing for the tab buttons, and control the overall look of the tab panel separately, so the whole block can match your site's design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkktoddvom5mo9ph1spo4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkktoddvom5mo9ph1spo4.webp" alt="New Tabs Blocks In WordPress 7.1" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Playlist Block
&lt;/h3&gt;

&lt;p&gt;The Playlist block lets you embed a simple audio playlist, complete with a waveform visualization that shows the shape of each track. It supports whatever audio file types WordPress already accepts.&lt;/p&gt;

&lt;p&gt;The playlist itself has several toggles you can turn on or off: showing the tracklist, artist names, track numbers, track duration, tracklist images, and whether the track image appears on the play button. You can also set the track order to ascending or descending.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frf2bvnfgksimp1cklsna.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frf2bvnfgksimp1cklsna.webp" alt="New Playlist Blocks In WordPress 7.1" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each track has its own settings: artist, album, title, and a track image, so every song in the playlist can show proper metadata and cover art instead of a generic placeholder.&lt;/p&gt;

&lt;p&gt;On the styling side, the block has its own controls for the block and the waveform background, and their shape, so the playlist can match your site's design rather than looking like a generic embed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzo1ei53x9clii7k02xql.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzo1ei53x9clii7k02xql.webp" alt="Playlist Block Playlist Track Settings In WordPress 7.1" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New for the Icon Block in WordPress 7.1?
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1 opens up the Icon block to icon sets beyond WordPress's own defaults, and gives the Icon block itself a few small but useful upgrades.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom SVG Icons API
&lt;/h3&gt;

&lt;p&gt;One of the biggest developer-focused additions in WordPress 7.1 is the new&amp;nbsp;&lt;a href="https://make.wordpress.org/core/2026/07/24/registering-and-rendering-svg-icons-in-wordpress-7-1/" rel="noopener noreferrer"&gt;&lt;strong&gt;Custom SVG Icons API&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Previously, developers who wanted custom icons often had to register them using JavaScript, build custom icon libraries, or depend on third-party solutions. WordPress now provides an official API for registering and managing SVG icon collections.&lt;/p&gt;

&lt;p&gt;Developers can register icons individually or as complete collections and make them available throughout the Block Editor. Once registered, these icons can be used in custom blocks, plugins, themes, and the Icon Block itself.&lt;/p&gt;

&lt;p&gt;If you build WordPress plugins and want the full technical walkthrough, including a working example plugin,&amp;nbsp;&lt;a href="https://wpvibes.com/blog/wordpress-custom-icons/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;see our complete guide on registering custom SVG icons in WordPress 7.1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuo8sq4zwvbw8keugbwb3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuo8sq4zwvbw8keugbwb3.webp" alt="Custom SVG Icons Added" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Icon Block Toolbar Improvements
&lt;/h3&gt;

&lt;p&gt;The Icon block itself picked up a few smaller improvements too: newly inserted Icon blocks now start with a default icon instead of an empty placeholder, and the toolbar has new controls to flip an icon horizontally or vertically, or rotate it 90 degrees at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuawnhe2cr8b5fe1ja5fh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuawnhe2cr8b5fe1ja5fh.webp" alt="Icon Block Toolbar Improve With Flip and Rotate Settings" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Else Changed in the WordPress Admin?
&lt;/h2&gt;

&lt;p&gt;A handful of smaller changes add up to a more consistent day-to-day experience.&lt;/p&gt;

&lt;p&gt;The admin toolbar now appears inside the post editor and the Site Editor for the first time. Previously, opening either editor made the toolbar disappear entirely, and the only way back to the dashboard was clicking the "W" logo in the editor's own toolbar, which didn't read as a back button to most people.&lt;/p&gt;

&lt;p&gt;WordPress 7.1 fixes both problems: the editor toolbar now has a proper back button (a left-pointing chevron), and the admin toolbar itself shows your site's icon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frps2lknhqzjou06idn38.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frps2lknhqzjou06idn38.webp" alt="WordPress Admin Toolbar and Back Button Displayed on Post Editor" width="799" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftrngqo6c28nh2ks0ly20.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftrngqo6c28nh2ks0ly20.webp" alt="WordPress Admin Toolbar Displayed on Site Editor" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;&lt;strong&gt;Site Editor sidebar&lt;/strong&gt;&amp;nbsp;finally matches whatever admin color scheme you've chosen in your profile, instead of always defaulting to black regardless of your preference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsi6t7svw09jqeqd0eewt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsi6t7svw09jqeqd0eewt.webp" alt="Site Editor Matched User Profile Theme in WordPress" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's also a new Identity section under Design in the Site Editor, grouping your site title, tagline, logo, and icon in one place instead of scattering them across Settings and templates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwvl2vqtyat1kuc95wdna.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwvl2vqtyat1kuc95wdna.webp" alt="Site Editor Showing New Identity Section" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Other Small Improvements Should You Know About?
&lt;/h2&gt;

&lt;p&gt;A few smaller design tools rounded out this release. None of these are big enough for their own section, but they're worth knowing about.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text-shadow support&lt;/strong&gt;: You can now add a text shadow through Global Styles. It's an early implementation, currently only configurable through theme.json rather than a visual control, so it's more useful to theme developers than to someone editing a single post today.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Background gradients&lt;/strong&gt;: Several blocks (Group, Accordion, Pullquote, Post Content, and Quote) now support background gradients by default, controllable right in the block settings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimum width&lt;/strong&gt;: Blocks can now have a minimum width set, alongside the existing height and width controls.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Developer Experience Improvements Does WordPress 7.1 Bring?
&lt;/h2&gt;

&lt;p&gt;Beyond visual improvements, WordPress 7.1 introduces several enhancements aimed at developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Block Bindings
&lt;/h3&gt;

&lt;p&gt;Block Bindings already let you connect a block's content, like a Heading, Paragraph, or Button, to a dynamic data source such as a custom field, without writing rendering code. WordPress 7.1 extends that same capability to list items, so a bullet or numbered list can pull from a data source too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expanded Abilities API
&lt;/h3&gt;

&lt;p&gt;The Abilities API, which lets WordPress expose specific actions to AI tools and other integrations, picks up better filtering, validation, and a clearer way to mark an ability as publicly available.&lt;/p&gt;

&lt;p&gt;If you're building AI-connected features or automations on top of WordPress, this makes those integrations more reliable and easier to control.&amp;nbsp;&lt;a href="https://wpvibes.com/blog/wordpress-abilities-api-mcp/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;See our Abilities API + MCP guide for the full guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1 is a design and collaboration release: responsive styling and hover states without touching CSS, Notes that finally feel built for teams, faster image handling for supported browsers, and two genuinely useful new blocks. None of it demands a relearning curve, but the responsive and interactive styling controls alone are worth exploring the moment you update.&lt;/p&gt;

&lt;p&gt;Keep an eye on your usual update workflow: back up first, test on staging if you run custom blocks or older plugins, then roll it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When will WordPress 7.1 be released?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress 7.1 is scheduled for release on&amp;nbsp;&lt;strong&gt;August 19, 2026&lt;/strong&gt;. If you're running a production website, it's a good idea to test the update on a staging site first, especially if you use custom themes or plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does WordPress 7.1 support real-time, multi-person editing?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;No. WordPress 7.1 focuses on asynchronous collaboration through the upgraded Notes feature. Two people editing the same post at the same time, Google Docs style, is not part of this release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the Custom SVG Icons API in WordPress 7.1?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Custom SVG Icons API is a new developer feature that provides a standardized way to register and manage custom SVG icon collections. Developers can register individual icons or entire icon libraries and use them throughout the Block Editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does WordPress 7.1 require a new minimum PHP version?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;WordPress 7.1 doesn't introduce a new PHP requirement beyond what WordPress 7.0 already set. If your site runs comfortably on 7.0, it will run on 7.1.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Custom SVG Icons: An Exciting WordPress 7.1 Feature</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 13 Aug 2026 06:35:51 +0000</pubDate>
      <link>https://dev.to/wpvibes/custom-svg-icons-an-exciting-wordpress-71-feature-1n8k</link>
      <guid>https://dev.to/wpvibes/custom-svg-icons-an-exciting-wordpress-71-feature-1n8k</guid>
      <description>&lt;p&gt;For years, adding a custom icon to the block editor meant reaching for JavaScript, a build step, or a workaround that only half-worked. WordPress 7.1 is about to change that.&lt;/p&gt;

&lt;p&gt;A new public API for custom SVG icons is landing in the next release. You'll be able to register your own icons, group them into named collections, and have them show up in the&amp;nbsp;&lt;code&gt;core/icon&lt;/code&gt;&amp;nbsp;block picker, render server-side in PHP, and appear over the REST API, all from a plugin, with no JavaScript build step at all.&lt;/p&gt;

&lt;p&gt;This guide walks through building a small plugin called&amp;nbsp;&lt;code&gt;wpvibes-custom-icons&lt;/code&gt;&amp;nbsp;that registers a collection, adds icons two different ways (inline SVG and file paths), and shows how to use those icons in the block editor, in PHP templates, and as custom block icons. The complete plugin is on &lt;a href="https://github.com/webtechhardik/wpviibes-custom-icons" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;WordPress 7.1 releases on August 19, 2026. The code below matches what's expected to ship on release day, and we'll come back and verify every detail against the final build once it's out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt;&amp;nbsp;&lt;code&gt;wpvibes-custom-icons&lt;/code&gt;&amp;nbsp;&lt;strong&gt;plugin&lt;/strong&gt;&amp;nbsp;with a registered icon collection called&amp;nbsp;&lt;code&gt;wpvibes&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Three ready-to-use icons&lt;/strong&gt;: a heart (registered inline), a star, and a bookmark (both registered from&amp;nbsp;&lt;code&gt;.svg&lt;/code&gt;&amp;nbsp;files)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A bulk registration pattern&lt;/strong&gt;&amp;nbsp;that reads every SVG file from a folder and registers each one automatically&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A practical shortcode&lt;/strong&gt;&amp;nbsp;for inserting any registered icon into post content&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real integration&lt;/strong&gt;&amp;nbsp;with our&amp;nbsp;&lt;a href="https://wpvibes.com/blog/php-only-blocks-wordpress/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;PHP-only blocks guide&lt;/a&gt;, using a custom icon as a block icon&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WordPress 7.1 or later&lt;/strong&gt;&amp;nbsp;(7.1 RC works for development)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A few SVG icon files&lt;/strong&gt;&amp;nbsp;(or use the ones provided below)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local development environment&lt;/strong&gt;&amp;nbsp;(LocalWP, WordPress Studio, wp-env)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic PHP knowledge&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Understand the API in Three Minutes
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1's custom icons API has three main functions and one organizing concept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;wp_register_icon_collection( $name, $args ): register a named group of icons&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;wp_register_icon( $name, $args ): register a single icon&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;wp_get_icon( $name, $args ): render an icon's SVG in PHP (returns the SVG string)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The organizing concept: collections.&amp;nbsp;Every icon belongs to a collection. The collection name becomes a prefix for every icon inside it, which is what makes&amp;nbsp;core/plus&amp;nbsp;different from&amp;nbsp;wpvibes/plus. This keeps custom icons from colliding with core icons or with other plugins.&lt;/p&gt;

&lt;p&gt;Two ways to provide the SVG when registering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;content: the SVG markup as a string, embedded directly in PHP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;file_path: an absolute path to an&amp;nbsp;.svg&amp;nbsp;file on disk&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use one or the other per icon, not both. Inline&amp;nbsp;content&amp;nbsp;is handy for one or two icons;&amp;nbsp;file_path&amp;nbsp;scales better for a folder of many.&lt;/p&gt;

&lt;p&gt;That's the API. Now let's build with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Set Up the Plugin and Register the Collection
&lt;/h2&gt;

&lt;p&gt;While building this, one thing became clear quickly:&amp;nbsp;&lt;strong&gt;the collection has to be registered before you can add icons to it.&lt;/strong&gt;&amp;nbsp;If you try to register an icon whose collection doesn't exist yet, the icon silently fails to appear in the picker.&lt;/p&gt;

&lt;p&gt;Create a folder inside&amp;nbsp;&lt;code&gt;wp-content/plugins/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wpvibes-custom-icons/
├── wpvibes-custom-icons.php
└── icons/
    ├── star.svg
    └── bookmark.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create&amp;nbsp;&lt;code&gt;wpvibes-custom-icons.php&lt;/code&gt;&amp;nbsp;with the plugin header and the collection registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: WPVibes Custom Icons
 * Description: Register custom SVG icons for WordPress 7.1+.
 * Version:     1.0.0
 * Author:      WPVibes
 * Requires at least: 7.1
 * Requires PHP: 7.4
 */&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPVIBES_ICONS_PATH'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugin_dir_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Step 1: Register the icon collection.
 */&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_register_icon_collection'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpvibes_register_icon_collection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;wp_register_icon_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'wpvibes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPVibes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-icons'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collection name (&lt;code&gt;wpvibes&lt;/code&gt;) becomes the prefix for every icon that follows:&amp;nbsp;&lt;code&gt;wpvibes/heart&lt;/code&gt;,&amp;nbsp;&lt;code&gt;wpvibes/star&lt;/code&gt;, and so on. The&amp;nbsp;&lt;code&gt;label&lt;/code&gt;&amp;nbsp;is what shows up as a section heading in the icon picker.&lt;/p&gt;

&lt;p&gt;Activate the plugin from&amp;nbsp;&lt;strong&gt;Plugins → Installed Plugins&lt;/strong&gt;. Nothing to see yet, since collections don't render until they contain icons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Register Icons Two Ways
&lt;/h2&gt;

&lt;p&gt;WordPress supports two ways to register an icon. This step uses both: the inline&amp;nbsp;&lt;code&gt;content&lt;/code&gt;&amp;nbsp;method for the heart icon, and the&amp;nbsp;&lt;code&gt;file_path&lt;/code&gt;&amp;nbsp;method for star and bookmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1: Register an inline icon with&amp;nbsp;content
&lt;/h3&gt;

&lt;p&gt;The heart SVG lives directly in the PHP file. Useful when there's just one or two icons and you don't want extra files.&lt;/p&gt;

&lt;p&gt;Add this to&amp;nbsp;&lt;code&gt;wpvibes-custom-icons.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Step 2: Register icons using inline SVG content.
 */&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_register_inline_icons'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpvibes_register_inline_icons&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;wp_register_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'wpvibes/heart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Heart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-icons'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"&amp;gt;&amp;lt;path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/&amp;gt;&amp;lt;/svg&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How the code flows:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The action hooks into&amp;nbsp;&lt;code&gt;init&lt;/code&gt;&amp;nbsp;at priority&amp;nbsp;&lt;code&gt;20&lt;/code&gt;, which is later than the collection registration (default priority&amp;nbsp;&lt;code&gt;10&lt;/code&gt;), so the collection exists before the icon tries to join it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The icon name&amp;nbsp;&lt;code&gt;wpvibes/heart&lt;/code&gt;&amp;nbsp;follows the pattern&amp;nbsp;&lt;code&gt;collection/icon-slug&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The&amp;nbsp;&lt;code&gt;label&lt;/code&gt;&amp;nbsp;shows in the icon picker;&amp;nbsp;&lt;code&gt;content&lt;/code&gt;&amp;nbsp;holds the SVG markup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The SVG uses&amp;nbsp;&lt;code&gt;stroke="currentColor"&lt;/code&gt;&amp;nbsp;so the icon inherits color from surrounding text, with no hardcoded fill.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2: Register icons from&amp;nbsp;&lt;code&gt;.svg&lt;/code&gt;&amp;nbsp;files
&lt;/h3&gt;

&lt;p&gt;For icons stored on disk, use&amp;nbsp;&lt;code&gt;file_path&lt;/code&gt;&amp;nbsp;instead. Save these two files in the&amp;nbsp;&lt;code&gt;icons/&lt;/code&gt;&amp;nbsp;folder:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;icons/star.svg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;polygon&lt;/span&gt; &lt;span class="na"&gt;points=&lt;/span&gt;&lt;span class="s"&gt;"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;icons/bookmark.svg&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now register both from PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Step 3: Register icons from .svg files.
 */&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_register_file_icons'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpvibes_register_file_icons&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;wp_register_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'wpvibes/star'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'label'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Star'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-icons'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'file_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_ICONS_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'icons/star.svg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;wp_register_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'wpvibes/bookmark'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'label'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Bookmark'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-icons'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'file_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_ICONS_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'icons/bookmark.svg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same registration function, same collection. Only&amp;nbsp;&lt;code&gt;content&lt;/code&gt;&amp;nbsp;becomes&amp;nbsp;&lt;code&gt;file_path&lt;/code&gt;. WordPress reads the file on demand.&lt;/p&gt;

&lt;p&gt;Now open the&amp;nbsp;&lt;code&gt;core/icon&lt;/code&gt;&amp;nbsp;block in the editor. The picker shows a new&amp;nbsp;&lt;strong&gt;WPVibes&lt;/strong&gt;&amp;nbsp;section with Heart, Star, and Bookmark. Insert any of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcje7z8ste2ubesxaes5t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcje7z8ste2ubesxaes5t.webp" alt="Custom Icon Block Picker Added in WordPress" width="799" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; If an icon doesn't appear, the most common cause is registering the icon before the collection exists. Make sure the collection is registered first (default priority), and the icons are registered after (priority&amp;nbsp;&lt;code&gt;20&lt;/code&gt;&amp;nbsp;in the examples above).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Register a Whole Folder of Icons (Bulk Pattern)
&lt;/h2&gt;

&lt;p&gt;For a real icon library, registering one icon at a time doesn't scale. Nobody writes 30&amp;nbsp;wp_register_icon()&amp;nbsp;calls by hand. The practical pattern: drop SVGs into a folder, and let PHP auto-register everything on the next request.&lt;/p&gt;

&lt;p&gt;Replace the individual&amp;nbsp;wpvibes_register_file_icons()&amp;nbsp;function with this bulk version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_register_all_file_icons'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpvibes_register_all_file_icons&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$icons_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_ICONS_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'icons/'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$svg_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$icons_dir&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'*.svg'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$svg_files&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$svg_files&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$file_path&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$slug&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.svg'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ucwords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$slug&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;wp_register_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'wpvibes/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'file_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Code flow in plain words:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;glob( $icons_dir . '*.svg' )&lt;/code&gt;&amp;nbsp;returns every&amp;nbsp;&lt;code&gt;.svg&lt;/code&gt;&amp;nbsp;file in the folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;basename( $file_path, '.svg' )&lt;/code&gt;&amp;nbsp;takes just the filename without extension, so&amp;nbsp;&lt;code&gt;advanced-button.svg&lt;/code&gt;&amp;nbsp;becomes&amp;nbsp;&lt;code&gt;advanced-button&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ucwords( str_replace( '-', ' ', $slug ) )&lt;/code&gt;&amp;nbsp;turns the slug into a readable label:&amp;nbsp;&lt;code&gt;advanced-button&lt;/code&gt;&amp;nbsp;becomes "Advanced Button" for the picker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The loop registers each icon under the&amp;nbsp;&lt;code&gt;wpvibes/&lt;/code&gt;&amp;nbsp;collection.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop any new SVG into the&amp;nbsp;&lt;code&gt;icons/&lt;/code&gt;&amp;nbsp;folder, refresh the editor, and it appears in the picker. No code changes needed.&lt;/p&gt;

&lt;p&gt;This is the pattern used to register the WPVibes icon library. The same six lines register the entire icon set: heart (registered inline earlier), star, bookmark, and any additional icons dropped into the folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6khsmuc27nla3kzazqo0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6khsmuc27nla3kzazqo0.webp" alt="Multiple Icons Added in Custom SVG Icon Tab" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Use the Icons in Three Places
&lt;/h2&gt;

&lt;p&gt;Once icons are registered, three places automatically get access to them. Here's each one with a small example.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1: In the core/icon block
&lt;/h3&gt;

&lt;p&gt;The&amp;nbsp;core/icon&amp;nbsp;block picker automatically shows every registered collection. Users insert the block, pick a WPVibes icon, and it renders on the frontend. No extra code needed.&lt;/p&gt;

&lt;p&gt;Icons also appear at the REST endpoint&amp;nbsp;/wp-json/wp/v2/icons&amp;nbsp;(with a companion&amp;nbsp;/wp-json/wp/v2/icon-collections&amp;nbsp;endpoint for collections), for any external tool that reads WordPress data.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2: In PHP via&amp;nbsp;wp_get_icon()
&lt;/h3&gt;

&lt;p&gt;The&amp;nbsp;wp_get_icon()&amp;nbsp;function returns an icon's SVG as a string. The simplest form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;wp_get_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes/heart'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For real templates, pass a second argument to control size, class, and accessibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;wp_get_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'wpvibes/star'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'size'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'featured-icon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Featured'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-icons'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;size&lt;/code&gt;&amp;nbsp;sets the SVG's&amp;nbsp;&lt;code&gt;width&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;height&lt;/code&gt;&amp;nbsp;in pixels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;class&lt;/code&gt;&amp;nbsp;adds a CSS class to the SVG element&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;label&lt;/code&gt;&amp;nbsp;sets&amp;nbsp;&lt;code&gt;aria-label&lt;/code&gt;&amp;nbsp;for screen readers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A practical shortcode example.&lt;/strong&gt;&amp;nbsp;Here's a small&amp;nbsp;&lt;code&gt;[wpvibes_icon]&lt;/code&gt;&amp;nbsp;shortcode that lets anyone drop an icon into post content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_icon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes_icon_shortcode'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpvibes_icon_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$atts&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$atts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shortcode_atts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'heart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'text'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'size'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;$atts&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_get_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'wpvibes/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;sanitize_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$atts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'size'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$atts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'size'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_html_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$atts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'class'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$icon&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'&amp;lt;span class="wpvibes-icon-inline"&amp;gt;%s %s&amp;lt;/span&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$atts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it inside any post, page, or widget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wpvibes_icon&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"heart"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wpvibes_icon&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"star"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Featured post"&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"32"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wpvibes_icon&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bookmark"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Save for later"&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The icon appears next to the text, sized to whatever the&amp;nbsp;&lt;code&gt;size&lt;/code&gt;&amp;nbsp;attribute says. Because the SVG uses&amp;nbsp;&lt;code&gt;currentColor&lt;/code&gt;, the icon picks up the surrounding text color automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3: As a custom block icon in&amp;nbsp;register_block_type()
&lt;/h3&gt;

&lt;p&gt;Custom icons can replace the default Dashicon in any block registration. In our&amp;nbsp;&lt;a href="https://wpvibes.com/blog/php-only-blocks-wordpress/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;PHP-only blocks guide&lt;/a&gt;, block icons look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'icon' =&amp;gt; 'warning',   // Uses a Dashicon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With WordPress 7.1 custom icons, that becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'icon' =&amp;gt; 'wpvibes/star',   // Uses a registered custom icon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The block inserter now shows the branded WPVibes icon instead of a generic Dashicon. This works for both JavaScript-registered blocks and PHP-only blocks.&lt;/p&gt;

&lt;p&gt;Same registration function, three consumer contexts. This "register once, use everywhere" pattern is the same architecture used by WordPress's Abilities API, a good sign that WordPress is heading toward a genuinely consistent developer story across its foundational APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Style Custom Icons with CSS
&lt;/h2&gt;

&lt;p&gt;Custom icons look native by default, but real integration means they adapt to their surroundings: inheriting text color, scaling with font size, changing appearance on hover.&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;class&amp;nbsp;argument passed through&amp;nbsp;wp_get_icon()&amp;nbsp;(shown in Step 5.2) already covers the standard case of adding a CSS hook to the SVG. This section covers what to do with that hook, plus patterns that work everywhere the icon appears, including in the block editor where the&amp;nbsp;class&amp;nbsp;argument isn't in play.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1: Use&amp;nbsp;currentColor&amp;nbsp;for color inheritance
&lt;/h3&gt;

&lt;p&gt;The single most important pattern in SVG design: use&amp;nbsp;stroke="currentColor"&amp;nbsp;(or&amp;nbsp;fill="currentColor") instead of hardcoded colors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ Hardcoded color: icon always red --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; 
&lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"#dc2626"&lt;/span&gt; 
&lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;    

&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With&amp;nbsp;currentColor, an icon inside red text renders red. Inside a dark theme, it renders dark. Inside a link, it matches the link color. The SVG behaves like text.&lt;/p&gt;

&lt;p&gt;This is why the icons in our library ship without hardcoded colors. It's the difference between an icon that fits any theme and one that clashes with half of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2: Override color when a specific color is needed
&lt;/h3&gt;

&lt;p&gt;Sometimes an icon&amp;nbsp;must&amp;nbsp;be a specific color regardless of surroundings, like a brand mark or a status indicator. Use CSS to force it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.featured-icon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f59e0b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c"&gt;/* Amber, regardless of surrounding text */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the SVG uses&amp;nbsp;&lt;code&gt;currentColor&lt;/code&gt;, setting&amp;nbsp;&lt;code&gt;color&lt;/code&gt;&amp;nbsp;on the SVG (via the class from Step 5.2) cascades into its stroke or fill. The icon renders amber even inside dark text.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3: Size and hover states
&lt;/h3&gt;

&lt;p&gt;For icons rendered through the shortcode or&amp;nbsp;&lt;code&gt;wp_get_icon()&lt;/code&gt;, the&amp;nbsp;&lt;code&gt;size&lt;/code&gt;&amp;nbsp;argument handles pixel sizing directly. For CSS-controlled sizing or hover states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.wpvibes-icon-inline&lt;/span&gt; &lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.15s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-icon-inline&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.4: When to use CSS classes vs. the&amp;nbsp;&lt;code&gt;size&lt;/code&gt;&amp;nbsp;argument
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;&amp;nbsp;&lt;code&gt;size&lt;/code&gt;&amp;nbsp;&lt;strong&gt;in&lt;/strong&gt;&amp;nbsp;&lt;code&gt;wp_get_icon()&lt;/code&gt;&amp;nbsp;for one-off cases where the exact pixel size matters and there's no reason to write CSS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use CSS classes&lt;/strong&gt;&amp;nbsp;(via the&amp;nbsp;&lt;code&gt;class&lt;/code&gt;&amp;nbsp;argument, or on the wrapper element) when the icon appears in many places and should stay consistent, or when styling depends on state (hover, focus, active) or context (inside a link, inside a heading).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the shortcode example in Step 5.2, both approaches work. Small tweaks per shortcode use the&amp;nbsp;&lt;code&gt;size&lt;/code&gt;&amp;nbsp;attribute; site-wide consistency uses CSS on&amp;nbsp;&lt;code&gt;.wpvibes-icon-inline svg&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Remove Icons and Collections
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1 ships two matching functions for unregistering: one for a single icon, one for a whole collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1: Remove a single icon
&lt;/h3&gt;

&lt;p&gt;Useful during development, or when a plugin update drops an icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp_unregister_icon( 'wpvibes/bookmark' );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The icon disappears from the picker on the next request. Any post that already inserted this icon in a&amp;nbsp;&lt;code&gt;core/icon&lt;/code&gt;&amp;nbsp;block will render empty (WordPress doesn't rewrite post content).&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2: Remove the entire collection on deactivation
&lt;/h3&gt;

&lt;p&gt;When the plugin is deactivated, all its icons should stop appearing in the picker. Unregistering the collection removes every icon inside it in one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;register_deactivation_hook( __FILE__, 'wpvibes_unregister_icons' );

function wpvibes_unregister_icons() {

    wp_unregister_icon_collection( 'wpvibes' );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to loop through individual icons. The collection cleanup handles it all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Full Plugin
&lt;/h2&gt;

&lt;p&gt;The complete&amp;nbsp;wpvibes-custom-icons&amp;nbsp;plugin, with sample SVGs, README, and the full bulk registration pattern, is on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Custom Icons Fit the Bigger Picture
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1's custom icons follow the same architectural pattern as the Abilities API and the&amp;nbsp;&lt;a href="https://wpvibes.com/blog/php-only-blocks-wordpress/?utm_source=dev.to&amp;amp;utm_medium=social&amp;amp;utm_campaign=blog_content"&gt;&lt;strong&gt;PHP-only blocks&lt;/strong&gt;&lt;/a&gt; guide&amp;nbsp;covered earlier: register once, use everywhere. The&amp;nbsp;core/icon&amp;nbsp;block reads from the registry,&amp;nbsp;wp_get_icon()&amp;nbsp;reads from the same registry in PHP, and the REST API exposes it to external tools including AI agents.&lt;/p&gt;

&lt;p&gt;WordPress is deliberately building foundational APIs, abilities, icons, block bindings, that follow the same register-once, discoverable-everywhere pattern. Plugin authors who invest in these APIs get their code integrated into the editor, the REST layer, and any tool built on the WordPress data model. Icons look like a small feature on the surface. Underneath, they're another piece of a much bigger shift in how WordPress plugins get built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This is a small API on paper, but it quietly closes a gap plugin developers have worked around for years. Register a collection, add icons inline or from files, and use them across the block editor, PHP templates, and REST, all without touching JavaScript. The bulk pattern with&amp;nbsp;glob()&amp;nbsp;scales from three icons to three hundred without changing the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Collections come first: icons attach to collections, not the other way around&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Two registration methods:&amp;nbsp;content&amp;nbsp;for inline,&amp;nbsp;&lt;code&gt;file_path&lt;/code&gt;&amp;nbsp;for icons on disk. Both work in the same collection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bulk registration beats per-icon calls:&amp;nbsp;&lt;code&gt;glob()&lt;/code&gt;&amp;nbsp;over a folder registers unlimited icons in six lines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;wp_get_icon( $name, $args )&lt;/code&gt;&amp;nbsp;accepts size, class, and label: a proper templating function, not just an SVG dump&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;currentColor&lt;/code&gt;&amp;nbsp;in SVGs is the pattern to follow: icons that adapt to their surroundings look native everywhere&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The same registration serves editor + PHP + REST: one function call, three consumer contexts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom icons complete the story for custom blocks. Combined with the&amp;nbsp;PHP-only blocks guide, a plugin can now register blocks, register their icons, and ship a completely branded editor experience without a build step. Once WordPress 7.1 ships on August 19, this is genuinely one of the releases worth updating for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I have to register a collection before registering icons?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Icons must belong to an existing collection, or they silently fail to appear in the picker. Register the collection at default priority on&amp;nbsp;&lt;code&gt;init&lt;/code&gt;, then register icons at a higher priority (like&amp;nbsp;&lt;code&gt;20&lt;/code&gt;) so they run after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I mix inline&amp;nbsp;&lt;/strong&gt;&lt;code&gt;content&lt;/code&gt;&lt;strong&gt;&amp;nbsp;icons and&amp;nbsp;&lt;/strong&gt;&lt;code&gt;file_path&lt;/code&gt;&lt;strong&gt;&amp;nbsp;icons in the same collection?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Each&amp;nbsp;&lt;code&gt;wp_register_icon()&lt;/code&gt;&amp;nbsp;call is independent. Use&amp;nbsp;&lt;code&gt;content&lt;/code&gt;&amp;nbsp;for one or two icons,&amp;nbsp;&lt;code&gt;file_path&lt;/code&gt;&amp;nbsp;for the rest. WordPress treats them the same once registered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do custom icons work as block icons in&amp;nbsp;&lt;/strong&gt;&lt;code&gt;register_block_type()&lt;/code&gt;&lt;strong&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Pass the registered icon name (like&amp;nbsp;&lt;code&gt;'wpvibes/star'&lt;/code&gt;) to the&amp;nbsp;&lt;code&gt;icon&lt;/code&gt;&amp;nbsp;key in&amp;nbsp;&lt;code&gt;register_block_type()&lt;/code&gt;. The block inserter shows the custom SVG instead of a Dashicon. Works for JavaScript-registered blocks and PHP-only blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does&amp;nbsp;&lt;/strong&gt;&lt;code&gt;wp_get_icon()&lt;/code&gt;&lt;strong&gt;&amp;nbsp;return the icon as HTML, or as a string?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wp_get_icon()&lt;/code&gt;&amp;nbsp;returns the SVG as a string, so you can echo it directly or wrap it in your own HTML. The second&amp;nbsp;&lt;code&gt;$args&lt;/code&gt;&amp;nbsp;array accepts&amp;nbsp;&lt;code&gt;size&lt;/code&gt;,&amp;nbsp;&lt;code&gt;class&lt;/code&gt;,&amp;nbsp;&lt;code&gt;label&lt;/code&gt;&amp;nbsp;(for&amp;nbsp;&lt;code&gt;aria-label&lt;/code&gt;), and&amp;nbsp;&lt;code&gt;title&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What SVG attributes should I remove before registering an icon?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remove hardcoded&amp;nbsp;&lt;code&gt;fill&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;stroke&lt;/code&gt;&amp;nbsp;colors,&amp;nbsp;&lt;code&gt;width&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;height&lt;/code&gt;&amp;nbsp;attributes on the root&amp;nbsp;&lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;&amp;nbsp;(leave the&amp;nbsp;&lt;code&gt;viewBox&lt;/code&gt;), and any&amp;nbsp;&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;&amp;nbsp;blocks or scripts. Keep&amp;nbsp;&lt;code&gt;viewBox&lt;/code&gt;&amp;nbsp;and structural attributes. Use&amp;nbsp;&lt;code&gt;currentColor&lt;/code&gt;&amp;nbsp;for any color reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I register icons from a theme instead of a plugin?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The same&amp;nbsp;&lt;code&gt;wp_register_icon()&lt;/code&gt;&amp;nbsp;call works from a theme's&amp;nbsp;&lt;code&gt;functions.php&lt;/code&gt;&amp;nbsp;using&amp;nbsp;&lt;code&gt;get_stylesheet_directory()&lt;/code&gt;&amp;nbsp;for the file path. The trade-off is that icons registered from a theme disappear when the theme changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if two collections have the same icon name?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Collections namespace icon names, so&amp;nbsp;&lt;code&gt;wpvibes/heart&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;myplugin/heart&lt;/code&gt;&amp;nbsp;coexist without conflict. The full name including the collection prefix is what makes each unique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does WordPress sanitize SVGs on registration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress does not modify the SVG content on registration. The exact string you provide (or file contents you reference) is what gets served. Sanitize SVGs yourself before shipping: remove scripts, external references, and anything unnecessary.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>gutenberg</category>
      <category>webdev</category>
    </item>
    <item>
      <title>PHP-Only Blocks in WordPress 7.0: The Developer's Complete Guide</title>
      <dc:creator>WPVibes</dc:creator>
      <pubDate>Thu, 06 Aug 2026 06:05:10 +0000</pubDate>
      <link>https://dev.to/wpvibes/php-only-blocks-in-wordpress-70-the-developers-complete-guide-5e02</link>
      <guid>https://dev.to/wpvibes/php-only-blocks-in-wordpress-70-the-developers-complete-guide-5e02</guid>
      <description>&lt;p&gt;For the first time in eight years, you can register real Gutenberg blocks using only PHP - no React, no npm, no Webpack, no build step. Just a plugin file,&amp;nbsp;&lt;code&gt;register_block_type()&lt;/code&gt;, and a new&amp;nbsp;&lt;code&gt;autoRegister&lt;/code&gt;&amp;nbsp;flag introduced in WordPress 7.0.&lt;/p&gt;

&lt;p&gt;This guide walks you through building a complete plugin with&amp;nbsp;&lt;strong&gt;two production-ready blocks&lt;/strong&gt;&amp;nbsp;- a Notice block and a Category Display block - from setup to shipping. The complete plugin, including all CSS and helper files, is on GitHub. Everything you need to follow along is in this article; everything you need to ship is in the repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick clarification before we start:&lt;/strong&gt;&amp;nbsp;Server-rendered Gutenberg blocks have existed for years, but until now they always required a JavaScript layer using&amp;nbsp;&lt;code&gt;ServerSideRender&lt;/code&gt;&amp;nbsp;to scaffold the block in the editor - even though the actual rendering happened in PHP. You still needed&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;, an&amp;nbsp;&lt;code&gt;edit.js&lt;/code&gt;&amp;nbsp;file, and a build step. WordPress 7.0's&amp;nbsp;&lt;code&gt;autoRegister =&amp;gt; true&lt;/code&gt;&amp;nbsp;flag removes that JavaScript ceremony entirely.&lt;/p&gt;

&lt;p&gt;We'll cover architecture and comparisons at the end. First, let's build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Is This Guide For?
&lt;/h2&gt;

&lt;p&gt;WordPress developers comfortable with PHP who want to build blocks without a JavaScript build pipeline. Basic knowledge of&amp;nbsp;&lt;code&gt;register_block_type()&lt;/code&gt;&amp;nbsp;helps but isn't required - this guide explains each step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Will You Build?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notice Block&lt;/strong&gt;: Title, description, and four skin variants (Alert, Warning, Success, Info)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Category Display Block&lt;/strong&gt;: Show post categories in Grid, List, or Compact layout, with optional images (via ACF if installed, plugin placeholder if not), post counts, and recent post lists&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WordPress 7.0 or later&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local development environment&lt;/strong&gt;&amp;nbsp;(LocalWP, WordPress Studio, wp-env, or similar)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code editor&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic PHP knowledge&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optional:&lt;/strong&gt;&amp;nbsp;Advanced Custom Fields - the Category Display block supports ACF images if available and falls back to a placeholder otherwise&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Tips:&lt;/strong&gt; Test all changes on a local or staging site first. WordPress 7.0 is recent and PHP-only blocks are a new API - verify everything before shipping to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set Up the Plugin
&lt;/h2&gt;

&lt;p&gt;The plugin contains both blocks, organized cleanly so more blocks can be added later without refactoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 - File structure
&lt;/h3&gt;

&lt;p&gt;Create this folder inside&amp;nbsp;&lt;code&gt;wp-content/plugins/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wpvibes-blocks/
├── wpvibes-blocks.php
├── includes/
│   └── class-blocks-loader.php
├── blocks/
│   ├── class-notice-block.php
│   └── class-category-display-block.php
└── assets/
    ├── notice.css
    ├── category-display.css
    └── images/
        └── category-placeholder.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this structure?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main plugin file loads everything. The loader class registers all blocks in one loop. Each block lives in its own file. Each block's CSS lives in its own file too. Adding a new block later means creating one new class file, adding one line to the loader, and registering one stylesheet. This pattern scales - a plugin with 20 blocks stays maintainable; a single 5,000-line file does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 - The main plugin file
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;wpvibes-blocks.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: WPVibes Blocks
 * Description: PHP-only Gutenberg blocks — Notice and Category Display.
 * Version:     1.0.0
 * Author:      WPVibes
 * Requires at least: 7.0
 * Requires PHP: 7.4
 */&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPVIBES_BLOCKS_VERSION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.0.0'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPVIBES_BLOCKS_PATH'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugin_dir_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPVIBES_BLOCKS_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugin_dir_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_BLOCKS_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'blocks/class-notice-block.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_BLOCKS_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'blocks/class-category-display-block.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_BLOCKS_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'includes/class-blocks-loader.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;WPVibes_Blocks_Loader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main file wires things together. Nothing more.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 - The loader
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;includes/class-blocks-loader.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WPVibes_Blocks_Loader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nv"&gt;$block_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'WPVibes_Notice_Block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'WPVibes_Category_Display_Block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__CLASS__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'register_styles'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__CLASS__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'register_all_blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register_styles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nf"&gt;wp_register_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'wpvibes-notice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;WPVIBES_BLOCKS_URL&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'assets/notice.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="no"&gt;WPVIBES_BLOCKS_VERSION&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;wp_register_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'wpvibes-category-display'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;WPVIBES_BLOCKS_URL&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'assets/category-display.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="no"&gt;WPVIBES_BLOCKS_VERSION&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register_all_blocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$block_classes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$class_name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nv"&gt;$block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;$class_name&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate the plugin from&amp;nbsp;&lt;strong&gt;Plugins → Installed Plugins&lt;/strong&gt;. Nothing visible yet - the blocks come next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build the Notice Block
&lt;/h2&gt;

&lt;p&gt;The Notice block shows a message with a title, description, and one of four skin options (Alert, Warning, Success, Info). It's the simpler of the two blocks - a good starting point for learning the API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4beb7zvi5wz5rogbkph2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4beb7zvi5wz5rogbkph2.webp" alt="All four Notice skins created with php stacked on the frontend" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 - Create the block class
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/class-notice-block.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WPVibes_Notice_Block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nf"&gt;register_block_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'wpvibes/notice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'title'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Notice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'description'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'A notice or callout with 4 skins.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'category'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'icon'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'warning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'style'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-notice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'editor_style'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-notice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'attributes'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'supports'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_supports&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'render_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'render'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Attribute, supports, and render methods below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two keys worth pointing out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;style&lt;/code&gt;&amp;nbsp;&lt;strong&gt;and&lt;/strong&gt;&amp;nbsp;&lt;code&gt;editor_style&lt;/code&gt;&amp;nbsp;- attach the stylesheet registered in the loader (same handle for both). WordPress loads it only when the block is used.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;render_callback&lt;/code&gt;&amp;nbsp;- the PHP function that returns the block's HTML.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.2 - Define the attributes
&lt;/h3&gt;

&lt;p&gt;Add this method to the class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Important notice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="s1"&gt;'description'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Add your notice message here.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="s1"&gt;'skin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'enum'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Alert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Warning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Info'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Info'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Skin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;title&lt;/code&gt;&amp;nbsp;- a string, so WordPress auto-generates a text input in the sidebar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;description&lt;/code&gt;&amp;nbsp;- same, another text input.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;skin&lt;/code&gt;&amp;nbsp;- string with&amp;nbsp;&lt;code&gt;enum&lt;/code&gt;, so WordPress auto-generates a dropdown with exactly those four options.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The&amp;nbsp;&lt;code&gt;label&lt;/code&gt;&amp;nbsp;key controls what the user sees. Without it, WordPress derives a label from the attribute name.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 - Add block supports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_supports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'autoRegister'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'color'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'background'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'spacing'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'padding'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'margin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'border'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'color'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'radius'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'style'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happen here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;autoRegister =&amp;gt; true&lt;/code&gt;&amp;nbsp;- the flag that tells WordPress to register this block from PHP alone, without JavaScript scaffolding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;color&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;spacing&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;border&lt;/code&gt;&amp;nbsp;- turn on native design panels in the sidebar. Users get Color, Spacing, and Border controls automatically.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2.4 - Write the render callback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$skin_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-notice--'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'skin'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_block_wrapper_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-notice '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$skin_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'role'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'alert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;h4 class="wpvibes-notice__title"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/h4&amp;gt;'&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;p class="wpvibes-notice__description"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/p&amp;gt;'&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'&amp;lt;div %1$s&amp;gt;&amp;lt;div class="wpvibes-notice__icon"&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class="wpvibes-notice__content"&amp;gt;%2$s%3$s&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$description&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;get_block_wrapper_attributes()&lt;/code&gt;&amp;nbsp;returns HTML attributes carrying every design choice the user made - color, background, padding, border. Always use it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;esc_html()&lt;/code&gt;&amp;nbsp;on all user-provided content. Standard WordPress escaping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BEM naming&lt;/strong&gt;&amp;nbsp;-&amp;nbsp;&lt;code&gt;wpvibes-notice&lt;/code&gt;&amp;nbsp;(base) plus&amp;nbsp;&lt;code&gt;wpvibes-notice--alert&lt;/code&gt;&amp;nbsp;(variant). Predictable CSS.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.5 - Add the CSS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.wpvibes-notice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-notice--alert&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fee2e2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#991b1b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#dc2626&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-notice--warning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fef3c7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#92400e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f59e0b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-notice--success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d1fae5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#065f46&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#10b981&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-notice--info&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#dbeafe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1e3a8a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b82f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The full CSS (icon circles, title and description typography, hover states) is in the plugin repository at&amp;nbsp;&lt;a rel="noopener noreferrer nofollow" href="https://github.com/webtechhardik/wpvibes-blocks/blob/main/assets/notice.css"&gt;assets/notice.css&lt;/a&gt;. Copy the complete file into the plugin's&amp;nbsp;&lt;code&gt;assets/&lt;/code&gt;&amp;nbsp;folder before testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6 - See it working
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new page or post&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;+&lt;/strong&gt;&amp;nbsp;in the block inserter and search for&amp;nbsp;&lt;strong&gt;Notice&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Try each skin from the sidebar dropdown&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust the native Color and Padding controls&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbzc0ltldd7rdml567ui.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbzc0ltldd7rdml567ui.webp" alt="Notice block in the editor with sidebar controls visible" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first PHP-only block is done. No JavaScript. No build step. No&amp;nbsp;&lt;code&gt;block.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The complete file for reference is at&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://github.com/webtechhardik/wpvibes-blocks/blob/main/blocks/class-notice-block.php"&gt;blocks/class-notice-block.php&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build the Category Display Block
&lt;/h2&gt;

&lt;p&gt;The Category Display block shows post categories in Grid, List, or Compact layout. For each category: name, post count, an image (from ACF if installed, plugin placeholder otherwise), and a list of recent posts.&lt;/p&gt;

&lt;p&gt;This block covers&amp;nbsp;&lt;strong&gt;all four supported attribute types&lt;/strong&gt;&amp;nbsp;- string, integer, boolean, and enum - in one useful example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rtzf8zxre0or0r0m1fi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rtzf8zxre0or0r0m1fi.webp" alt="Category Display block in Grid layout on the frontend" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 - Create the block class
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;blocks/class-category-display-block.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WPVibes_Category_Display_Block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nf"&gt;register_block_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'wpvibes/category-display'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'title'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Category Display'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'description'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Display categories with post count, image, and recent posts.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'category'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'widgets'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'icon'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'style'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-category-display'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'editor_style'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-category-display'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'attributes'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'supports'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_supports&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'render_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'render'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Attributes, supports, and render methods below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 - Define the attributes (all 4 types)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="c1"&gt;// ENUM — dropdown&lt;/span&gt;
            &lt;span class="s1"&gt;'layout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'enum'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Grid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'List'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Compact'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Grid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Layout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// INTEGER — number input with min/max&lt;/span&gt;
            &lt;span class="s1"&gt;'columns'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'integer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'minimum'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'maximum'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Columns (Grid only)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// INTEGER&lt;/span&gt;
            &lt;span class="s1"&gt;'postCount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'integer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'minimum'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'maximum'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Posts per Category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// STRING — text input (ACF field key)&lt;/span&gt;
            &lt;span class="s1"&gt;'acfFieldKey'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'term_featured_image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ACF Image Field Key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// ENUM — dropdown&lt;/span&gt;
            &lt;span class="s1"&gt;'orderBy'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'enum'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Post Count'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Slug'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Order By'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// BOOLEAN — toggle&lt;/span&gt;
            &lt;span class="s1"&gt;'showImage'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'boolean'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Show Image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// BOOLEAN&lt;/span&gt;
            &lt;span class="s1"&gt;'showPostCount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'boolean'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Show Post Count'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// BOOLEAN&lt;/span&gt;
            &lt;span class="s1"&gt;'showRecentPosts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'boolean'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Show Recent Posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// BOOLEAN&lt;/span&gt;
            &lt;span class="s1"&gt;'hideEmpty'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'boolean'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'label'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Hide Empty Categories'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What each attribute type teaches:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;layout&lt;/code&gt;&amp;nbsp;&lt;strong&gt;and&lt;/strong&gt;&amp;nbsp;&lt;code&gt;orderBy&lt;/code&gt;&amp;nbsp;- string with&amp;nbsp;&lt;code&gt;enum&lt;/code&gt;. WordPress auto-generates dropdowns with exactly those choices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;columns&lt;/code&gt;&amp;nbsp;&lt;strong&gt;and&lt;/strong&gt;&amp;nbsp;&lt;code&gt;postCount&lt;/code&gt;&amp;nbsp;- integers with&amp;nbsp;&lt;code&gt;minimum&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;maximum&lt;/code&gt;. WordPress auto-generates number inputs that enforce the ranges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;acfFieldKey&lt;/code&gt;&amp;nbsp;- a plain string. Text input where the user types the ACF field name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;showImage&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;showPostCount&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;showRecentPosts&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt;&amp;nbsp;&lt;code&gt;hideEmpty&lt;/code&gt;&amp;nbsp;- booleans. Each becomes a toggle switch in the sidebar.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's every attribute type WordPress 7.0 auto-generates a control for.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 - Block supports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_supports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'autoRegister'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'align'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wide'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'full'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'color'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'background'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'spacing'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'padding'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'margin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'border'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'color'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'radius'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'style'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same as the Notice block, with&amp;nbsp;&lt;code&gt;align&lt;/code&gt;&amp;nbsp;added so users can center or full-width the block.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 - Write the render callback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$order_by_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'Name'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'Post Count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'count'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'Slug'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'hide_empty'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'hideEmpty'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'orderby'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order_by_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'orderBy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'order'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ASC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$categories&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;p&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'No categories to display.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$layout&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'layout'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$layout_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-category-display--'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$columns_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'grid'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$layout&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;' wpvibes-category-display--cols-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'columns'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_block_wrapper_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-category-display '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$layout_class&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$columns_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$items_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$categories&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$items_html&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render_category_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'&amp;lt;div %1$s&amp;gt;%2$s&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$items_html&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the render callback does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Queries categories&lt;/strong&gt;&amp;nbsp;using the&amp;nbsp;&lt;code&gt;hideEmpty&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;orderBy&lt;/code&gt;&amp;nbsp;attributes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handles empty state&lt;/strong&gt;&amp;nbsp;with a friendly message&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Builds layout classes&lt;/strong&gt;&amp;nbsp;-&amp;nbsp;&lt;code&gt;wpvibes-category-display--grid&lt;/code&gt;&amp;nbsp;plus&amp;nbsp;&lt;code&gt;wpvibes-category-display--cols-3&lt;/code&gt;&amp;nbsp;for column-based grids&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loops through categories&lt;/strong&gt;&amp;nbsp;and renders each via a helper method&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.5 - Render a single category
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render_category_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$image_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'showImage'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$image_url&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_category_image_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'acfFieldKey'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$image_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'&amp;lt;div class="wpvibes-category-display__image"&amp;gt;&amp;lt;img src="%s" alt="%s" loading="lazy"&amp;gt;&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$image_url&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$count_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'showPostCount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$count_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'&amp;lt;span class="wpvibes-category-display__count"&amp;gt;%s&amp;lt;/span&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'%d post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%d posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpvibes-blocks'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$posts_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'showRecentPosts'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="nv"&gt;$recent_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;'category'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;term_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'posts_per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;absint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'postCount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="s1"&gt;'orderby'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'order'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DESC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$recent_posts&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

                &lt;span class="nv"&gt;$posts_html&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;ul class="wpvibes-category-display__posts"&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$recent_posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$posts_html&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s1"&gt;'&amp;lt;li&amp;gt;&amp;lt;a href="%s"&amp;gt;%s&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;get_permalink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;get_the_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="nv"&gt;$posts_html&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/ul&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'&amp;lt;div class="wpvibes-category-display__item"&amp;gt;%1$s&amp;lt;div class="wpvibes-category-display__content"&amp;gt;&amp;lt;h3 class="wpvibes-category-display__name"&amp;gt;&amp;lt;a href="%2$s"&amp;gt;%3$s&amp;lt;/a&amp;gt;&amp;lt;/h3&amp;gt;%4$s%5$s&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$image_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;get_category_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$count_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$posts_html&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each part (image, count, recent posts) is built conditionally based on the toggles. If a toggle is off, that part's HTML variable stays empty and contributes nothing to the final output.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.6 - The ACF image with fallback
&lt;/h3&gt;

&lt;p&gt;The Category Display block uses an ACF image if the site has ACF installed and the field is set. If not, it falls back to a placeholder from the plugin's own assets folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_category_image_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$acf_field_key&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if ACF is active AND a field key is provided.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'get_field'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_field_key&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="c1"&gt;// ACF term meta uses 'category_' . term_id as the object ID.&lt;/span&gt;
            &lt;span class="nv"&gt;$acf_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_field_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'category_'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;term_id&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

                &lt;span class="c1"&gt;// ACF image field can return an array, an ID, or a URL string&lt;/span&gt;
                &lt;span class="c1"&gt;// depending on the "Return Format" setting.&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;is_numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_get_attachment_image_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$acf_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'medium'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Fallback: plugin's placeholder image.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;WPVIBES_BLOCKS_URL&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'assets/images/category-placeholder.png'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things this pattern teaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;function_exists()&lt;/code&gt;&amp;nbsp;&lt;strong&gt;before using an external plugin's function&lt;/strong&gt;&amp;nbsp;- always. Never assume ACF (or any plugin) is active.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The ACF term meta prefix&lt;/strong&gt;&amp;nbsp;- ACF stores term meta under&amp;nbsp;&lt;code&gt;category_&lt;/code&gt;&amp;nbsp;+&amp;nbsp;&lt;code&gt;term_id&lt;/code&gt;. This trips up many developers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling ACF's different return formats&lt;/strong&gt;&amp;nbsp;- ACF image fields can return an array, an ID, or a URL, depending on the "Return Format" setting. The code should handle at least the two common ones.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3.7 - Add the CSS
&lt;/h3&gt;

&lt;p&gt;Create&amp;nbsp;&lt;code&gt;assets/category-display.css&lt;/code&gt;&amp;nbsp;with the base structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.wpvibes-category-display&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-category-display--grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-category-display--grid.wpvibes-category-display--cols-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-category-display--grid.wpvibes-category-display--cols-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wpvibes-category-display--list&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.wpvibes-category-display--compact&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Important:&lt;/strong&gt; The full CSS (card styling, hover states, image aspect ratios, list-layout flex rules, compact-layout borders, and mobile responsiveness) is in the plugin repository at&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://github.com/webtechhardik/wpvibes-blocks/blob/main/assets/category-display.css"&gt;assets/category-display.css&lt;/a&gt;. Copy the complete file into the plugin's&amp;nbsp;&lt;code&gt;assets/&lt;/code&gt;&amp;nbsp;folder before testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.8 - Add a placeholder image
&lt;/h3&gt;

&lt;p&gt;Place any image at&amp;nbsp;&lt;code&gt;assets/images/category-placeholder.png&lt;/code&gt;&amp;nbsp;- an 800×450 image works well. The plugin repository includes one; a custom branded placeholder also works.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.9 - See it working
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Assign a few posts to different categories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insert the&amp;nbsp;&lt;strong&gt;Category Display&lt;/strong&gt;&amp;nbsp;block&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the sidebar, try:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Each layout (Grid, List, Compact)

*   Different column counts (2, 3, 4) while in Grid layout

*   Toggling Show Image, Show Post Count, Show Recent Posts

*   Changing Posts per Category
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzn4dchno662si0pvs9tq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzn4dchno662si0pvs9tq.webp" alt="Category Display block Settings to Changes Layout or Hide/Show Fields" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two production-ready PHP-only blocks - one simple, one covering the full API - done.&lt;/p&gt;

&lt;p&gt;💡&lt;br&gt;
&lt;strong&gt;Notes:&lt;/strong&gt; The complete file for reference is at&amp;nbsp;&lt;a rel="noreferrer noopener" href="https://github.com/webtechhardik/wpvibes-blocks/blob/main/blocks/class-category-display-block.php"&gt;blocks/class-category-display-block.php&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Tips
&lt;/h2&gt;

&lt;p&gt;When something breaks during development, enable WordPress debug logging in&amp;nbsp;&lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP_DEBUG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP_DEBUG_LOG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP_DEBUG_DISPLAY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check&amp;nbsp;&lt;code&gt;wp-content/debug.log&lt;/code&gt;&amp;nbsp;after triggering the issue. Nine times out of ten, a PHP error during registration is the culprit — and it will be in the log with a line number.&lt;/p&gt;

&lt;p&gt;For attribute-level debugging (checking what's being passed to the render callback), add temporary&amp;nbsp;&lt;code&gt;error_log()&lt;/code&gt;&amp;nbsp;calls at the top of the render function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Notice block attributes: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove these before shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Full Plugin
&lt;/h2&gt;

&lt;p&gt;The complete plugin - with all CSS, placeholder image, and README - is on &lt;a href="https://github.com/webtechhardik/wpvibes-blocks" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How PHP-Only Blocks Work Under the Hood
&lt;/h2&gt;

&lt;p&gt;Now that the plugin builds, here's what actually happens when a PHP-only block is registered and rendered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before WordPress 7.0
&lt;/h3&gt;

&lt;p&gt;Building a server-rendered block required these files, at minimum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-plugin/
├── src/block/
│   ├── block.json
│   ├── edit.js
│   └── index.js
├── package.json
└── my-plugin.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The&amp;nbsp;&lt;code&gt;edit.js&lt;/code&gt;&amp;nbsp;typically looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ServerSideRender&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@wordpress/editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ServerSideRender&lt;/span&gt;
            &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A JavaScript component whose only job was to say "please render this on the server." Then&amp;nbsp;&lt;code&gt;npm install&lt;/code&gt;, run a build, and hope everything worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  With WordPress 7.0
&lt;/h3&gt;

&lt;p&gt;WordPress 7.0 adds one flag:&amp;nbsp;&lt;code&gt;autoRegister =&amp;gt; true&lt;/code&gt;. When set, WordPress:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Takes the block registration from PHP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adds it to a JavaScript global in the editor settings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The editor picks it up and uses&amp;nbsp;&lt;code&gt;ServerSideRender&lt;/code&gt;&amp;nbsp;internally for the preview&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When a user changes an attribute, the editor calls the REST API, which runs the PHP&amp;nbsp;&lt;code&gt;render_callback&lt;/code&gt;, and swaps in the new HTML&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero JavaScript written. WordPress handles the ceremony.&lt;/p&gt;

&lt;h3&gt;
  
  
  The runtime flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User changes an attribute in the sidebar
        ↓
Editor sends REST API request
        ↓
WordPress runs render_callback in PHP
        ↓
render_callback returns HTML string
        ↓
Editor swaps HTML into the block preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02zqx3jdfyvq2v332nwb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02zqx3jdfyvq2v332nwb.png" alt="WordPress 7.0 Benefits to Create PHP Block" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is why every attribute change causes a small delay - there's always a server round-trip. That's the deliberate trade-off: no client-side complexity, but no live reactivity either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-generated inspector controls&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress inspects the&amp;nbsp;&lt;code&gt;attributes&lt;/code&gt;&amp;nbsp;array and generates sidebar controls automatically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute definition&lt;/th&gt;
&lt;th&gt;Generated control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type: 'string'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;type: 'string'&lt;/code&gt;&amp;nbsp;+&amp;nbsp;&lt;code&gt;enum: [...]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Dropdown (SelectControl)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;type: 'integer'&lt;/code&gt;&amp;nbsp;with&amp;nbsp;&lt;code&gt;minimum&lt;/code&gt;&amp;nbsp;/&amp;nbsp;&lt;code&gt;maximum&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Number input with enforced range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;type: 'boolean'&lt;/td&gt;
&lt;td&gt;Toggle switch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Controls are&amp;nbsp;&lt;strong&gt;not&lt;/strong&gt;&amp;nbsp;generated for&amp;nbsp;&lt;code&gt;object&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;array&lt;/code&gt;&amp;nbsp;types, or for attribute names that conflict with reserved block-supports names like&amp;nbsp;&lt;code&gt;style&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Use PHP-Only Blocks?
&lt;/h2&gt;

&lt;p&gt;An honest decision table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use PHP-only blocks when&lt;/th&gt;
&lt;th&gt;Use JavaScript-registered blocks when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Block is display-only (notice, category grid, author box)&lt;/td&gt;
&lt;td&gt;Block needs InnerBlocks (nested blocks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content comes from server data (users, posts, terms, APIs)&lt;/td&gt;
&lt;td&gt;Inline rich-text editing is required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replacing a legacy shortcode&lt;/td&gt;
&lt;td&gt;Editor experience needs live client-side reactivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avoiding a JavaScript build pipeline&lt;/td&gt;
&lt;td&gt;Interactivity API is needed for state management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple enough that a server round-trip on each change is acceptable&lt;/td&gt;
&lt;td&gt;Real-time visual manipulation (drag handles, instant previews) is essential&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both approaches are first-class WordPress citizens. PHP-only blocks aren't a downgrade -they're a shortcut for a specific class of blocks that never needed JavaScript in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Limitations of PHP-Only Blocks?
&lt;/h2&gt;

&lt;p&gt;Deliberate scope decisions, not bugs. Knowing them helps pick the right approach.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No InnerBlocks / nested blocks&lt;/td&gt;
&lt;td&gt;Can't build columns, accordions, sections&lt;/td&gt;
&lt;td&gt;No timeline announced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only 4 attribute types (string, integer, boolean, enum)&lt;/td&gt;
&lt;td&gt;No media picker, rich text, date picker&lt;/td&gt;
&lt;td&gt;DataForm being extended to PHP-only blocks in 7.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Bindings limited in 7.0&lt;/td&gt;
&lt;td&gt;Custom fields, post meta bindings need workarounds&lt;/td&gt;
&lt;td&gt;ACF and Meta Box become native binding sources in 7.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server round-trip on every attribute change&lt;/td&gt;
&lt;td&gt;Preview feels less responsive than JS blocks&lt;/td&gt;
&lt;td&gt;By design - the trade-off for zero JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom&amp;nbsp;&lt;code&gt;style&lt;/code&gt;&amp;nbsp;attribute conflicts with supports&lt;/td&gt;
&lt;td&gt;Must rename to&amp;nbsp;&lt;code&gt;skin&lt;/code&gt;,&amp;nbsp;&lt;code&gt;variant&lt;/code&gt;, etc.&lt;/td&gt;
&lt;td&gt;Reserved namespace, not a bug&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can PHP-only blocks contain InnerBlocks?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. PHP-only blocks cannot contain nested blocks. For layouts like columns, accordions, or sections that wrap other blocks, use the traditional JavaScript block registration path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do PHP-only blocks work with the Interactivity API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not directly. The Interactivity API is designed for client-side state and reactivity, which PHP-only blocks don't provide. For blocks needing state (toggles, tabs, live filtering), JavaScript block registration is the correct choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which attribute types get auto-generated controls?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four types: string (text input), string with enum (dropdown), integer with minimum/maximum (number input), and boolean (toggle). Types like object and array do not generate controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is my custom&amp;nbsp;&lt;/strong&gt;&lt;code&gt;style&lt;/code&gt;&lt;strong&gt;&amp;nbsp;attribute causing errors?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress reserves the&amp;nbsp;&lt;code&gt;style&lt;/code&gt;&amp;nbsp;attribute name when block supports for color, spacing, or typography are enabled. Rename any custom attribute called&amp;nbsp;&lt;code&gt;style&lt;/code&gt;&amp;nbsp;to something like&amp;nbsp;&lt;code&gt;skin&lt;/code&gt;,&amp;nbsp;&lt;code&gt;variant&lt;/code&gt;, or&amp;nbsp;&lt;code&gt;theme&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the editor preview update in real time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Each attribute change triggers a REST API call to the PHP&amp;nbsp;&lt;code&gt;render_callback&lt;/code&gt;, then the editor swaps in the returned HTML. There's a small delay on every change - this is the trade-off for not writing JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use&amp;nbsp;&lt;/strong&gt;&lt;code&gt;wp_enqueue_block_style()&lt;/code&gt;&lt;strong&gt;&amp;nbsp;for PHP-only block CSS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not reliably. &lt;code&gt;wp_enqueue_block_style() &lt;/code&gt;works for JavaScript-registered blocks but doesn't always fire for PHP-only blocks in the editor. Use &lt;code&gt;wp_register_style() &lt;/code&gt;with the &lt;code&gt;style&lt;/code&gt; and &lt;code&gt;editor_style &lt;/code&gt;keys inside &lt;code&gt;register_block_type()&lt;/code&gt; instead - the pattern shown in this guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens in the editor if the render callback throws an error?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The block shows "Error loading block" in the editor preview. Check&amp;nbsp;&lt;code&gt;wp-content/debug.log&lt;/code&gt;&amp;nbsp;for the PHP error - the line number points directly to the issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do PHP-only blocks support WordPress block themes and Full Site Editing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. PHP-only blocks work in block themes, templates, template parts, and the Site Editor. WordPress 7.1 enforces iframe mode for block themes - the examples in this guide use current APIs, so no changes are needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building the WPVibes Blocks plugin has covered:&lt;/p&gt;

&lt;p&gt;autoRegister =&amp;gt; true&amp;nbsp;is the entire "new API." Everything else is standard&amp;nbsp;register_block_type().&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;style&amp;nbsp;and&amp;nbsp;editor_style&amp;nbsp;keys&amp;nbsp;with&amp;nbsp;wp_register_style()&amp;nbsp;are the right way to attach CSS to a PHP-only block — cleaner and correctly loading in both editor and frontend.&lt;/p&gt;

&lt;p&gt;get_block_wrapper_attributes()&amp;nbsp;carries all block-supports styles into the render output. Always use it.&lt;/p&gt;

&lt;p&gt;Auto-generated sidebar controls&amp;nbsp;work well within their limits - string, integer, boolean, enum.&lt;/p&gt;

&lt;p&gt;Server round-trip on every attribute change&amp;nbsp;is the trade-off - keep render callbacks fast.&lt;/p&gt;

&lt;p&gt;One file per block&amp;nbsp;scales. Adding a new block later is one new file, one line in the loader.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>gutenberg</category>
      <category>development</category>
    </item>
  </channel>
</rss>
