In this guide we’ll build a complete custom WordPress theme with Tailwind CSS, from an empty folder to a polished, production-ready theme – a homepage with hero and services sections, a blog with cards and pagination, single posts with comments, page templates, a 404, the lot. This isn’t a “hello world” exercise: it’s the Business Tailwind theme we ship, and you can download the finished theme free at the end to follow along or use as your starter.

Why Tailwind CSS for WordPress Themes?
Tailwind is a utility-first CSS framework: you style elements by composing small classes (flex, py-20, text-ink) directly in your templates, and a build step generates a stylesheet containing only the classes you actually used. For WordPress theme developers that means:
- A tiny stylesheet. Our finished theme ships 27 KB of minified CSS – most hand-written theme stylesheets pass 150 KB and keep growing. Less CSS means faster First Contentful Paint and better Core Web Vitals, which Google rewards.
- The design lives in your templates. When a template and its styles are the same file, nothing gets orphaned. Delete a template, its styles vanish from the next build automatically.
- A design system for free. Colors, fonts and spacing live in
tailwind.config.js, so every template stays consistent without a style guide document nobody reads.
What You’ll Need
- A local WordPress install (LocalWP, DevKinsta, or
wp-envall work) - Node.js 18+ with pnpm or npm
- Any code editor
Step 1: Create the Theme Folder and Required Files
A WordPress theme needs exactly two files to exist: style.css with a comment header, and index.php. Create your folder inside wp-content/themes/:
cd wp-content/themes mkdir business-tailwind && cd business-tailwind
Here’s the full structure we’re going to end up with – worth creating the folders now:

style.css carries the theme header WordPress reads for the Appearance → Themes screen. The actual design will live in a compiled Tailwind file, so this stays almost empty:
/*
Theme Name: Business Tailwind
Author: Bytes Brothers
Description: A multipurpose corporate WordPress theme built with Tailwind CSS.
Version: 1.0.0
Requires PHP: 7.4
License: GNU General Public License v2 or later
Text Domain: business-tailwind
*/
Add an empty index.php for now, and your theme already appears in the admin. Activate it – we’ll build it up live.
Step 2: Set Up Tailwind Inside the Theme
Tailwind runs as a dev dependency inside the theme folder. Install it (npm users: swap pnpm add for npm install):
pnpm add -D tailwindcss postcss autoprefixer npx tailwindcss init -p

The single most important line in a WordPress + Tailwind setup is the content array: point it at your PHP templates, so Tailwind finds every class you use in them:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./*.php',
'./includes/**/*.php',
'./loops/**/*.php',
'./assets/js/*.js',
],
theme: {
extend: {
colors: {
primary: { DEFAULT: '#ef4135', 50: '#fef2f2', /* … */ 600: '#dc2a1e' },
ink: { DEFAULT: '#101828', soft: '#475467', mute: '#98a2b3' },
},
fontFamily: {
sans: ['Poppins', 'ui-sans-serif', 'system-ui', 'sans-serif'],
slab: ['"Roboto Slab"', 'ui-serif', 'serif'],
},
},
},
}
Everything in theme.extend is your design system: one primary brand palette, an ink neutral scale for text, and two font families.
Create src/input.css with the three Tailwind directives, plus a small @layer components block for patterns you’ll reuse in many templates (buttons, cards, section titles). Keep this layer small – utilities in the markup are the default; components are the exception:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.container-site {
@apply mx-auto w-full max-w-site px-4 sm:px-6 lg:px-8;
}
.btn-primary {
@apply inline-flex items-center justify-center gap-2 rounded-md
bg-primary px-6 py-3 text-sm font-medium uppercase
tracking-wider text-white transition-colors hover:bg-primary-600;
}
.section-title {
@apply mb-10 text-3xl font-semibold text-ink sm:text-4xl;
}
.card {
@apply rounded-lg bg-white shadow-sm ring-1 ring-slate-100
transition-shadow duration-300 hover:shadow-lg;
}
}
Wire up the build scripts in package.json:
quot;scripts": {
"dev": "tailwindcss -i ./src/input.css -o ./assets/css/business.css --watch",
"build": "tailwindcss -i ./src/input.css -o ./assets/css/business.min.css --minify"
}
Run pnpm run dev in a terminal and leave it running – every template you write from here on rebuilds the stylesheet instantly.

