Hi everyone!
For years, our team relied heavily on ACF’s Flexible Content Builder. It was our go-to approach for building modular, dynamic layouts in WordPress, and it served us well. But as Gutenberg has matured and become more powerful, we saw the writing on the wall: the future of WordPress is in the block editor. So, we made the leap—transitioning to native Gutenberg blocks powered by ACF Composer for our custom needs. It’s been a journey of discovery, problem-solving, and a little trial and error, but I’d love to share where we’ve landed and open up the floor for insights from the Roots community.
One of the first challenges we faced was deciding how to handle core WordPress blocks like headings, paragraphs, and buttons. They’re essential to Gutenberg’s functionality, but as Tailwind CSS enthusiasts, we wanted them to align perfectly with our design system. Should we restyle these blocks to fit into our framework, or disable them entirely and create custom ACF Composer blocks for everything? This question became a recurring debate in our meetings, and after experimenting with both approaches, we’ve landed on a hybrid strategy, leaning heavily towards restyling core blocks.
Restyling turned out to be a surprisingly smooth process thanks to the combination of Tailwind CSS and theme.json. For example, we used Tailwind utility classes to style core blocks like headings and buttons, creating a unified look that feels like it belongs to our design system. Here’s a quick snippet of what this looks like in practice:
hero.php
: Block Definition
This file defines the backend configuration of the Hero block using ACF Composer. It’s where the structure, custom fields, and block template are specified.
.
.
.
/**
* The block template.
*
* @var array
*/
public $template = [
['core/heading', ['placeholder' => 'Hello World']],
['core/paragraph', ['placeholder' => 'Welcome to the Hero block.']],
['core/button', ['placeholder' => 'Get Started']],
];
/**
* Data to be passed to the block before rendering.
*/
public function with(): array
{
return [
'hero_image_mobile' => $this->heroImageMobile(),
'hero_image_tablet' => $this->heroImageTablet(),
'hero_image_desktop' => $this->heroImageDesktop(),
];
}
/**
* The block field group.
*/
public function fields(): array
{
$fields = Builder::make('hero');
$fields
->addFile('header_media_mobile', [
'label' => 'Header Hintergrundbild / -video',
'instructions' => 'Wählen Sie ein Bild oder Video für den Header aus.',
'return_format' => 'array',
'library' => 'all',
'wrapper' => [
'width' => '50',
],
])
->addFile('header_media_tablet', [
'label' => 'Header Hintergrundbild / -video (Tablet)',
'instructions' => 'Wählen Sie ein Bild oder Video für den Header aus.',
'return_format' => 'array',
'library' => 'all',
'wrapper' => [
'width' => '50',
],
]);
return $fields->build();
}
/**
* Retrieve the items.
*
* @return array
*/
public function heroImageMobile()
{
return get_field('header_media_mobile') ?: [];
}
public function heroImageTablet()
{
return get_field('header_media_tablet') ?: [];
}
public function heroImageDesktop()
{
return the_post_thumbnail_url() ?: "";
}
.
.
.
hero.blade.php
: Block Markup
This file defines the visual structure and rendering logic of the Hero block using Blade templates. It serves as the frontend for the block.
<section class="c-hero {{ $block->classes }} bg-white dark:bg-gray-900" style="{{ $block->inlineStyle }}">
<div class="grid max-w-screen-xl px-4 py-8 mx-auto lg:gap-8 xl:gap-0 lg:py-16 lg:grid-cols-12">
<div class="mr-auto place-self-center lg:col-span-7">
<InnerBlocks template="{{ $block->template }}" />
</div>
<div class="hidden lg:mt-0 lg:col-span-5 lg:flex">
<img src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/hero/phone-mockup.png" alt="mockup">
</div>
</div>
</section>
app.css
: Styling with Tailwind CSS
This file contains the Tailwind CSS-based styles for the Hero block. It ensures that the block adheres to the project’s design system and remains consistent across different pages.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.c-hero {
h1 {
@apply max-w-2xl mb-4 text-4xl font-extrabold tracking-tight leading-none md:text-5xl xl:text-6xl dark:text-white;
}
p {
@apply max-w-2xl mb-6 font-light text-gray-500 lg:mb-8 md:text-lg lg:text-xl dark:text-gray-400;
}
.btn.btn-primary a {
@apply inline-flex items-center justify-center px-5 py-3 mr-3 text-base font-medium text-center text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 dark:focus:ring-primary-900;
}
.btn.btn-secondary a {
@apply inline-flex items-center justify-center px-5 py-3 text-base font-medium text-center text-gray-900 border border-gray-300 rounded-lg hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 dark:text-white dark:border-gray-700 dark:hover:bg-gray-700 dark:focus:ring-gray-800;
}
}
}
Now, I’d love to hear from you. How are you handling core blocks in your projects? Are you embracing Gutenberg fully, or do you still prefer the flexibility of custom approaches like ACF Composer? Let’s swap ideas and learn from each other as we navigate this exciting evolution of WordPress together.
Cheers
Tim