Clients want to build their own pages. Designers want those pages to look like the design. ACF’s flexible content field can give you both, but only if you’re deliberate about what you let editors change. Give them too little and every new page needs a developer. Give them too much and six months later the site has nine shades of blue and a heading set in 13px.
Here’s how I set up flexible content so editors have real freedom and the design still holds.
Layouts are components, not blank canvases
A flexible content field is a list of layouts: hero, text and image, testimonial, call to action and so on. Editors stack them in any order. The trick is to make each layout a finished design component with a small number of decisions, not a box of styling controls.
Compare two “text and image” layouts. One has fields for:
- Heading, text, image
- Image side: left or right
The other adds background colour (colour picker), heading size (number), padding top and bottom (number), image width (percentage) and text alignment.
The second looks more flexible, and it’s where design drift comes from. Every number field is an invitation to make one page slightly different from the rest. The first layout can only produce combinations the designer has already approved.
When a client really does need a variation, I add it as a named choice rather than a value:
array(
'key' => 'field_text_image_style',
'label' => 'Background',
'name' => 'style',
'type' => 'button_group',
'choices' => array(
'plain' => 'Plain',
'tinted' => 'Tinted',
'dark' => 'Dark',
),
'default_value' => 'plain',
),
Three options, each one designed. The template turns the choice into a class, and the CSS decides what “tinted” means. If the brand colour changes, it changes in one place.
One template per layout
On the theme side, each layout gets its own template part, named after the layout:
<?php
if ( have_rows( 'sections' ) ) :
while ( have_rows( 'sections' ) ) :
the_row();
get_template_part( 'template-parts/sections/' . get_row_layout() );
endwhile;
endif;
Adding a layout then means adding one field definition and one template file, and nothing else in the theme has to know about it. It also keeps each template small enough to read in one go.
Register fields in PHP
I register field groups in PHP with acf_add_local_field_group() rather than building them in the admin. The definitions live in version control alongside the templates that read them, a fresh install has all its fields straight away, and nobody can change a field on the live site by accident. (There’s a catch with field keys when you move existing sites over to this, which I’ll cover in a separate post.)
Defaults that look right
An empty layout should still look finished. Set sensible default values, and in the template fall back gracefully when a field is blank:
$heading = get_sub_field( 'heading' );
$image = get_sub_field( 'image' );
?>
<section class="text-image text-image--<?php echo esc_attr( get_sub_field( 'style' ) ?: 'plain' ); ?>">
<?php if ( $heading ) : ?>
<h2 class="text-image__heading"><?php echo esc_html( $heading ); ?></h2>
<?php endif; ?>
…
A layout with no image should still sit nicely on the page. That means deciding up front what it looks like without one, not leaving an empty box where the image would be.
Keep headings honest
Editors stack layouts in any order, so a layout can’t assume where it sits in the page outline. I use an <h2> for each section heading and an <h3> for anything inside it, so the page keeps a sensible structure for screen readers and search engines whatever the order. If a layout needs a heading to look bigger or smaller, that’s a CSS class, not a different heading level.
Help text and previews
Every field gets instructions written for the person editing, not the developer:
- “Shown above the button. Keep it under 8 words.” is useful.
- “CTA heading text” isn’t.
ACF can also show a screenshot of each layout in the “Add row” menu with a little CSS in the admin, or with ACF Blocks’ preview if you’re using the block editor. Editors pick components visually instead of guessing from names like “Feature Grid 2”.
Limit, don’t trust
A few constraints prevent most problems:
- Maximum rows on repeaters inside a layout. A three-column grid with eleven items will wrap badly.
- Character limits on short fields such as button labels.
- Image size guidance, plus registered image sizes so a 6 MB upload is served as a sensible thumbnail.
- Required fields only where the layout genuinely breaks without them.
The result
Done this way, editors build new pages in an afternoon without asking, and the site still looks designed a year later. The developer’s time goes on new layouts when the business needs them, not on fixing pages that drifted.
That’s how I build custom WordPress sites: give clients room to work, with the design protected.