Step 3: Enqueue the Stylesheet the WordPress Way
Never hardcode a <link> tag – enqueue. We keep enqueues in their own include so functions.php stays a clean bootstrap. Create includes/bt-styles-scripts.php:
add_action('wp_enqueue_scripts', 'bt_styles');
function bt_styles() {
wp_enqueue_style('bt-fonts',
'https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Roboto+Slab:wght@300;400;700&display=swap',
array(), null);
// Compiled Tailwind stylesheet - built from src/input.css.
wp_enqueue_style('bt-tailwind',
BT_ASSETS_DIRECTORY_URI . 'css/business.min.css', array(), BT_THEME_VERSION);
wp_enqueue_style('bt-style', get_stylesheet_uri(), array('bt-tailwind'), BT_THEME_VERSION);
}
Use a version constant, not time() – you want browsers to cache the file, and you bump the constant when you rebuild.
Then functions.php bootstraps everything – includes, theme supports, menus and image sizes:
define('BT_THEME_VERSION', '1.0.0');
$bt_inc = get_template_directory() . '/includes/';
require_once $bt_inc . 'constants.php'; // TEMPLATE_DIRECTORY_URI etc.
require_once $bt_inc . 'bt-styles-scripts.php'; // enqueues
require_once $bt_inc . 'template-functions.php'; // banner + brand helpers
add_action('after_setup_theme', 'bt_theme_setup');
function bt_theme_setup() {
add_theme_support('title-tag');
add_theme_support('post-thumbnails');
add_theme_support('custom-logo');
add_theme_support('html5', array('search-form', 'comment-form', 'comment-list'));
register_nav_menus(array(
'primary' => __('Primary Menu (header)', 'business-tailwind'),
'footer_quick' => __('Footer - Quick Links', 'business-tailwind'),
));
add_image_size('card-thumb', 800, 450, true); // blog cards (16:9)
add_image_size('banner-wide', 1600, 700, true); // page banners
}
Step 4: Build header.php and footer.php
Every page template starts with get_header() and ends with get_footer(). The header is where Tailwind shines – a sticky, blurred white bar is a handful of utilities:
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<?php wp_head(); ?>
</head>
<body <?php body_class('bg-white font-sans text-ink-soft antialiased'); ?>>
<?php wp_body_open(); ?>
<header id="site-header" class="sticky top-0 z-50 bg-white/95 backdrop-blur transition-shadow">
<div class="container-site">
<nav class="flex flex-wrap items-center justify-between py-4 lg:py-0">
<?php echo bt_brand('header'); ?>
<button id="nav-toggle" class="rounded-md p-2 text-ink ring-1 ring-slate-200 lg:hidden"
aria-controls="nav-menu" aria-expanded="false">☰</button>
<div id="nav-menu" class="hidden w-full pt-4 lg:block lg:w-auto lg:pt-0">
<?php wp_nav_menu(array(
'theme_location' => 'primary',
'menu_class' => 'main-nav',
'container' => false,
)); ?>
</div>
</nav>
</div>
</header>
<main>
The navigation is a standard wp_nav_menu() – editors manage it from Appearance → Menus. The trick is styling WordPress’s own menu classes once, in src/input.css, so dropdowns and active states work for whatever menu the editor builds:
.main-nav { @apply flex flex-col lg:flex-row lg:items-center; }
.main-nav > li > a { @apply nav-link; }
.main-nav > li.current-menu-item > a { @apply text-primary; }
.main-nav ul.sub-menu {
@apply hidden lg:invisible lg:absolute lg:top-full lg:z-50 lg:block lg:w-52
lg:rounded-b-md lg:bg-white lg:py-2 lg:opacity-0 lg:shadow-lg
lg:ring-1 lg:ring-slate-100 lg:transition-opacity;
}
.main-nav li:hover > ul.sub-menu,
.main-nav li:focus-within > ul.sub-menu { @apply block lg:visible lg:opacity-100; }
footer.php closes </main>, renders the widget columns (a footer_quick menu location plus a recent-posts query) and – critically – calls wp_footer() before </body>.
Step 5: The Template Hierarchy
WordPress picks templates by name. Our theme covers the hierarchy like this:
| Template | Renders |
|---|---|
index.php |
Blog index (and final fallback) |
archive.php |
Category, tag, author, date archives |
single.php + comments.php |
Single posts |
page.php |
Static pages |
front.tpl.php |
Homepage (assignable page template) |
coming-soon.tpl.php, 404.tpl.php |
Special pages |
Repeated markup goes into partials. Our blog card lives in loops/blog-card.php and gets reused by the blog index, archives and the homepage:
<article <?php post_class('card overflow-hidden'); ?>>
<a href="<?php the_permalink(); ?>" class="group block overflow-hidden">
<?php the_post_thumbnail('card-thumb', array(
'class' => 'aspect-[16/8] w-full object-cover transition-transform
duration-500 group-hover:scale-105',
'loading' => 'lazy',
)); ?>
</a>
<div class="p-7">
<h2 class="mb-3 text-xl font-semibold leading-snug">
<a href="<?php the_permalink(); ?>" class="hover:text-primary"><?php the_title(); ?></a>
</h2>
<p class="mb-4 text-sm leading-relaxed">
<?php echo esc_html(wp_trim_words(get_the_excerpt(), 24)); ?></p>
</div>
</article>
index.php is then just a banner, a grid, and the loop:
get_header();
echo bt_render_banner(array('title' => 'Blog', 'page_name' => 'Blog'));
?>
<section class="py-20 lg:py-24">
<div class="container-site">
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
<?php while (have_posts()) : the_post();
get_template_part('loops/blog-card');
endwhile; ?>
</div>
<?php the_posts_pagination(array('mid_size' => 2)); ?>
</div>
</section>
<?php get_footer(); ?>
Here’s that template rendering real posts, and a single post with threaded comments:


