Mastering Modularity: Crafting a Reusable Button Component in FlyntWP
I've spent years battling slow, bloated WordPress sites, often victims of "convenient" low-code page builders. You know the drill: drag-and-drop promises speed, but delivers agonizing load times, messy HTML, and a developer's nightmare whenever a design tweak is needed. That's why I've fallen head over heels for FlyntWP. It's not just a theme starter; it's a philosophy that empowers you to build lightning-fast, highly maintainable WordPress sites by embracing a programmatic, component-driven approach. Today, I want to pull back the curtain on one of the foundational elements of this philosophy: building truly reusable Twig components, specifically a smart button.
Think about it: almost every website needs buttons. A Call-to-Action here, a "Read More" there. If you're using a page builder, you're likely dragging a "button block" and configuring it dozens of times, resulting in inconsistent styles and unnecessary CSS bloat. With FlyntWP, we approach this differently. We build a single, intelligent button component that can be dropped into any module, configured via ACF Pro, and rendered with pristine HTML. This isn't just about aesthetics; it's about a clean DOM, optimized for flawless Core Web Vitals right out of the gate.
Let's dive into the code. We establish a dedicated directory for general-purpose components, typically `_Components`. Inside `_Components/Button`, we place `functions.php` and `index.twig`. The `functions.php` registers the Advanced Custom Fields for our button, allowing us to define its text, URL, and style. This makes our button highly configurable from the WordPress admin. Here's a simplified example of `functions.php` to define the button's fields:
```php <?php
namespace Flynt\Components\Button;
use ACFComposer\ACFComposer; use Flynt\FieldVariables;
add_action('Flynt/afterRegisterComponents', function () { ACFComposer::registerSubComponent('Button', [ 'name' => 'Button', 'title' => 'Button', 'fields' => [ [ 'label' => __('Text', 'flynt'), 'name' => 'buttonText', 'type' => 'text', 'required' => 1, ], [ 'label' => __('Link', 'flynt'), 'name' => 'buttonLink', 'type' => 'url', 'required' => 1, ], [ 'label' => __('Style', 'flynt'), 'name' => 'buttonStyle', 'type' => 'select', 'choices' => [ 'primary' => 'Primary', 'secondary' => 'Secondary', ], 'default_value' => 'primary', 'wrapper' => [ 'width' => '50', ], ], [ 'label' => __('Open in new tab?', 'flynt'), 'name' => 'buttonTargetBlank', 'type' => 'true_false', 'ui' => 1, ], ] ]); }); ```
With our fields defined, the `index.twig` is where the button's structure comes to life. Notice how we use the `attributes` variable, a common FlyntWP pattern, to pass in any extra classes or IDs dynamically, keeping our Twig clean and highly flexible.
```twig <a href="{{ buttonLink }}" class="button {{ buttonStyle ? 'button--' ~ buttonStyle : '' }}" {{ buttonTargetBlank ? 'target="_blank" rel="noopener"' : '' }} {{ attributes|without('class') }} > {{ buttonText }} </a> ```
Now for the real magic of reusability. Imagine you have a `Hero` module (`_Components/Hero`). You want to place this button inside it. Instead of rewriting the button's HTML or ACF fields, you simply include our `Button` sub-component. In your `Hero/index.twig`, you might have something like this, passing the button data to the sub-component:
```twig <section class="hero"> <div class="container"> <h1>{{ title }}</h1> <p>{{ content }}</p> {% if button %} {% include '_Components/Button/index.twig' with button only %} {% endif %} </div> </section> ```
And in your `Hero/functions.php`, you'd include the button fields by simply referencing the registered `Button` sub-component via `FieldVariables::get('Button')`:
```php <?php
namespace Flynt\Components\Hero;
use ACFComposer\ACFComposer; use Flynt\FieldVariables;
add_action('Flynt/afterRegisterComponents', function () { ACFComposer::registerComponent('Hero', [ 'title' => __('Hero', 'flynt'), 'fields' => [ // Other Hero fields... [ 'label' => __('Button', 'flynt'), 'name' => 'button', 'type' => 'group', 'layout' => 'row', 'sub_fields' => FieldVariables::get('Button') ], ] ]); }); ```
This pattern is a game-changer. We've created a single source of truth for our button component. Any update to its structure, classes, or available ACF fields happens in one place, instantly propagating across your entire site. This means incredible consistency, reduced development time, and a DOM that's as lean as possible. Less markup means faster rendering, fewer reflows, and a better chance at nailing those crucial Core Web Vitals metrics like LCP and CLS. Plus, FlyntWP’s integration with Vite ensures your assets are compiled at blistering speeds, so even when you’re building complex sites with dozens of these components, your development workflow stays snappy. This is the power of FlyntWP: programmatic, modular, and performance-first. It's an investment in a robust, future-proof codebase, freeing you from the performance penalties of traditional page builders and empowering you to build WordPress experiences that truly fly.