
🛠️ Creating a WordPress theme from HTML: step by step guide (part 1)
A finished HTML site doesn't have to be thrown away when you move to WordPress. You can take your markup, index.html and style.css, and turn it into a fully functional theme in an hour or so. This is not magic: WordPress is built on PHP templates, which in turn are assembled from ordinary HTML split into logical parts.
The problem is that most tutorials either overwhelm you with terminology from the first paragraphs or silently assume you already know PHP. We'll take a different approach: we'll start with a specific HTML mockup (header, content, sidebar, footer) and methodically, file by file, build a working theme from it. No magic, just code and logic.
By the end of this guide, you will have a ready-made theme that you can upload to the WordPress admin panel, activate, and see your HTML inside a dynamic platform. This is the first part, the foundation. In the second part, we will cover individual page templates, custom fields, and the customizer.
💡 Quick overview:
- Give the theme a unique name and fill in the style.css header, WordPress recognizes themes by these metadata.
- Cut the original index.html into four PHP files: header.php, index.php, sidebar.php, and footer.php.
- Implement standard WordPress hooks (get_header, get_sidebar, get_footer, wp_head, and wp_footer) so the platform "sees" and picks up your markup.
- Add the WordPress Loop to index.php, the code snippet that outputs blog posts inside your HTML framework.
- Create a ZIP archive, upload the theme to the admin panel and activate it, your site comes alive on WordPress.
1. Naming the theme and the style.css file
The first thing you need to do is give your theme a unique name. Even if you're making the theme exclusively for your own site, WordPress needs to identify it somehow in the "Appearance → Themes" panel.
Starting conditions:
- You have an index.html and a stylesheet file style.css (or any other CSS file).
- You have a working WordPress installation with at least one standard theme, for example, Twenty Twenty-Five.
- You have already created a folder for your future theme inside
/wp-content/themes/.
Open your code editor (we recommend Sublime Text or VS Code), copy the contents of your stylesheet into a new file, and save it as style.css in the theme folder. At the very top of the file, before all CSS code, add the metadata block:
1 /* 2 Theme Name: My HTML-to-WordPress Theme 3 Theme URI: https://example.com 4 Description: Theme assembled from a static HTML layout. 5 Version: 1.0 6 Author: Your Name 7 Author URI: https://example.com 8 Tags: custom-theme, html-to-wordpress, beginner 9 */
Don't remove the comment symbols /* and */, WordPress reads exactly these. The Theme Name field is key here: this is the name under which the theme will appear in the admin panel. The other fields are optional, but we recommend filling them all in, it's good practice.
Save the file. At this stage, WordPress can already recognize your theme, although it's still empty and doesn't do anything useful.
2. Splitting HTML into PHP templates
A classic WordPress theme is assembled from several PHP files, each responsible for its own section of the page. The most common layout is header, content, sidebar, footer. If your HTML is structured differently (for example, sidebar on the left or two sidebars), the principle remains the same, just adapt the split to your structure.
Create four empty files in the theme folder:
header.php, site headerindex.php, main template, assembly pointsidebar.php, sidebar panelfooter.php, footer
They're empty for now, don't expect miracles. Let's go in order.
2.1 header.php, site header
Go to the WordPress admin panel, open "Appearance → Theme Editor" and select the standard theme (for example, Twenty Twenty-Five). Find its header.php and copy the entire <head> block, this is the minimum set of meta tags and hooks without which WordPress won't work correctly. Here's what you need to transfer to your header.php:
1 <head> 2 <meta charset="<?php bloginfo( 'charset' ); ?>"> 3 <meta name="viewport" content="width=device-width"> 4 <title><?php wp_title( '|', true, 'right' ); ?></title> 5 <link rel="profile" href="https://gmpg.org/xfn/11"> 6 <link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>"> 7 <?php wp_head(); ?> 8 </head>
The bloginfo() function dynamically substitutes site settings (encoding, pingback URL). The wp_head() hook is critical: plugins and the WordPress core insert their scripts, styles, and meta tags through it. Without it, the theme will be half dead.
Now open your original index.html and copy the header code, everything inside <header> or a similar block, and paste it into header.php **right after the closing </head> tag and the opening **<body>. In our demo markup, it looks like this:
1 <body> 2 <header class="header"> 3 <p>This is header section. Put your logo and other details here.</p> 4 </header> 5
Add the connection to your stylesheet between the <head> tags:
1 <link rel="stylesheet" type="text/css" media="all" href="<?php echo get_template_directory_uri(); ?>/style.css" />
The get_template_directory_uri() function returns the URL of your theme folder, the path to CSS will work on any server without hardcoding. Save header.php.
2.2 index.php, assembly point
index.php in WordPress is a required file. It doesn't store markup as such but assembles the page from parts. Open your empty index.php and add three lines:
1 <?php get_header(); ?> 2 3 <?php get_sidebar(); ?> 4 <?php get_footer(); ?>
The first line includes header.php. The last two, sidebar.php and footer.php, go at the very bottom. The main content (content area and Loop, more on this in the next section) will be placed between them.
Copy the block with the .content class (main content area) from your original index.html and paste it between get_header() and get_sidebar():
1 <?php get_header(); ?> 2 3 <div class="content"> 4 <p>This is the main content area.</p> 5 </div><!-- .content --> 6 7 <?php get_sidebar(); ?> 8 <?php get_footer(); ?> 9
The scheme is simple: WordPress sequentially assembles the page from top to bottom, header, content, sidebar, footer. Each get_*() pulls in the corresponding PHP file.
2.3 sidebar.php and footer.php
The sidebar and footer are very straightforward. Copy the contents of the .sidebar and .footer blocks from your index.html to the corresponding PHP files.
sidebar.php:
1 <aside class="sidebar"> 2 <p>This is the side bar</p> 3 </aside> 4 5 <?php dynamic_sidebar( 'sidebar' ); ?> 6
The dynamic_sidebar() function outputs widgets assigned in the admin panel ("Appearance → Widgets"). If the sidebar is not needed, delete this line.
footer.php:
1 <footer class="footer"> 2 <p>And this is the footer.</p> 3 </footer> 4 5 <?php wp_footer(); ?> 6 </body> 7 </html> 8
Note: </body> and </html> are moved to footer.php. In your original HTML, they are probably also at the very bottom, and that's how it should be. The wp_footer() hook is just as mandatory as wp_head(): without it, many plugins and WordPress itself won't work (for example, the admin bar won't appear).
3. The WordPress Loop: outputting posts
The theme is assembled, the framework works. But the content area is still static, with text hardcoded from the original HTML. For WordPress to start outputting real blog posts, you need The Loop, a special PHP construct that iterates through published posts and displays them one by one inside your markup.
This is the most important piece of code in the theme. It goes in index.php, inside the <div class="content"> block, replacing the static placeholder text:
1 <?php if ( have_posts() ) : ?> 2 <?php while ( have_posts() ) : the_post(); ?> 3 <article <?php post_class(); ?>> 4 <header class="post-header"> 5 <time datetime="<?php echo get_the_date( 'c' ); ?>"> 6 <?php echo get_the_date(); ?> 7 </time> 8 <h2> 9 <a href="<?php the_permalink(); ?>" rel="bookmark"> 10 <?php the_title(); ?> 11 </a> 12 </h2> 13 <span class="post-author"><?php the_author(); ?></span> 14 </header> 15 16 <div class="entry"> 17 <?php if ( has_post_thumbnail() ) : ?> 18 <?php the_post_thumbnail( 'medium' ); ?> 19 <?php endif; ?> 20 <?php the_content(); ?> 21 <?php edit_post_link( 'Edit', '<span class="edit-link">', '</span>' ); ?> 22 <?php wp_link_pages(); ?> 23 </div> 24 25 <footer class="post-footer"> 26 <?php comments_popup_link( 27 'Leave a comment', 28 '1 comment', 29 '% comments' 30 ); ?> 31 </footer> 32 </article> 33 <?php endwhile; ?> 34 35 <nav class="navigation"> 36 <div class="prev-posts"><?php next_posts_link( '← Previous posts' ); ?></div> 37 <div class="next-posts"><?php previous_posts_link( 'Newer posts →' ); ?></div> 38 </nav> 39 <?php else : ?> 40 <p>No posts found.</p> 41 <?php endif; ?> 42
How it works:
have_posts()checks if there are posts to display. If there are, it enters the Loop.the_post()prepares the data for the next post.post_class()adds standard CSS classes to<article>, useful for styling different post types.the_permalink()andthe_title()output the URL and title of the post.the_content()is the actual body of the post.the_post_thumbnail()is the post thumbnail, if one is set.comments_popup_link()is the link to comments.next_posts_link()andprevious_posts_link()handle pagination.
The else block fires when there are no posts at all, displaying a placeholder message.
After inserting the Loop, your complete index.php looks like this:
1 <?php get_header(); ?> 2 3 <div class="content"> 4 <?php if ( have_posts() ) : ?> 5 <?php while ( have_posts() ) : the_post(); ?> 6 <article <?php post_class(); ?>> 7 <header class="post-header"> 8 <time datetime="<?php echo get_the_date( 'c' ); ?>"> 9 <?php echo get_the_date(); ?> 10 </time> 11 <h2> 12 <a href="<?php the_permalink(); ?>" rel="bookmark"> 13 <?php the_title(); ?> 14 </a> 15 </h2> 16 <span class="post-author"><?php the_author(); ?></span> 17 </header> 18 <div class="entry"> 19 <?php if ( has_post_thumbnail() ) : ?> 20 <?php the_post_thumbnail( 'medium' ); ?> 21 <?php endif; ?> 22 <?php the_content(); ?> 23 <?php edit_post_link( 'Edit', '<span class="edit-link">', '</span>' ); ?> 24 <?php wp_link_pages(); ?> 25 </div> 26 <footer class="post-footer"> 27 <?php comments_popup_link( 28 'Leave a comment', 29 '1 comment', 30 '% comments' 31 ); ?> 32 </footer> 33 </article> 34 <?php endwhile; ?> 35 <nav class="navigation"> 36 <div class="prev-posts"><?php next_posts_link( '← Previous posts' ); ?></div> 37 <div class="next-posts"><?php previous_posts_link( 'Newer posts →' ); ?></div> 38 </nav> 39 <?php else : ?> 40 <p>No posts found.</p> 41 <?php endif; ?> 42 </div><!-- .content --> 43 44 <?php get_sidebar(); ?> 45 <?php get_footer(); ?> 46
4. Building and installing the theme
All five files (style.css, header.php, index.php, sidebar.php, and footer.php) should be in your theme folder (name it, say, my-html-theme). Package the folder into a ZIP archive.
Next, the standard scenario: WordPress admin → "Appearance" → "Themes" → "Add Theme" → "Upload Theme." Select the ZIP file, click "Install," then "Activate." Visit the site, your HTML mockup has come alive inside WordPress, and blog posts are displayed in the content area.
This concludes the first part. You now have a working theme framework: WordPress recognizes it, picks up the hooks, and outputs dynamic content through the Loop. In the next part, we will cover individual page templates (single.php, page.php), the template hierarchy, and script inclusion via functions.php.
⁉️🤔 Frequently asked questions
Do I need to know PHP to build a theme from HTML?
No. The minimum you'll write is a few function calls like
get_header()and a 15-line Loop. Everything else is copying HTML code you already have. PHP here acts as "glue," not the main language. If you understand where<?php ?>opens and closes, that's enough to get started.
What if my HTML mockup doesn't look like "header-content-sidebar-footer"?
The principle doesn't change. WordPress assembles the page by sequentially calling PHP files, their order in index.php determines the layout. If your sidebar is on the left, just call
get_sidebar()before the content block. Two sidebars? Createsidebar-left.phpandsidebar-right.phpwith separate calls. The scheme is flexible.
Can I create a theme without a sidebar?
Yes, and it even simplifies the task. Just remove the
<?php get_sidebar(); ?>call fromindex.phpand don't create thesidebar.phpfile. Many modern themes, including WordPress block themes, work without a sidebar at all.
Is it mandatory to use exactly header.php, index.php, sidebar.php, and footer.php?
index.phpandstyle.cssare mandatory, without them WordPress won't see the theme. The other files are optional. Butheader.php,sidebar.php, andfooter.phpare the de facto standard for classic themes: all developers understand them, and the functionsget_header(),get_sidebar(), andget_footer()are designed for them. For your first theme, stick with this set of four.
Does this approach work with block themes (FSE)?
No, this guide is for classic PHP themes, which are still alive and supported (WordPress has not abandoned them and has no plans to). Block themes (Full Site Editing) use HTML templates and a theme.json file. If your goal is to quickly turn existing HTML into a working theme, the classic approach is significantly simpler: fewer abstractions, familiar "header-content-sidebar-footer" model.
What's next: your first step in theme development
You've assembled a basic theme framework from HTML markup, with a working Loop, included hooks, and dynamic content. The theme already installs in the admin panel and outputs posts. Now there are two directions for growth:
- If outputting posts in your markup is enough, fill the site with content and refine the CSS to match your brand.
Start small: build the theme following this guide, activate it, and add a couple of test posts. Once you see your HTML with live posts inside, everything else will come easier.



