Convert static HTML to production-ready WordPress theme with ACF (Advanced Custom Fields). Use for: analyzing HTML structure, mapping sections to ACF field groups, generating theme templates, creating section-based architecture with admin tabs. Specialized for Vietnamese WordPress development workflow.
Senior WordPress Architect workflow for converting static HTML into production-ready WordPress themes with ACF field groups.
1 Section = 1 ACF Group = 1 Admin Tab = 1 Template Part
This is the foundation of maintainable WordPress themes with ACF.
Before writing any code, analyze the HTML structure:
Page Sections:
Dynamic vs Static:
Output Required:
1. Architecture diagram
- Global Layout (header/footer)
- Page Templates needed
- Reusable Components
2. Template files list
- front-page.php (home.html)
- page-about.php (about.html)
- archive.php (blog.html)
- single.php (blog-detail.html)
- etc.
3. ACF Section mapping
- Page → Sections → Fields
- Group names and locations
Create production-standard structure:
theme-name/
├── style.css
├── functions.php
├── screenshot.png
├── index.php
├── front-page.php
├── page.php
├── single.php
├── archive.php
├── header.php
├── footer.php
├── acf-json/ ← ACF JSON auto-sync
├── template-parts/
│ ├── section/ ← Reusable sections
│ │ ├── hero.php
│ │ ├── features.php
│ │ └── cta.php
│ └── component/ ← Small components
│ ├── button.php
│ └── card.php
├── inc/
│ ├── function-setup.php ← Enqueue, theme support
│ ├── function-pagination.php
│ ├── function-post-types.php ← Custom post types
│ ├── function-field.php ← ACF helpers
│ └── function-custom.php ← Custom functions
├── UI/ ← Original HTML files
└── assets/
└── images/
Key Requirements:
get_template_directory_uri())HTML to WordPress template mapping:
| HTML File | WordPress Template | When to Use |
|---|---|---|
| home.html | front-page.php | Static homepage |
| about.html | page-about.php | Specific page by slug |
| contact.html | page-contact.php | Specific page by slug |
| blog.html | archive.php | Blog listing |
| blog-detail.html | single.php | Single post |
| service.html | archive-service.php | Custom post type archive |
| service-detail.html | single-service.php | Custom post type single |
Template Selection Priority:
page-{slug}.php - Specific page by slugpage-{id}.php - Specific page by IDpage.php - Default page templateAnalyze HTML to identify content types that need CPT:
Common CPT Candidates:
project)service)product)team)testimonial)job)case-study)Implementation:
inc/function-post-types.phparchive-{posttype}.phpsingle-{posttype}.phptitle, editor, thumbnail, excerptWP_Query - NEVER query_posts()The Golden Rule: 1 Page = 1 ACF Group (with Tabs for each Section)
CRITICAL STRUCTURE RULES:
group_home_page.json)"style": "default", NEVER use "seamless"wysiwyg field type for ALL paragraphs/descriptions, NOT textareaExample - Home Page with 8 Sections:
If your Home Page HTML has 8 sections:
✔ Create 1 ACF field group: group_home_page.json
✔ Use 8 Tab fields to separate sections
✔ Create 8 template-parts files for rendering
Correct ACF Structure:
{
"key": "group_home_page",
"title": "Homepage Content",
"style": "default", // ← ALWAYS default, NOT seamless
"fields": [
{
"key": "field_tab_hero",
"label": "Hero Section",
"type": "tab",
"placement": "top"
},
{
"key": "field_hero_title",
"name": "hero_title",
"type": "wysiwyg" // ← WYSIWYG, not textarea
},
// ... more hero fields
{
"key": "field_tab_about",
"label": "About Section",
"type": "tab",
"placement": "top"
},
// ... about fields
]
}
OLD (INCORRECT) Structure:
❌ group_home_hero.json
❌ group_home_about.json
❌ group_home_services.json
NEW (CORRECT) Structure:
✅ group_home_page.json (with Tab fields)
ACF Groups Configuration (Deprecated - DO NOT USE):
Group 1: "Home - Hero"
- Location: Page Template = front-page.php
- Style: Seamless
- Label Placement: Top
- Fields: hero_title, hero_content, hero_button, hero_background
Group 2: "Home - About"
- Location: Page Template = front-page.php
- Fields: about_title, about_content, about_image
... and so on for each section
Field Naming Convention:
hero_title, features_itemsField Type Rules:
| HTML Element | ACF Field Type | Return Format | Notes |
|---|---|---|---|
| Short text (eyebrow, label) | Text | - | Single line only |
| Paragraph/Description | WYSIWYG | - | ALWAYS wysiwyg, NEVER textarea |
| Button/Link | Link | Array | - |
| Image | Image | Array | - |
| Grid/List/Slider | Repeater | - | - |
| Toggle sections | True/False | - | - |
| Select dropdown | Select | - | - |
CRITICAL: Text Field Selection
Image Rendering Functions:
Use custom helper functions for all images (includes lazy loading optimization):
get_image_attrachment($image, 'image') - Renders ACF image field with lazy loadingget_image_post($post_id, 'image') - Renders post featured image with lazy loading'url' as second parameter for both functionsIMPORTANT HTML Adjustment:
When content comes from WYSIWYG fields, change <p> tags to <div> to prevent content from escaping:
// ❌ WRONG - Content may break out of <p>
<p class="content"><?php echo wp_kses_post($hero_content); ?></p>
// ✔ CORRECT - Use div for WYSIWYG content
<div class="content"><?php echo wp_kses_post($hero_content); ?></div>
Repeater Example:
// HTML: List of features
<div class="features">
<div class="feature-item">
<img src="icon1.svg" alt="Feature 1">
<h3>Feature Title</h3>
<p>Feature description</p>
</div>
<!-- More items -->
</div> (using custom image function):
<?php if (have_rows('features_items')): ?>
<div class="features">
<?php while (have_rows('features_items')): the_row();
$icon = get_sub_field('feature_icon');
$title = get_sub_field('feature_title');
$content = get_sub_field('feature_content');
?>
<div class="feature-item">
<?php if ($icon): ?>
<?php echo get_image_attrachment($icon, 'image'); ?
$icon = get_sub_field('feature_icon');
$title = get_sub_field('feature_title');
$content = get_sub_field('feature_content');
?>
<div class="feature-item">
<?php if ($icon): ?>
<img src="<?php echo esc_url($icon['url']); ?>"
alt="<?php echo esc_attr($icon['alt']); ?>">
<?php endif; ?>
<?php if ($title): ?>
<h3><?php echo esc_html($title); ?></h3>
<?php endif; ?>
<?php if ($content): ?>
<div><?php echo wp_kses_post($content); ?></div>
<?php endif; ?>
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>
ALWAYS check data before output:
// ✔ CORRECT
if ($hero_title) {
echo '<h1>' . esc_html($hero_title) . '</h1>';
}
// ❌ WRONG - Missing check
echo '<h1>' . esc_html($hero_title) . '</h1>';
Escaping Functions:
| Context | Function | Use Case |
|---|---|---|
| Plain text | esc_html() | Titles, headings, plain text |
| URL | esc_url() | Links, image sources |
| HTML content | wp_kses_post() | WYSIWYG content |
| Attribute | esc_attr() | HTML attributes |
| JavaScript | esc_js() | Inline JS strings |
| ACF Images | get_image_attrachment() | ACF image fields (includes escaping) |
| Post Images | get_image_post() | Post thumbnails (includes escaping) |
Header and Footer content should use ACF Options Page, not page-specific fields.
Create Options Page:
// inc/function-setup.php
if (function_exists('acf_add_options_page')) {
acf_add_options_page(array(
'page_title' => 'Theme Settings',
'menu_title' => 'Theme Settings',
'menu_slug' => 'theme-settings',
'capability' => 'edit_posts',
'redirect' => false
));
}
ACF Groups for Global Elements:
Use get_template_part() for sections:
// front-page.php
<?php get_header(); ?>
<?php get_template_part('template-parts/section/hero'); ?>
<?php get_template_part('template-parts/section/about'); ?>
<?php get_template_part('template-parts/section/services'); ?>
<?php get_template_part('template-parts/section/features'); ?>
<?php get_template_part('template-parts/section/cta'); ?>
<?php get_footer(); ?>
Benefits:
get_template_part() argsLocation:
wp-content/themes/canhcamtheme/acf-json/
Why ACF JSON?
Setup:
ACF automatically saves field groups to acf-json/ folder if it exists. Create the folder in your theme root.
Alternative: Use acf_add_local_field_group() code if JSON is not preferred.
Before delivery, verify:
Structure:
inc/ folder)ACF:
Code Quality:
query_posts() usedWP_Query correctlySEO & Accessibility:
Performance:
inc/function-setup.phpWhen completing a conversion, provide in this order:
Architecture Explanation
File List
Code for Each File
ACF Field Groups
Example Implementation
Admin Guide (Optional)
❌ Hardcoded URLs
// Wrong
<img src="http://example.com/wp-content/themes/mytheme/images/logo.png">
// Correct
<img src="<?php echo esc_url(get_template_directory_uri() . '/assets/images/logo.png'); ?>">
❌ Missing Data Validation
// Wrong
<h1><?php echo esc_html(get_field('title')); ?></h1>
// Correct
<?php
$title = get_field('title');
if ($title): ?>
<h1><?php echo esc_html($title); ?></h1>
<?php endif; ?>
❌ Using query_posts()
// Wrong
query_posts('post_type=product');
// Correct
$products = new WP_Query(array('post_type' => 'product'));
❌ <p> Tags for WYSIWYG Content
// Wrong - content will escape
<p class="content"><?php echo wp_kses_post($content); ?></p>
// Correct
<div class="content"><?php echo wp_kses_post($content); ?></div>
Your WordPress theme is production-ready when:
✔ Admin can manage all content via ACF without touching code ✔ Each section is independent and reusable ✔ Code is clean, documented, and follows WordPress standards ✔ Theme is scalable for future additions ✔ All output is properly validated and escaped ✔ ACF structure mirrors HTML structure logically