Add a masonry gallery to WordPress without a heavy plugin: a block you can build today
· 5 min read
The WordPress gallery block takes a set of images and arranges them in a grid. Column count, spacing, and crop mode are the controls it exposes. Masonry layout, where images of different heights fill vertical space without padding rows to a uniform height, is not a current option. There are open tracking issues for this and it may arrive in a future release, but for now the block ships without it.
The conventional answer has been to install a gallery plugin. Most of them offer masonry as one layout option inside a system that also handles lightboxes, slideshows, album structures, social sharing, and ecommerce-ready display modes. That is a lot of surface for a site that only needs images to stack sensibly in columns without cropping.
A plugin that adds one block to the inserter and renders a masonry layout is about a hundred lines across two files. Here is how to build one.
What one build gives you
- A Gutenberg block that selects multiple images from the WordPress media library
- CSS columns masonry layout with configurable column count (2 to 5) and gap size
- PHP render callback with no JavaScript build step required
- Responsive single-column layout on mobile via CSS media query
- Lazy loading on all images via the loading=lazy attribute
What it does not do
- Lightbox or image zoom on click: needs a separate JavaScript library
- Video files or mixed media in the same gallery
- Visible captions below or over each image (can be added via follow-up prompt)
- Drag-and-drop reordering of images within the block
- Gallery filtering or sorting by category, tag, or date from the front end
Envira Gallery does these. This covers the part most sites use.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WordPress plugin called Masonry Gallery that registers a Gutenberg block with namespace masonry-gallery/block. Write the editor JavaScript as plain uncompiled JS with no JSX and no build step, enqueued on enqueue_block_editor_assets. Block attributes: images (array of objects each with id, url, alt, width, height; default empty array), columns (integer default 3), gap (integer default 16). The edit function uses MediaUpload and MediaUploadCheck from wp.blockEditor to open the media library in multiple-select mode; when images are selected store them in the images attribute. Show selected images in the editor as a div using CSS column-count equal to the columns attribute with column-gap set to the gap attribute in pixels. Add an InspectorControls panel from wp.blockEditor with a PanelBody titled Layout containing a RangeControl for Columns (min 2, max 5, default 3) and a RangeControl for Gap in pixels (min 0, max 32, step 4, default 16). The save function returns null. Register the block in PHP with register_block_type passing the editor script handle and a render_callback. The render_callback outputs a div with inline style column-count equal to the columns attribute and column-gap equal to gap in pixels. Inside, render each image as a figure element with break-inside: avoid and margin-bottom equal to gap in pixels, containing an img with src, alt, width, height, loading=lazy, and style width: 100%; height: auto; display: block. Enqueue a separate front-end CSS file that adds a media query targeting max-width 600px where the gallery div gets column-count: 1. Do not use any external library, npm package, or build tool.
Why the native gallery block does not have masonry
The gallery block was designed to be predictable, accessible, and renderable correctly in every context including email clients and RSS readers. Masonry means one image affects the position of the next, which requires either JavaScript reflow or CSS columns. Both are outside the block's current scope, and the block team has focused on consistent cropped grids. Feature requests exist and the behaviour may change, but there is no timeline.
The result is that masonry in WordPress has belonged to gallery plugins for years. Install one and you get masonry plus lightboxes, album management, custom post types for galleries, video support, watermarking, and a settings page with dozens of options. For a site that only wants images to fill columns at their natural height, almost none of that is relevant.
What a purpose-built block provides
A Steem build for this job registers a single block in the inserter. You select images from the WordPress media library. An inspector panel gives you a column count from two to five and a gap size in pixels. The block stores image IDs as block attributes and renders the layout from PHP. No JavaScript library runs on the front end.
The layout uses CSS columns, a standard property with full browser support. Images fill columns vertically at their natural aspect ratio. A tall image takes more vertical space in its column; a short one takes less, and the next image drops below it. The column count collapses to one on mobile via a media query. This is the effect called masonry, and in CSS it takes three lines.
Who should stay on a gallery plugin
If you need a lightbox, a layout-only plugin will not give you one. Clicking an image and viewing it in a full-screen overlay requires JavaScript. You can add a separate lightweight lightbox plugin that attaches to linked images across the page, but that is a second install.
If you mix video files with images in the same gallery, or want galleries a visitor can filter by tag or sort by date from the front end, a dedicated gallery plugin handles that. The scope is real and a purpose-built block does not cover it.
If a client is managing dozens of galleries on a content-heavy site, the media library picker works, but the drag-and-drop controls in a full gallery plugin are richer for non-developer workflows.
A note on captions
The plugin as built uses image alt text for screen-reader accessibility but does not display visible captions. Masonry captions are a layout problem: the column structure separates images from any shared row baseline, so captions need to either float below each image or appear as a hover overlay.
If you want visible captions, ask in a follow-up message after the initial build: tell Steem to pull the caption from the WordPress attachment caption field and render it as a small paragraph below each image, or as a text overlay that appears on hover. Both are small additions to the render callback and do not require rebuilding the plugin.
How to build it
Paste the prompt below into Steem. The plugin downloads as a zip and installs under Plugins > Add New > Upload Plugin. After activation, a Masonry Gallery block appears in the inserter.
Start with a single gallery instance and verify the column layout at your target count before adding it across the site. The inspector panel lets you adjust columns and gap per block instance, so you can have a three-column gallery on one page and a four-column one on another without touching code.
Questions
- Will this block work inside Elementor or Divi?
- No. Elementor and Divi use their own editors and do not recognize Gutenberg blocks. This block works in the standard WordPress block editor. If your site uses a page builder, the right answer is to use that builder's native gallery or image grid widget.
- How do I add a lightbox so clicking an image opens a full-size view?
- Install a separate lightweight lightbox plugin that targets linked images sitewide. Then ask Steem in a follow-up to wrap each img in the render callback with an anchor tag pointing to the full-size image URL. The lightbox plugin will intercept those links automatically.
- The images look more like a grid than masonry. What is happening?
- CSS columns masonry is most visible when images vary in height. If all your images have the same dimensions, the result looks like a uniform grid. Upload images with different aspect ratios and the stacking effect becomes clear. If you want JavaScript-driven masonry that reflows images precisely, ask Steem in a follow-up to switch the layout to Masonry.js.
- Can I have multiple galleries on the same page with different column counts?
- Yes. Each block instance stores its own columns and gap values independently. Add a second Masonry Gallery block, select different images, and set a different column count in the inspector panel. They render independently.
- The native gallery block already supports column count. What does this add?
- The native gallery block enforces a consistent row height, cropping images to fill a uniform grid. This plugin uses CSS columns, which stacks images at their natural aspect ratios without cropping. Tall images take more column height; short ones take less. That variable-height stacking is the masonry effect.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WordPress plugin called Masonry Gallery that registers a Gutenberg block with namespace masonry-gallery/block. Write the editor JavaScript as plain uncompiled JS with no JSX and no build step, enqueued on enqueue_block_editor_assets. Block attributes: images (array of objects each with id, url, alt, width, height; default empty array), columns (integer default 3), gap (integer default 16). The edit function uses MediaUpload and MediaUploadCheck from wp.blockEditor to open the media library in multiple-select mode; when images are selected store them in the images attribute. Show selected images in the editor as a div using CSS column-count equal to the columns attribute with column-gap set to the gap attribute in pixels. Add an InspectorControls panel from wp.blockEditor with a PanelBody titled Layout containing a RangeControl for Columns (min 2, max 5, default 3) and a RangeControl for Gap in pixels (min 0, max 32, step 4, default 16). The save function returns null. Register the block in PHP with register_block_type passing the editor script handle and a render_callback. The render_callback outputs a div with inline style column-count equal to the columns attribute and column-gap equal to gap in pixels. Inside, render each image as a figure element with break-inside: avoid and margin-bottom equal to gap in pixels, containing an img with src, alt, width, height, loading=lazy, and style width: 100%; height: auto; display: block. Enqueue a separate front-end CSS file that adds a media query targeting max-width 600px where the gallery div gets column-count: 1. Do not use any external library, npm package, or build tool.
Steem