Crafting a Reusable Button Component in FlyntWP with Twig and ACF
I've been building WordPress sites for over a decade, and I can tell you, the rise of page builders, while convenient on the surface, often led me down a path of frustration. Bloated code, sluggish editor experiences, and a constant fight against a rigid framework just to get a simple design right. That's why discovering FlyntWP was such a breath of fresh air. It offers a truly programmatic, lightweight alternative that empowers developers without sacrificing client usability, and today I want to walk you through a specific example: creating a reusable button component.
Imagine you need a standard call-to-action button that appears in various sections of your site – a hero banner, a features block, maybe a contact section. In a typical page builder, you might drag in a "button" element, configure it, and then repeat that process, potentially leading to inconsistencies or making a global style change a nightmare. With FlyntWP, we leverage modular Twig components and ACF Pro to build this once, perfectly, and reuse it everywhere.
First, we define our button's core structure in a dedicated Twig file. Let's create `Components/Button/index.twig`. Inside, we'll keep it as lean as possible:
<a href="{{ link.url }}" class="button button--{{ style }}" {% if link.target %}target="{{ link.target }}"{% endif %}> {{ link.title }} </a>
Notice how clean that is. No extra divs, no unnecessary wrappers – just the `<a>` tag we need. We're expecting `link` to be an object (likely from an ACF Link field) and `style` to be a string (like 'primary' or 'secondary') to apply a BEM-style class for styling.
Next, we need a way for our clients to actually *define* this button's content and destination. This is where ACF Pro shines. In `Components/Button/functions.php`, we register an ACF field group specifically for our button using Flynt's `addComponentData` filter:
add_filter('Flynt/addComponentData', function ($data) { if ($data['name'] == 'Button') { $data['fields'] = [ 'buttonLink' => [ 'label' => 'Button Link', 'name' => 'buttonLink', 'type' => 'link', 'required' => 1, ], 'buttonStyle' => [ 'label' => 'Button Style', 'name' => 'buttonStyle', 'type' => 'select', 'choices' => [ 'primary' => 'Primary', 'secondary' => 'Secondary', ], 'default_value' => 'primary', 'allow_null' => 0, 'multiple' => 0, 'ui' => 0, ], ]; } return $data; });
This snippet tells ACF to create a Link field and a Select field for our button. When a client adds a component that *uses* our button, they'll see these intuitive fields in the WordPress editor.
Now, how do we use this little hero? Let's say we have a `Hero` component. In its `index.twig` file, we might have something like this to display the button:
<section class="hero"> <div class="hero__content"> <h1>{{ hero_title }}</h1> <p>{{ hero_text }}</p> {% include 'Components/Button/index.twig' with { link: hero_button_link, style: hero_button_style } %} </div> </section>
Here, `hero_button_link` and `hero_button_style` would be ACF fields defined within the `Hero` component's `functions.php` file, specifically for its button. We pass these values directly to our `Button` component using the `with` keyword in Twig. This creates a clean separation of concerns: the `Hero` component manages its own content, but delegates the rendering of its button to the dedicated `Button` component.
The beauty of this approach is multi-faceted. Firstly, the modularity means our button is truly reusable and consistent across the site. Secondly, the DOM structure is perfectly clean, directly contributing to flawless Core Web Vitals because there's no unnecessary markup to render or style. FlyntWP's integration with Vite ensures our JavaScript and CSS builds are lightning fast, further enhancing performance. We have complete programmatic control over every pixel and every line of code, ensuring our sites are not just functional, but genuinely performant and maintainable. This pattern scales wonderfully, allowing us to build complex layouts from simple, well-defined components, leaving the bloat and frustration of generic page builders firmly in the past.