<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged gutenberg</title>
<link>https://coderlegion.com/tag/gutenberg</link>
<description>Powered by Question2Answer</description>
<item>
<title>Quovex Blocks: A Post Grid, Featured Posts and Announcement Bar Built on the Interactivity API</title>
<link>https://coderlegion.com/30258/quovex-blocks-post-grid-featured-posts-and-announcement-bar-built-the-interactivity-api</link>
<description>&lt;p&gt;Most WordPress sites end up needing the same few things: a grid of recent posts, a featured section on the homepage, and a banner for announcements. The usual answer is a page builder or a stack of separate plugins, each bringing its own UI and its own frontend scripts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quovex Blocks&lt;/strong&gt; is a free WordPress plugin that does all three as native Gutenberg blocks, with the frontend interactivity running on the WordPress Interactivity API instead of jQuery.&lt;/p&gt;
&lt;p&gt;▶️ Watch the 2-minute demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/ADyo0WuhiR0&quot;&gt;https://youtu.be/ADyo0WuhiR0&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-three-blocks&quot;&gt;The three blocks&lt;/h3&gt;
&lt;h4 id=&quot;query-grid&quot;&gt;Query Grid&lt;/h4&gt;
&lt;p&gt;A filterable, paginated grid that works with any public post type: Posts, Pages, WooCommerce products or your own custom post types.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Grid, List, Masonry and Carousel layouts&lt;/li&gt;
&lt;li&gt;Search bar and taxonomy filter pills (multi-select within a taxonomy), optionally in a left or right sidebar&lt;/li&gt;
&lt;li&gt;Numbered pagination or a &quot;Load more&quot; button, plus a &quot;Show all posts&quot; toggle&lt;/li&gt;
&lt;li&gt;Responsive column counts, plus color and typography controls that use your theme's fonts&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;content-showcase&quot;&gt;Content Showcase&lt;/h4&gt;
&lt;p&gt;A magazine-style section for up to three posts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Magazine, Split, Overlay and Two-Thirds/One-Third layouts&lt;/li&gt;
&lt;li&gt;Automatic mode (latest posts, optionally filtered by taxonomy) or manual mode (hand-pick a post for each slot)&lt;/li&gt;
&lt;li&gt;Image hotspots: pins you place on a post's image, each opening a popover with its own title, text and link&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;notification-banner&quot;&gt;Notification Banner&lt;/h4&gt;
&lt;p&gt;A dismissible announcement bar or inline alert.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Announcement, Info, Success and Warning presets, with five style variations&lt;/li&gt;
&lt;li&gt;Emoji, dashicon or SVG icon picker, and an optional call-to-action button&lt;/li&gt;
&lt;li&gt;Dismissal is remembered in the visitor's browser, so a closed banner stays hidden on their next visit&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-it-s-built&quot;&gt;How it's built&lt;/h3&gt;
&lt;h4 id=&quot;dynamic-blocks-the-interactivity-api&quot;&gt;Dynamic blocks + the Interactivity API&lt;/h4&gt;
&lt;p&gt;Query Grid and Content Showcase are dynamic blocks rendered in PHP through &lt;code&gt;render.php&lt;/code&gt;, so the first paint is plain server-rendered HTML. Frontend behavior (search, filters, pagination, the carousel and hotspots) is attached with the Interactivity API through a &lt;code&gt;viewScriptModule&lt;/code&gt;, so it ships as a script module and no jQuery or other framework is loaded.&lt;/p&gt;
&lt;p&gt;Here's the relevant part of Query Grid's &lt;code&gt;block.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;apiVersion&quot;: 3,
  &quot;name&quot;: &quot;quovex-blocks/query-grid&quot;,
  &quot;render&quot;: &quot;file:./render.php&quot;,
  &quot;viewScriptModule&quot;: &quot;file:./view.js&quot;,
  &quot;supports&quot;: {
    &quot;interactivity&quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;pagination-that-search-engines-can-follow&quot;&gt;Pagination that search engines can follow&lt;/h4&gt;
&lt;p&gt;Clicking a page number swaps in the new results without a full page reload, but the address bar updates to a real URL, and opening that URL directly server-renders the correct page. So visitors get instant navigation, and crawlers still get normal, followable links. The pagination URL segment is configurable (for example &lt;code&gt;/your-site/quovex-page/2/&lt;/code&gt;) as a site-wide setting.&lt;/p&gt;
&lt;h4 id=&quot;layouts-as-style-variations&quot;&gt;Layouts as style variations&lt;/h4&gt;
&lt;p&gt;Every layout is a native block style variation. Switching from Grid to Masonry, or from Magazine to Overlay, is one click in the editor's Styles panel, with no custom layout picker to learn.&lt;/p&gt;
&lt;h4 id=&quot;a-cache-that-invalidates-itself&quot;&gt;A cache that invalidates itself&lt;/h4&gt;
&lt;p&gt;Query results are cached and cleared automatically the moment a post, category or tag changes, so repeat visits stay fast without serving stale content. Nothing is loaded on pages that don't use the blocks.&lt;/p&gt;
&lt;h4 id=&quot;clean-uninstall&quot;&gt;Clean uninstall&lt;/h4&gt;
&lt;p&gt;Deactivating leaves everything in place in case you come back. Deleting the plugin runs &lt;code&gt;uninstall.php&lt;/code&gt;, which removes every option and cached value the plugin created.&lt;/p&gt;
&lt;h4 id=&quot;tooling&quot;&gt;Tooling&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Built with &lt;code&gt;@wordpress/scripts&lt;/code&gt; using &lt;code&gt;--experimental-modules&lt;/code&gt; and &lt;code&gt;--blocks-manifest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;PHP classes autoloaded via PSR-4, with a built-in fallback loader so the shipped plugin needs no &lt;code&gt;vendor/&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;PHPUnit tests, PHPCS and ESLint, with Husky pre-commit hooks&lt;/li&gt;
&lt;li&gt;GitHub Actions for CI, and a workflow that deploys new tags to WordPress.org&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;The plugin is free on WordPress.org. In your dashboard, go to &lt;strong&gt;Plugins → Add New&lt;/strong&gt; and search for &quot;Quovex Blocks&quot;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plugin: &lt;a rel=&quot;nofollow&quot; href=&quot;https://wordpress.org/plugins/quovex-blocks/&quot;&gt;https://wordpress.org/plugins/quovex-blocks/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akshat009/quovex-blocks&quot;&gt;https://github.com/akshat009/quovex-blocks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feedback and issues are welcome on GitHub, and a review on WordPress.org helps other people find it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I'm Akshat Saxena, a Senior WordPress Developer with 8+ years in WordPress, working across WooCommerce, Gutenberg/FSE and custom plugins. If you're looking for someone to build or extend a WordPress product, feel free to reach out on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/developer-akshat-saxena&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30258/quovex-blocks-post-grid-featured-posts-and-announcement-bar-built-the-interactivity-api</guid>
<pubDate>Thu, 08 Oct 2026 10:37:36 +0000</pubDate>
</item>
<item>
<title>A Gutenberg block that uses the Interactivity API — with no render callback and no framework on the front end</title>
<link>https://coderlegion.com/27788/gutenberg-block-that-uses-the-interactivity-with-render-callback-framework-the-front-end</link>
<description>&lt;p&gt;A YouTube embed looks innocent enough. &lt;/p&gt;
&lt;p&gt;You drop an &lt;code&gt;iframe&lt;/code&gt; into a page, give it a &lt;code&gt;src&lt;/code&gt;, and you're done, right? &lt;/p&gt;
&lt;p&gt;Except you're not. &lt;/p&gt;
&lt;p&gt;Google's Lighthouse documentation puts a full embedded YouTube player at around &lt;strong&gt;540 KB&lt;/strong&gt;. On a live page, it's closer to &lt;strong&gt;1.3 MB&lt;/strong&gt;. And the cost doesn't get amortized when you add another embed — two players can mean roughly twice the payload because the resources aren't shared.&lt;/p&gt;
&lt;p&gt;HTTP Archive data referenced by web.dev puts another horrifying metric on it: the median YouTube embed occupies the browser's main thread for &lt;strong&gt;more than 1.7 seconds&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;And here's the part that bothered me most: &lt;strong&gt;All of that happens even if nobody presses Play.&lt;/strong&gt; &lt;/p&gt;
&lt;h3 id=&quot;the-solution-the-facade-pattern&quot;&gt;The Solution: The Facade Pattern&lt;/h3&gt;
&lt;p&gt;The fix isn't new. It's the &lt;strong&gt;facade pattern&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Show a poster.&lt;/li&gt;
&lt;li&gt;Show a play button.&lt;/li&gt;
&lt;li&gt;Don't load the actual player.&lt;/li&gt;
&lt;li&gt;Only load it when the visitor clicks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tools like &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/paulirish/lite-youtube-embed&quot;&gt;&lt;code&gt;lite-youtube-embed&lt;/code&gt;&lt;/a&gt; have been doing this for years. &lt;/p&gt;
&lt;p&gt;I wanted to bring this exact same concept into a WordPress Gutenberg block—but not just for YouTube, and &lt;em&gt;definitely&lt;/em&gt; not by shipping an entire JavaScript framework to the browser just to replace a JPEG with an iframe.&lt;/p&gt;
&lt;p&gt;That experiment became the &lt;a rel=&quot;nofollow&quot; href=&quot;https://wordpress.org/plugins/story-video-block/&quot;&gt;Story Video Block&lt;/a&gt; (now live on the WP repo, source on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akshat009/story-video-block&quot;&gt;GitHub&lt;/a&gt;). &lt;/p&gt;
&lt;p&gt;Here are the most interesting architectural decisions and technical gotchas I ran into while building it. &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-interactivity-dilemma&quot;&gt;The Interactivity Dilemma&lt;/h3&gt;
&lt;p&gt;The actual interaction needed for a facade is tiny. &lt;em&gt;Click. Swap in the embed. Play the video.&lt;/em&gt; That's basically it. &lt;/p&gt;
&lt;p&gt;But traditional block solutions can be surprisingly expensive for such a small interaction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You could enqueue a classic &lt;code&gt;view.js&lt;/code&gt; and manually handle event delegation and DOM manipulation.&lt;/li&gt;
&lt;li&gt;You could ship React to the front end (some block plugins actually do this). You're essentially sending a UI framework to someone's phone so a poster image can turn into an iframe.&lt;/li&gt;
&lt;li&gt;You could use jQuery (please don't).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;enter-wordpress-s-interactivity-api-&quot;&gt;Enter WordPress's Interactivity API ⚡&lt;/h4&gt;
&lt;p&gt;Since WordPress 6.5, we have the &lt;strong&gt;Interactivity API&lt;/strong&gt;. It gives blocks a declarative way to add client-side behavior. More importantly, the runtime is shared by all blocks on the page. &lt;/p&gt;
&lt;p&gt;The opt-in is incredibly simple in your &lt;code&gt;block.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;editorScript&quot;: &quot;file:./index.js&quot;,
&quot;style&quot;: &quot;file:./style-index.css&quot;,
&quot;viewScriptModule&quot;: &quot;file:./view.js&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice one detail: &lt;strong&gt;&lt;code&gt;viewScriptModule&lt;/code&gt;, not &lt;code&gt;viewScript&lt;/code&gt;.&lt;/strong&gt; &lt;br&gt;
That registers the file through the Script Modules API and allows you to natively import the shared WP runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { store } from '@wordpress/interactivity';
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;revelation-you-don-t-need-a-dynamic-block&quot;&gt;Revelation: You Don't Need a Dynamic Block&lt;/h3&gt;
&lt;p&gt;This was my biggest misconception. Because almost every Interactivity API tutorial uses a dynamic block (&lt;code&gt;render.php&lt;/code&gt;), I assumed they were mandatory. They aren't.&lt;/p&gt;
&lt;p&gt;The directives are just HTML attributes. A static block's &lt;code&gt;save()&lt;/code&gt; function can output them, and the frontend runtime will hydrate whatever it finds. &lt;/p&gt;
&lt;p&gt;Here's how my static &lt;code&gt;save()&lt;/code&gt; looks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const blockProps = useBlockProps.save( {
	className: clsx( {
		[ `has-media-${ videoPosition }` ]: videoPosition,
		[ `card-style-${ cardStyle }` ]: cardStyle,
	} ),
	style: {
		'--story-video-block-bg': backgroundColor || undefined,
	},
	...( ! isFile
		? {
				'data-wp-interactive': 'create-block/story-video-block',
				'data-wp-context': JSON.stringify( {
					isPlaying: false,
					embedUrl,
					videoSrc: '',
				} ),
		  }
		: {} ),
} );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;JSON.stringify()&lt;/code&gt; is the whole server-state story for this block! The markup lives in &lt;code&gt;post_content&lt;/code&gt;. There's no PHP render callback running on every request, yet each block instance gets its own scoped, reactive state. &lt;/p&gt;
&lt;h3 id=&quot;the-performance-trick-the-iframe-with-no-src&quot;&gt;The Performance Trick: The &lt;code&gt;iframe&lt;/code&gt; with no &lt;code&gt;src&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;You cannot render an iframe and just CSS hide it. If it has a &lt;code&gt;src&lt;/code&gt;, the browser &lt;strong&gt;will&lt;/strong&gt; load the player. Hidden doesn't mean unloaded. &lt;/p&gt;
&lt;p&gt;So, the iframe is deliberately rendered without a &lt;code&gt;src&lt;/code&gt;. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;div
	className=&quot;story-video-block__video&quot;
	hidden
	data-wp-bind--hidden=&quot;state.isNotPlaying&quot;
