WordPress Theme Build Prompt — underscoretw
Create a WordPress theme named {THEME_SLUG} based on the underscoretw starter theme.
We will manually download the underscoretw starter theme from underscoretw.com and place it into the /themes/{THEME_SLUG}/ folder before development begins.
Your job is to continue from the existing scaffold already placed in the theme folder.
Follow this process carefully:
1. Prepare and verify the underscoretw setup
The downloaded underscoretw scaffold puts all PHP template files inside a theme/ subdirectory. WordPress scans only one level deep for style.css, so the theme is completely invisible to WordPress in this state.
Required first step — flatten the scaffold
Move all contents of theme/ to the theme root
Update package.json
change ./theme/style.css → ./style.css
change ./theme/style-editor.css → ./style-editor.css
change ./theme/js → ./js
Update node_scripts/zip.js if present
change the themeDir path from ../theme to ..
Delete the now-empty theme/ folder
Then confirm the structure is correct and run:
Ensure Tailwind compilation works and style.css appears at the theme root BEFORE editing any templates.
2. Use the HTML design from the wphtml/ folder
Preserve the design EXACTLY as provided
Do NOT redesign or simplify layouts
Preserve:
Tailwind classes
spacing
colors
gradients
animations
responsive behaviour
typography
hover effects
section ordering
Maintain semantic HTML where possible
3. Convert static HTML into WordPress templates
Break reusable sections into template-parts/ partials
Use proper WordPress template hierarchy
Keep underscoretw structure intact:
inc/
template-parts/
src/
compiled assets
functions.php organisation
For page <h1> headings:
Always use the_title()
Use ACF fields only for subtitles, body copy, and auxiliary content below it
Never echo a hardcoded title fallback alongside the_title()
4. Register editable content using ACF programmatically
Do NOT rely on ACF JSON import/export
Register all fields using PHP inside /inc/acf/
Use proper field groups and naming conventions
Important ACF safety rule
Always wrap every get_field() call with:
function_exists ('get_field ' )
before calling it.
ACF may not yet be active on a fresh install or in the theme previewer. Unguarded calls can cause fatal PHP errors.
Example:
if ( function_exists ( 'get_field ' ) ) {
$ hero_title = get_field ( 'hero_title ' );
}
For options page fields, always pass 'option' as the second argument:
get_field ( 'field_name ' , 'option ' );
Editable content includes
headings
paragraphs
buttons
links
hero content
CTA sections
testimonials
FAQ content
settings pages where appropriate
5. Use Custom Post Types for repeatable content
Register CPTs in PHP inside /inc/post-types/
Services
Portfolio
Team
Testimonials
FAQ
Create:
portfolio_category
After registering CPTs, the site owner must flush WordPress rewrite rules or archive/single URLs may return 404.
Add this comment to the CPT registration file:
// After activating this theme,
// go to Settings → Permalinks → Save Changes
// to flush rewrite rules.
5.5. Default content seeding from static HTML
The original HTML inside wphtml/ contains hardcoded demo content.
Do NOT leave CPT-driven sections empty after conversion.
The theme must automatically migrate the original hardcoded HTML content into WordPress content structures.
For every repeatable section converted into a CPT:
Parse the original static HTML content
Create equivalent default CPT entries programmatically
Preserve:
titles
descriptions
button labels
categories
tags
ordering
image references
links
slugs
Service cards → service CPT
Portfolio cards → portfolio CPT
Team members → team CPT
Testimonials → testimonial CPT
FAQ accordion items → faq CPT
Seeder implementation requirements
Create a dedicated demo content seeding system:
/inc/demo-content/
Recommended structure:
/inc/demo-content/
seed-demo-content.php
seed-services.php
seed-portfolio.php
seed-team.php
seed-testimonials.php
seed-faq.php
The seeder must:
Run only once after theme activation
Never create duplicate content
Check whether content already exists before inserting
Use stable slugs or unique meta flags for detection
Be safe to re-run manually if needed
Recommended detection pattern:
get_page_by_path ( 'web-design ' , OBJECT , 'service ' );
or:
meta_key = '_demo_seeded '
Use:
to trigger the initial seed process.
Example:
add_action ( 'after_switch_theme ' , 'underscoretw_seed_demo_content ' );
If the original HTML references local images:
Import them into the Media Library if possible
Set them as featured images
Preserve original filenames where possible
If automatic importing is too fragile:
Keep placeholder image references
Add clear TODO comments
If portfolio/service categories exist in the original HTML:
Automatically create taxonomy terms
Assign seeded posts to proper terms
Example:
wp_insert_term ( 'Web Design ' , 'portfolio_category ' );
After seeding:
All frontend sections must render dynamically from WordPress data
Templates must NOT continue rendering hidden hardcoded fallback HTML
The original static HTML becomes only the source material for migration
Provide a reusable helper function:
underscoretw_seed_demo_content ();
This allows developers to regenerate demo content during development.
When the theme is activated:
the website should immediately resemble the original HTML design
without requiring the site owner to manually create CPT entries
Use Contact Form 7 ONLY
Do NOT build custom forms
Do NOT use raw <form> implementations
Insert shortcode placeholders:
[contact-form-7 id="REPLACE_ME" title="Contact Form"]
The site owner will configure forms later in wp-admin.
7. Global theme settings — site identity and logo
Never hardcode the site name, logo, or tagline.
Wire them to native WordPress settings.
Register custom logo support
In functions.php:
add_theme_support (
'custom-logo ' ,
array (
'height ' => 60 ,
'width ' => 180 ,
'flex-height ' => true ,
'flex-width ' => true ,
)
);
Header logo implementation
In header.php:
if ( has_custom_logo () ) {
the_custom_logo ();
} else {
echo '<a href=" ' . esc_url ( home_url ( '/ ' ) ) . '" class="text-2xl font-bold text-forest tracking-tight"> '
. esc_html ( get_bloginfo ( 'name ' ) )
. '</a> ' ;
}
Apply the same fallback pattern in footer.php.
What
WordPress location
Function
Logo image
Appearance → Customize → Site Identity
has_custom_logo() / the_custom_logo()
Site name
Settings → General
get_bloginfo('name')
Tagline
Settings → General
get_bloginfo('description')
Favicon
Appearance → Customize → Site Identity
Handled automatically
Never hardcode these values anywhere.
8. WordPress best practices
Escape all output properly
esc_html
esc_url
esc_attr
wp_kses_post
Use WordPress enqueue system correctly
Avoid duplicated markup
Avoid hardcoded URLs
Use featured images where suitable
Use dynamic menus and widget areas where appropriate
This project uses Tailwind v4 with @tailwindcss/postcss.
Important differences from Tailwind v3:
There is NO tailwind.config.js
Define custom tokens inside:
tailwind/tailwind-theme.css
using:
Opacity modifiers compile using color-mix(in oklab, ...)
Do NOT reference:
theme.extend
purge
old Tailwind v3 config patterns
Tailwind v4 auto-detects templates
Ensure all PHP templates are reachable from the theme root
Alpine.js core is loaded from CDN
Alpine.js MUST use defer
Use:
add_filter ( 'script_loader_tag ' , function ( $ tag , $ handle ) {
if ( 'alpinejs ' === $ handle ) {
return str_replace ( '<script ' , '<script defer ' , $ tag );
}
return $ tag ;
}, 10 , 2 );
Alpine.js CDN core does NOT include plugins.
If using:
x-collapse
x-intersect
other plugins
explicitly enqueue the plugin CDN.
If plugin is not loaded:
do NOT use the directive
use plain x-show instead
After all templates are completed:
Verify:
no Tailwind classes are missing
responsive layouts still match original HTML
templates render correctly in WordPress
12. Important implementation rules
Never overwrite core underscoretw functionality unless necessary
Reuse existing underscoretw features/components where possible
Extend the starter instead of rebuilding existing systems
Keep code modular and production-ready
Prefer reusable template parts over duplicated sections
Maintain clean separation between:
presentation
content
data structures
theme logic
Fully functional custom WordPress theme
Based on underscoretw
Scaffold correctly flattened
Tailwind v4 compiled locally
All utility classes compiled correctly
Editable via ACF
All get_field() calls safely guarded
Dynamic CPT-powered sections
Automatic demo content seeding from original HTML
Compatible with standard WordPress workflows
Production-ready structure