Step 6: Page Templates for the Homepage and Specials
For pages that need bespoke layouts, WordPress page templates are the cleanest tool: a file with a Template Name header that editors assign from the page editor. Our homepage is front.tpl.php:
<?php
/**
* Template Name: Front Page
* Template Post Type: page
*/
get_header(); ?>
<section class="relative flex min-h-[80vh] items-center justify-center bg-ink"
style="background-image:linear-gradient(rgba(16,24,40,.62),rgba(16,24,40,.62)),
url('<?php echo esc_url(BT_ASSETS_DIRECTORY_URI . 'images/banner.jpg'); ?>');
background-size:cover">
<div class="container-site py-24 text-center">
<h1 class="mx-auto max-w-3xl font-slab text-4xl text-white sm:text-5xl lg:text-6xl">
<?php echo esc_html(get_bloginfo('name')); ?> -
<strong class="font-bold"><?php echo esc_html(get_bloginfo('description')); ?></strong>
</h1>
<a href="<?php echo esc_url(home_url('/services/')); ?>" class="btn-primary mt-8">
<?php esc_html_e('Explore Services', 'business-tailwind'); ?></a>
</div>
</section>
Below the hero it renders the page’s own content (so editors control the intro copy), a services grid, animated counters, the latest posts via the same loops/blog-card.php partial, and a partner-logo carousel (container-bounded, arrow-controlled, with logos that reveal their true color on hover – the list is overridable via the bt_partner_logos filter). Assign it to a page, point Settings → Reading at that page, done. The same pattern gives you coming-soon.tpl.php and the 404 template.
Step 7: A Few Lines of Vanilla JavaScript
No jQuery needed. assets/js/business.js (~170 lines) handles the mobile nav toggle, counters and scroll-reveals with IntersectionObserver, the partner-logo carousel (gentle auto-scroll, seamless loop, prev/next arrows, pauses on hover) and a back-to-top button – enqueued in the footer with wp_enqueue_script(..., true). The whole file is in the download.
The theme stays sharp at every width, straight out of these templates:

Step 8: Ship It
Before zipping the theme, run the production build:
pnpm run build
Two workflow rules keep this painless in production:
- Commit the compiled CSS. The theme installs and runs on any host with zero Node – the toolchain is only needed when you change the design.
- Bump
BT_THEME_VERSIONwhenever you rebuild, so caches refresh exactly once.
The final stylesheet: 27 KB minified, ~7 KB over the wire with gzip. That plus semantic markup – one h1 per view, landmark elements, breadcrumbs, loading="lazy" images – is the SEO foundation most themes bolt on afterwards, built in from the first commit.
Common Questions
Tailwind v3 or v4?
This guide uses Tailwind v3.4 and the classic tailwind.config.js workflow, which is what most WordPress toolchains and tutorials assume. Nothing about the theme structure changes with v4 – only the config format.
Do I need Node on my server?
No. The compiled CSS ships with the theme. Node/pnpm run only on your machine, at design time.
Should I use @apply everywhere so my templates look cleaner?
Resist it. @apply everything and you’ve reinvented a classic stylesheet with extra steps. We keep exactly one small components layer (buttons, cards, section titles, the WP menu bridge) – everything else is utilities in the template.
Does this work with Gutenberg?
It runs as a lean classic theme and dequeues block-editor CSS on the front end. Prefer blocks? Remove four wp_dequeue_style lines in functions.php and style .wp-block-* in your src/input.css.
How do plugins’ styles fit in?
Tailwind’s preflight resets only what your templates use; plugin output keeps its own CSS. For plugins you want restyled (forms, WooCommerce), target their classes inside @layer components.
Download the Finished Theme
The complete theme from this guide is free:
- ⬇ Business Tailwind – WordPress theme (zip) – install via Appearance → Themes → Upload, activate, assign the Front Page template.
It seeds a starter menu on activation and includes the full Tailwind source (src/input.css, tailwind.config.js) so you can rebrand it by editing one config file.
Want It Built for You?
Custom Tailwind WordPress themes are our bread and butter – designed, built and tuned for Core Web Vitals. If you’d rather ship next week than learn a toolchain:
Book a free consultation with Bytes Brothers →
Interested in a Tailwind-powered WordPress theme?
We’ve built enough of them to know what works and what creates problems down the line. If you want a WordPress site that’s fast, easy to maintain, and doesn’t need a CSS archaeologist to update – a Tailwind theme is the right call.
