Transitioning from ACF Flexible Content to Gutenberg + ACF Composer

Hi everyone! :wave:

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. :raised_hands::computer:

Cheers
Tim

2 Likes

So, we made the leap—transitioning to native Gutenberg blocks powered by ACF Composer for our custom needs.

Just a quick note on terminology — ACF blocks are not native Gutenberg blocks. I’d generally urge folks to not use ACF blocks and create their own proper native/first-party Gutenberg blocks, but I understand that a lot of people don’t have the time to spend building out a proper block without ACF.

:wave: Thanks for pointing that out, Ben—you’re totally right that ACF blocks aren’t “native” Gutenberg blocks. For us, though, ACF Composer has been a great middle ground. It lets us build structured, reusable components quickly without the heavier lift of native block development.

Of course, we’re aware of the trade-offs—like performance and future-proofing. Native blocks definitely integrate more seamlessly with Gutenberg, but the time and effort to build them from scratch can be a hurdle.

What’s your take? Do you see any must-have advantages to fully native blocks that outweigh the practicality of ACF for smaller teams or faster timelines?

ACF Composer is an incredible tool :fire:

:warning: I haven’t looked at ACF Blocks in at least a few years. That said, they are very limited with regards to what the block editor is actually capable of pulling off if you’re able to learn how to work with React.

I just want to deliver a great experience to whoever it is that is going to be using what I’m working on. In some cases, that person could care less about a fancy block and is completely fine with a simple ACF Block. I think you’ll have to see what works best for you and the folks that use the sites that you work on, but keep in mind that the block editor is extremely powerful and that ACF Blocks have limitations on what you’re able to do.

2 Likes

This great,
I have so much in love with sage theme and roots in general due to the fact that it is minimal and clean… ACF’s Flexible Content Builder has been a great partner… am glad I completely switched from it immediately sage10 and latest gutenberg was out our developer team have had to be creative on how to solely depend on gutenberg to achieve certain functionalities such a good muscle flex… so far it has been great… going minimal on plugins and clean on code is what makes this project fantastic… sage is wordpress on healthy diet and a weight loss routine… so far so good…

1 Like

FWIW, I have created a setup in which I remove all native blocks and then build everything from scratch using ACF Composer with some custom functionality where, for example, each Block/Field/Partial gets its own “model” class which allows me to separate and re-use things in a nice way.

In the editor, I have completely disabled the preview mode for the blocks so Gutenberg basically becomes a giant and performant version of ACFs flexible content. I have done it like this since I don’t want to spend any time (or the clients money) to make sure that everything works and looks in the editor as it does in the frontend. If the preview-mode isn’t 100% accurate, it’s pointless IMHO. It usually takes a building a couple of pages until the administrators gets the hang of how a block will look when it is rendered and can visualize it in their head, like in the good old days.

The reason for not using old school ACF is that i have accepted that Gutenberg is the future in one way or another and there are a lot of nice features in the editor like the block overview and saving is blazing fast. Also, saving post content the official way feels future proof in regards to how, for example, plugins expects data to be saved and formatted.

The reason for not using native blocks is that I am not keen on having to learn React to be able to work with WP and until it becomes impossible to create stuff using PHP, I am sticking to it :slight_smile:

3 Likes

I haven’t tried it out, but another alternative to ACF Blocks without needing to write React is https://blockstudio.dev/

3 Likes

It’s a good package—I’ve tried it before, and it works well. However, the free version can be quite limiting and challenging to extend, which makes the paid version almost necessary for ease of use.

That said, it reminds me of the Log1x/acf-composer package, which is excellent if you’re already using ACF Pro to streamline your workflow. However, with a bit of tweaking and leveraging Sage’s native Service Provider, you can achieve even more without necessarily needing the package and have fewer package dependencies in your application.

I don’t believe there’s a free version of Blockstudio, are you maybe thinking of another one?

https://github.com/yarovikov/gutengood is another one that was shared on here not too long ago