&amp;gt;
	&amp;lt;iframe
		title={ heading || __( 'Video', 'story-video-block' ) }
		data-wp-bind--src=&quot;context.videoSrc&quot;
		allow=&quot;autoplay; fullscreen; picture-in-picture&quot;
		allowFullScreen
	/&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initially, &lt;code&gt;context.videoSrc === ''&lt;/code&gt;. Because WP's &lt;code&gt;data-wp-bind--*&lt;/code&gt; removes an attribute when its value is falsy, there is no &lt;code&gt;src&lt;/code&gt; attribute at all. No request, no player.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bonus UX win:&lt;/strong&gt; Because the source is assigned inside the click handler, the browser treats playback as user-initiated. That means &lt;code&gt;autoplay=1&lt;/code&gt; actually works. One click, video starts. No double-clicking required.&lt;/p&gt;
&lt;h3 id=&quot;progressive-enhancement-the-facade-is-a-link&quot;&gt;Progressive Enhancement: The facade is a link&lt;/h3&gt;
&lt;p&gt;I didn't make the facade a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;. JavaScript shouldn't be the only way to reach the video.&lt;/p&gt;
&lt;p&gt;The facade is a real &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag pointing to the original video:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;a
	href={ videoUrl }
	target=&quot;_blank&quot;
	rel=&quot;noopener noreferrer&quot;
	className=&quot;story-video-block__facade&quot;
	data-wp-on--click=&quot;actions.play&quot;
	data-wp-bind--hidden=&quot;state.isPlaying&quot;
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our Interactivity store then enhances it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { store, getContext } from '@wordpress/interactivity';

