← All posts

FlyntWP Deep Dive: Building a Flexible Button Component with Twig & ACF

FlyntWP Deep Dive: Building a Flexible Button Component with Twig & ACF

I've been working with WordPress for years, and like many of you, I've seen the pendulum swing from custom themes to monolithic page builders and back again. The promise of "low-code" often turns into a slow, bloated reality, drowning site performance and making maintenance a headache. That's why discovering FlyntWP was such a breath of fresh air. It's a truly programmatic approach that lets us craft bespoke, lightweight sites that scream performance, boast impeccable Core Web Vitals, and offer a developer experience that's actually enjoyable.

One of FlyntWP's superpowers lies in its modular Twig components, seamlessly integrated with ACF Pro. Instead of wrestling with a drag-and-drop interface, we define our content structures with ACF and our presentation with clean Twig templates. This allows for incredible reusability, which is precisely what I want to walk you through today. We're going to build a simple, flexible button component that can be used across any block or partial on your site, ensuring consistency and drastically speeding up development time.

The problem with many components, even in a modular system, is repetition. Think about a simple button – it needs text, a URL, and maybe a style (primary, secondary). If you define these fields and styles every time you need a button within a `BlockHero` or a `BlockCallToAction`, you're introducing unnecessary duplication and potential inconsistencies. FlyntWP, however, encourages us to think compositionally. We can define our button's logic and presentation once, then simply "include" it wherever it's needed.

Let's start by defining our button's data structure using ACF. Instead of creating a full ACF field group, we'll create a simple PHP file that returns an array of field definitions. This file will live in a `Partials` folder within our `Components` directory. Create `Components/Partials/_Button-fields.php` with the following:

<?php

use function Flynt\Functions\get_sub_field;

return [ [ 'label' => 'Button Link', 'name' => 'link', 'type' => 'link', 'return_format' => 'array', ], [ 'label' => 'Button Style', 'name' => 'style', 'type' => 'select', 'choices' => [ 'primary' => 'Primary', 'secondary' => 'Secondary', ], 'default_value' => 'primary', ], ];

This defines the `link` (ACF's built-in link field) and `style` fields for our button. Now, let's create the Twig partial that renders this data into HTML. Create `Components/Partials/_Button.twig`:

{# Components/Partials/_Button.twig #} {% if link.url and link.title %} <a href="{{ link.url }}" target="{{ link.target }}" class="button button--{{ style|default('primary') }}"> {{ link.title }} </a> {% endif %}

Here, we check if `link.url` and `link.title` exist to ensure we're rendering a valid button. The `target` attribute is safely included from the ACF link field, and our `class` leverages the `style` variable, falling back to 'primary' if no style is explicitly set, thanks to Twig's `default` filter. This keeps our styling flexible and robust.

Now for the magic: integrating this reusable button into a full FlyntWP component. Let's imagine a `BlockCallToAction` component that needs a headline, some text, and our trusty button. In `Components/BlockCallToAction/functions.php`, we register our fields:

<?php

use Flynt\FieldVariables; // Make sure to use FieldVariables

add_filter('Flynt/addComponentData?name=BlockCallToAction', function ($data) { // We could add any data manipulation here before Twig renders return $data; });

function flynt_register_block_call_to_action_fields() { return [ 'name' => 'BlockCallToAction', 'title' => 'Call to Action Block', 'description' => 'A block with a title, text, and a reusable button.', 'category' => 'flynt', 'labels' => FieldVariables::getLabels('Call to Action Block'), 'fields' => [ [ 'label' => 'Content', 'name' => 'contentHtml', 'type' => 'wysiwyg', 'media_upload' => false, 'required' => true, ], [ 'label' => 'Button', 'name' => 'button', // This is the group name that holds our button fields 'type' => 'group', 'sub_fields' => FieldVariables::getGroup('Components/Partials/_Button-fields'), 'wrapper' => [ 'width' => '50', ], ], // ... any other fields specific to BlockCallToAction ] ]; } add_filter('Flynt/registerComponent', function () { return flynt_register_block_call_to_action_fields(); });

Notice the `FieldVariables::getGroup('Components/Partials/_Button-fields')` line. This is where FlyntWP shines! We're not copy-pasting field definitions; we're dynamically loading the fields we defined in `_Button-fields.php`. This creates a nested group named `button` in our ACF interface for the `BlockCallToAction`. Finally, in `Components/BlockCallToAction/index.twig`, we render it:

{# Components/BlockCallToAction/index.twig #} <section class="block-call-to-action"> <div class="wrapper"> <div class="content"> {{ contentHtml }} </div> {% include 'Components/Partials/_Button.twig' with { link: button.link, style: button.style } only %} </div> </section>

We simply `include` our `_Button.twig` partial and pass it the `link` and `style` data from our `button` ACF group. The `only` keyword ensures that only the explicitly passed variables are available within the partial, keeping it clean and isolated.

This pattern profoundly changes how you build WordPress sites. No more sifting through complex page builder settings; just clean, predictable ACF fields and lightning-fast Twig templates. This structured, programmatic approach leads directly to cleaner DOM structures, faster build times (hello, Vite!), and ultimately, flawless Core Web Vitals. You get consistency across your site, drastically reduced development time, and a codebase that’s a joy to maintain. This is FlyntWP: the pragmatic, performant future of WordPress development.