Crafting Reusable Button Components in FlyntWP with Twig and ACF
I've been working with FlyntWP for a while now, and one of the things that truly sets it apart from the sea of drag-and-drop page builders is its commitment to a programmatic, developer-first approach. We're talking about building sites that are inherently lightweight, blazing fast, and deliver those pristine Core Web Vitals scores that search engines absolutely love. It's about taking control back from the endless cascade of `div` soup and truly owning your markup, all powered by modular Twig components, seamless ACF Pro integration, and the incredible speed of Vite.
Today, I want to dive into a practical example that perfectly illustrates this philosophy: building a truly reusable button component. In the world of typical low-code builders, a button is often a pre-defined block that generates a bunch of nested elements, making it hard to style consistently or extend without fighting the system. With FlyntWP, we approach it as a clean, independent module that you can summon and configure with surgical precision.
Our goal is to create a button that can be dropped into any larger component – be it a hero section, a call-to-action block, or a simple text area – and inherit its functionality and styling while being managed centrally. Here's how we set it up.
First, we define our button's data structure using ACF Pro. I create a flexible content field or a group within a parent component that holds our button fields. For a standalone reusable button, I often define a simple group of fields, perhaps named `Button` within a flexible content layout, with these specific fields: * **Link Type:** Select field (`link_type`, choices: Internal Link, External URL). This helps determine how we handle the link. * **Internal Link:** Post Object field (`internal_link`, conditional logic: show if `link_type` is Internal Link). * **External URL:** URL field (`external_url`, conditional logic: show if `link_type` is External URL). * **Button Text:** Text field (`button_text`). * **Open in New Tab:** True/False field (`open_in_new_tab`). * **Button Style:** Select field (`button_style`, choices: Primary, Secondary, Outline, etc.). This allows content editors to pick a visual style.
Now, with our ACF data structure in place, we create our actual Twig component. In FlyntWP, components live in `src/Components`. For our button, I'd create `src/Components/Button/index.twig`. This is where the magic happens. The beauty here is that this Twig file only cares about rendering *a button*. It doesn't know or care about its parent's complexities.
Here’s a simplified version of `src/Components/Button/index.twig`:
<a href="{{ buttonLink }}" class="button button--{{ buttonStyle }}" {% if openInNewTab %}target="_blank" rel="noopener noreferrer"{% endif %} > {{ buttonText }} </a>
Let me break down how this gets its data. When you include this button component from a parent component (like `BlockHero/index.twig`), you pass the data it needs. Imagine your `BlockHero` component has a button field group named `button_config`. In `BlockHero/index.twig`, you would do something like this to render the button:
{% if fields.button_config %} {% include 'Components/Button/index.twig' with { buttonLink: fields.button_config.link_type == 'internal_link' ? fields.button_config.internal_link.link : fields.button_config.external_url, buttonText: fields.button_config.button_text, openInNewTab: fields.button_config.open_in_new_tab, buttonStyle: fields.button_config.button_style, } only %} {% endif %}
Notice how we're dynamically determining `buttonLink` based on the chosen link type from ACF. We process the parent's ACF data and pass a clean, processed set of variables to our `Button` component, ensuring it always receives exactly what it expects. The `only` keyword is crucial; it prevents the `Button` component from inheriting variables from its parent that it doesn't need, keeping its scope clean and isolated.
This pattern offers immense benefits. You get true reusability: create the button once, style it with your CSS (which Vite quickly compiles and injects for lightning-fast development), and then just pass data to it wherever you need it. The resulting DOM is incredibly clean – a single `<a>` tag, precisely what you want for performance and accessibility, free from extraneous wrappers and inline styles generated by typical page builders. This directly translates to superior Core Web Vitals scores, better user experience, and a much happier development workflow. It’s this kind of thoughtful, programmatic construction that makes FlyntWP such a powerful and refreshing tool for modern WordPress development.