store( 'create-block/story-video-block', {
	state: {
		get isPlaying() { return getContext().isPlaying; },
		get isNotPlaying() { return ! getContext().isPlaying; },
	},
	actions: {
		play( event ) {
			const context = getContext();
			if ( context.isPlaying ) return;

			// If no direct-embed URL (like TikTok), let the original &amp;lt;a&amp;gt; link work!
			if ( ! context.embedUrl ) return;

			event.preventDefault();
			context.videoSrc = context.embedUrl;
			context.isPlaying = true;
		},
	},
} );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us graceful degradation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JS + Supported provider (YouTube/Vimeo):&lt;/strong&gt; Plays inline seamlessly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JS + No direct embed (TikTok):&lt;/strong&gt; Acts as a normal link.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No JS enabled:&lt;/strong&gt; Acts as a normal link.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;a-css-gotcha-that-cost-me-an-evening&quot;&gt;A CSS Gotcha That Cost Me an Evening&lt;/h3&gt;
&lt;p&gt;The Interactivity API was working. The &lt;code&gt;hidden&lt;/code&gt; attribute was toggling perfectly in the DOM. But the elements were still visible on the screen. &lt;em&gt;Why?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Browsers have a default user-agent style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;[hidden] { display: none; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But if your block's custom CSS applies an explicit &lt;code&gt;display&lt;/code&gt; (like &lt;code&gt;display: flex;&lt;/code&gt;), it overrides the low-specificity &lt;code&gt;[hidden]&lt;/code&gt; rule. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;[hidden] {
	display: none !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Pro-tip: If you're using &lt;code&gt;data-wp-bind--hidden&lt;/code&gt; and nothing is happening visually, check your CSS specificity before ripping apart your JavaScript!&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-skip-wordpress-oembed-on-the-frontend-&quot;&gt;Why skip WordPress oEmbed on the frontend?&lt;/h3&gt;
&lt;p&gt;WordPress has robust oEmbed support. Why did I write manual regex provider detection for the frontend? &lt;/p&gt;
&lt;p&gt;Because the &lt;code&gt;/oembed/1.0/proxy&lt;/code&gt; endpoint requires &lt;code&gt;edit_posts&lt;/code&gt; permissions. An anonymous site visitor gets a &lt;code&gt;401 Unauthorized&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead, the block parses the URL itself (supporting YouTube, Vimeo, Dailymotion, Twitch, mp4s, etc.) and constructs the embed URL string locally. Zero network requests just to figure out a URL we already know how to build. &lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Building this reminded me of a golden rule in web development: &lt;strong&gt;Don't make every visitor pay for a feature that only some visitors will use.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;A video doesn't need to be a 1MB+ third-party application just because someone &lt;em&gt;might&lt;/em&gt; press Play. A static Gutenberg block doesn't need to become dynamic just to hold state. And a tiny bit of interactivity doesn't require shipping a massive JS runtime.&lt;/p&gt;
&lt;p&gt;If you're building blocks for WordPress, give the Interactivity API a shot for your next frontend component! &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://wordpress.org/plugins/story-video-block/&quot;&gt;Story Video Block&lt;/a&gt; is currently v0.1.0, requires WP 6.8+, and is GPL. If you want to check out the full code or contribute, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akshat009/story-video-block&quot;&gt;PRs are welcome on GitHub!&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27788/gutenberg-block-that-uses-the-interactivity-with-render-callback-framework-the-front-end</guid>
<pubDate>Sun, 20 Sep 2026 05:18:15 +0000</pubDate>
</item>
</channel>
</rss>