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
} %}
</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.