npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@i.sangam/create-wp-theme

v1.0.2

Published

Scaffold a WordPress FSE theme with Vite + Tailwind v4 + Biome

Readme

create-wp-theme

A CLI scaffolding tool that generates production-ready WordPress Full Site Editing (FSE) themes with a modern frontend toolchain.

Combines Vite's speed, Tailwind CSS v4's simplicity, and Biome's all-in-one linting into a single zero-config starter.

Why this exists

Existing WordPress + Tailwind starters either lack FSE support, ship with outdated tooling, or require manual configuration to get hot reload working. This tool generates a theme that:

  • Works with the WordPress Site Editor out of the box (theme.json, block templates, template parts)
  • Uses Vite for instant hot module replacement — no page refreshes during CSS/JS development
  • Automatically switches between dev server and production builds — zero manual file edits to toggle environments
  • Produces deploy-ready folders and zips that contain only the files your server needs
  • Uses class-based PHP with a clean singleton pattern instead of a monolithic functions.php
  • Always installs the latest versions of every dependency

Quick start

With Bun (recommended):

bunx @i.sangam/create-wp-theme

With npm:

npx @i.sangam/create-wp-theme

Both work identically. Bun is recommended for faster installs and builds, but Node.js 18+ works fine too.

Interactive setup

The CLI walks you through each option one at a time. Only the theme name is required — everything else has smart defaults you can accept by pressing Enter.

If you leave a required field empty, the cursor stays in place and shows an inline error — no disruptive re-prompts.

URLs are auto-prefixed with https:// if you omit the protocol (e.g. typing example.com becomes https://example.com).

  ┌  create-wp-theme
  │  WordPress FSE  •  Vite  •  Tailwind v4  •  Biome
  │
  ◆  Theme name *
  │  e.g. Flavor starter, flavor starter
  │  ❯ Flavor starter
  │
  ◆  Theme slug
  │  e.g. flavor_starter → folder: /flavor_starter, text-domain: flavor-starter
  │  press Enter for flavor_starter
  │  ❯
  │
  ◆  Function prefix
  │  e.g. flavor → Flavor_Theme, FLAVOR_THEME_DIR
  │  press Enter for flavor
  │  ❯
  │
  ◆  Theme URL
  │  e.g. example.com → https:// added automatically
  │  ❯
  │
  ◆  Author
  │  e.g. John Doe, Starter Inc.
  │  ❯
  │
  ◆  Author URL
  │  e.g. johndoe.com → https:// added automatically
  │  ❯
  │
  ◆  Description
  │  e.g. A minimal starter theme for client projects
  │  press Enter for Flavor starter — FSE WordPress theme
  │  ❯
  │
  ◆  Destination
  │  e.g. ./wp-content/themes → creates ./flavor_starter/ inside it
  │  press Enter for .
  │  ❯
  │
  ◇  Summary
  │
  │  Theme         Flavor starter
  │  Directory     ./flavor_starter/
  │  Slug          flavor-starter
  │  Prefix        flavor → Flavor_Theme
  │  Constants     FLAVOR_THEME_DIR, FLAVOR_THEME_URI
  │  Text Domain   flavor-starter
  │
  ◆  Create this theme?
  │  Y/n ❯ Yes
  │
  ◇  Scaffolding
  │
  │  ✓ Created directories
  │  ✓ Written theme files
  │  ✓ Dependencies installed
  │  ✓ Initial build complete
  │
  └  Done! Theme created at ./flavor_starter/

What each option does

| Option | Required | What it controls | |---|---|---| | Theme name | Yes | Display name in WordPress admin (Appearance → Themes) | | Theme slug | No | Folder name, translation text domain, and CSS handle prefix. Default: theme name in snake_case | | Function prefix | No | PHP class names and constants. E.g. flavor becomes Flavor_Theme, FLAVOR_THEME_DIR. Default: first word of slug | | Theme URL | No | Link shown in theme details. https:// added automatically if omitted | | Author | No | Author name shown in theme details | | Author URL | No | Author link in theme details. Defaults to Theme URL if provided | | Description | No | Short description shown in theme details. Default: {name} — FSE WordPress theme | | Destination | No | Parent directory where the theme folder is created. Default: current directory (.) |

What you get

The generated theme follows WordPress FSE conventions with a clean separation between source files (for development) and production files (for deployment):

my_theme/
│
│  ── WordPress theme files (production) ──────────────────
│
├── functions.php              Entry point — loads the class bootstrap
├── style.css                  Theme metadata (name, author, version, etc.)
├── theme.json                 FSE config: colors, fonts, layout, spacing
│
├── templates/                 FSE block templates
│   ├── index.html             Blog / archive listing
│   ├── page.html              Static pages
│   ├── single.html            Individual posts
│   └── 404.html               Not found page
│
├── parts/                     FSE block template parts
│   ├── header.html            Site header (navigation, logo)
│   └── footer.html            Site footer (copyright, links)
│
├── template-parts/            PHP template parts
│   └── common/                Shared partials across custom page templates
│
├── inc/                       PHP classes (clean architecture)
│   ├── class-{prefix}-theme.php    Singleton bootstrap, constants, dependency loader
│   ├── class-{prefix}-setup.php    Theme supports, nav menus, custom logo, i18n
│   └── class-{prefix}-assets.php   Asset enqueue with automatic HMR detection
│
├── assets/
│   ├── dist/                  Compiled CSS + JS (generated by Vite)
│   └── fonts/                 Self-hosted WOFF2 font files
│
│  ── Development files (not deployed) ────────────────────
│
├── src/
│   ├── main.css               Tailwind CSS v4 source + custom styles
│   └── main.js                JavaScript entry point (imports the CSS)
│
├── scripts/
│   └── bundle.js              Production bundler (creates deploy folder or zip)
│
├── vite.config.js             Vite config with Tailwind plugin + hot file management
├── biome.json                 Linter + formatter rules
├── package.json               Scripts and devDependencies
└── .gitignore                 Ignores node_modules, dist, hot, deploy output

Development workflow

1. Install dependencies

cd my_theme
bun install       # or: npm install

2. Start the dev server

bun run dev       # or: npm run dev

What happens behind the scenes:

  1. Vite starts and compiles your Tailwind CSS + JS
  2. A hot file is created in the theme root
  3. The PHP asset loader detects the hot file and loads CSS/JS from localhost:5173 instead of assets/dist/
  4. Vite watches for file changes and pushes updates to the browser via WebSocket

This means:

  • CSS changes (in src/main.css) appear instantly — no page refresh needed
  • JS changes (in src/main.js) are hot-swapped or trigger a minimal reload
  • PHP/HTML changes require a manual browser refresh (Vite only handles CSS/JS)
  • No manual file edits are needed to switch between dev and production mode

3. Edit your files

| What you want to change | Where to edit | |--------------------------------------|----------------------------------------------| | Styles (Tailwind utilities + custom) | src/main.css | | JavaScript | src/main.js | | Theme colors, fonts, spacing, layout | theme.json | | Block templates | templates/*.html | | Block header / footer | parts/header.html, parts/footer.html | | PHP template parts | template-parts/**/*.php | | Theme supports, menus, logo | inc/class-{prefix}-setup.php | | Asset loading behavior | inc/class-{prefix}-assets.php | | Linting / formatting rules | biome.json | | Vite build configuration | vite.config.js |

4. Stop the dev server

Press Ctrl+C. The hot file is automatically removed. The next time WordPress loads the page, it serves the compiled files from assets/dist/ instead. No configuration changes needed.

Production builds

Build + deploy folder

bun run build       # or: npm run build

This runs two steps automatically:

  1. Vite compiles Tailwind CSS + JS into assets/dist/
  2. Bundle script copies only the production files into a {theme_slug}/ folder inside the theme root

The output folder is ready to copy directly to your server's wp-content/themes/ directory:

my_theme/                ← your development theme
├── my_theme/            ← copy this entire folder to your server
│   ├── assets/          (includes compiled dist/ and fonts/)
│   ├── inc/             (PHP classes)
│   ├── parts/           (block template parts)
│   ├── templates/       (block templates)
│   ├── template-parts/  (PHP partials)
│   ├── functions.php
│   ├── style.css
│   └── theme.json

Build + deploy zip

bun run build:zip       # or: npm run build:zip

Same as above, but packages everything into {theme_slug}.zip. This is useful for:

  • Uploading via WordPress admin (Appearance → Themes → Add New → Upload Theme)
  • Distributing the theme to clients or other developers
  • Storing versioned releases

What's included vs excluded in production builds

| Included (deployed to server) | Excluded (development only) | |-------------------------------------------------------------|-----------------------------------------------| | assets/ (compiled CSS/JS in dist/ + fonts/) | node_modules/ (dependencies) | | inc/ (PHP classes) | src/ (Tailwind source CSS, JS source) | | parts/ (FSE block template parts) | scripts/ (build tooling) | | templates/ (FSE block templates) | package.json, bun.lock | | template-parts/ (PHP partials) | vite.config.js, biome.json | | functions.php, style.css, theme.json | .gitignore, README.md, hot |

All commands

| Command | What it does | |----------------------|-------------------------------------------------------------------| | bun run dev | Start Vite dev server with hot module replacement | | bun run build | Compile CSS/JS and create a deploy-ready theme folder | | bun run build:zip | Compile CSS/JS and create a deploy-ready theme zip | | bun run lint | Check JS/CSS files for errors and issues with Biome | | bun run lint:fix | Check and automatically fix issues with Biome | | bun run format | Format JS/CSS files with Biome |

All commands also work with npm run instead of bun run.

How auto HMR detection works

The theme uses a hot file mechanism (inspired by Laravel Vite) to automatically detect whether to load assets from the Vite dev server or from the compiled dist/ folder:

Developer runs `bun run dev`
       │
       ▼
Vite starts → creates empty `hot` file in theme root
       │
       ▼
WordPress loads a page → PHP checks: does `hot` file exist?
       │
   ┌───┴───┐
   YES     NO
   │       │
   ▼       ▼
Enqueue from        Enqueue from
localhost:5173      assets/dist/
(live dev server)   (compiled files)
       │
       ▼
Developer stops Vite (Ctrl+C)
       │
       ▼
Vite plugin removes `hot` file automatically
       │
       ▼
Next page load → `hot` file gone → serves from dist/

This is handled by two files working together:

  • vite.config.js — A custom Vite plugin creates the hot file when the dev server starts and removes it when the server stops
  • class-{prefix}-assets.php — The is_vite_dev() method checks for the hot file and routes asset loading accordingly

You never need to manually edit either file to switch between development and production modes.

PHP architecture

The theme avoids the common WordPress anti-pattern of a monolithic functions.php with hundreds of lines of unrelated code. Instead, it uses a class-based singleton pattern with separated concerns:

functions.php
  └── requires and boots {Prefix}_Theme (singleton)
        │
        ├── define_constants()
        │     ├── {PREFIX}_THEME_VERSION  → Theme version from style.css
        │     ├── {PREFIX}_THEME_DIR      → Absolute server path to theme root
        │     └── {PREFIX}_THEME_URI      → URL to theme root
        │
        ├── load_dependencies()
        │     ├── class-{prefix}-setup.php
        │     └── class-{prefix}-assets.php
        │
        └── register_hooks()
              ├── {Prefix}_Setup::init()
              │     └── after_setup_theme → supports, menus, logo, i18n
              │
              └── {Prefix}_Assets::init()
                    ├── wp_enqueue_scripts → auto HMR or production assets
                    └── script_loader_tag  → adds type="module" for Vite

Adding new functionality

To add a new feature (e.g. custom post types, widgets, REST API endpoints):

  1. Create a new class file: inc/class-{prefix}-{feature}.php
  2. Follow the same pattern — a final class with a public static function init() that registers WordPress hooks
  3. Require it in class-{prefix}-theme.php's load_dependencies() method
  4. Call {Prefix}_{Feature}::init() in the register_hooks() method

Example:

// inc/class-my-custom-posts.php
final class My_Custom_Posts {
    public static function init(): void {
        add_action( 'init', [ __CLASS__, 'register' ] );
    }

    public static function register(): void {
        register_post_type( 'portfolio', [ /* ... */ ] );
    }
}

Tailwind CSS v4

The theme uses Tailwind CSS v4's CSS-first configuration. There is no tailwind.config.js file. All customization happens directly in src/main.css using the @theme directive:

@import "tailwindcss";

@theme {
  --font-heading: "Inter", sans-serif;
  --font-body: "Noto Sans JP", sans-serif;
  --color-primary: #0073aa;
  --color-secondary: #23282d;
}

/* Custom component styles */
.btn-primary {
  @apply bg-primary text-white px-6 py-3 rounded-lg font-semibold
         hover:bg-primary/90 transition-colors;
}

Tailwind automatically scans all .php, .html, and .js files in the theme directory for class names and generates only the CSS you actually use.

For more details, see the Tailwind CSS v4 documentation.

Adding custom fonts

WordPress FSE themes load fonts through theme.json, not CSS @font-face rules. This ensures fonts work in both the front end and the block editor.

Step 1: Add font files

Place your .woff2 font files in assets/fonts/:

assets/fonts/
├── Inter-Regular.woff2
├── Inter-Bold.woff2
└── NotoSansJP-Variable.woff2

Step 2: Register in theme.json

Add a fontFace entry under settings.typography.fontFamilies:

{
  "fontFamily": "\"Inter\", sans-serif",
  "name": "Inter",
  "slug": "inter",
  "fontFace": [
    {
      "fontFamily": "Inter",
      "fontWeight": "400",
      "fontStyle": "normal",
      "fontDisplay": "swap",
      "src": ["file:./assets/fonts/Inter-Regular.woff2"]
    },
    {
      "fontFamily": "Inter",
      "fontWeight": "700",
      "fontStyle": "normal",
      "fontDisplay": "swap",
      "src": ["file:./assets/fonts/Inter-Bold.woff2"]
    }
  ]
}

For variable fonts, use a weight range:

{
  "fontWeight": "100 900",
  "src": ["file:./assets/fonts/NotoSansJP-Variable.woff2"]
}

Step 3: Make it available in Tailwind

Add the font to @theme in src/main.css:

@theme {
  --font-inter: "Inter", sans-serif;
}

Step 4: Use it

In templates: class="font-inter"

In theme.json styles:

"typography": {
  "fontFamily": "var(--wp--preset--font-family--inter)"
}

Adding custom page templates

The block editor handles most pages, but some pages need PHP logic — dynamic data, API calls, complex layouts with sliders or carousels, or integration with third-party services.

Step 1: Create the PHP template

Add a PHP file at the theme root following WordPress's template hierarchy:

| File name | When WordPress uses it | |---|---| | front-page.php | Static front page (Settings → Reading → "A static page") | | page-{slug}.php | Specific page by slug (e.g. page-about.php for /about/) | | single-{post-type}.php | Custom post type single view | | archive-{post-type}.php | Custom post type archive |

PHP templates at the root take precedence over block templates in templates/.

Step 2: Add template parts

Create partials in template-parts/ to keep your template organized:

template-parts/
├── common/           Shared across pages (header, footer)
└── frontpage/        Front page specific
    ├── hero.php
    ├── features.php
    └── testimonials.php

Use get_template_part() to include them:

<?php get_template_part( 'template-parts/frontpage/hero' ); ?>

Step 3: Add page-specific CSS/JS (if needed)

If the page needs its own styles or scripts beyond main.css/main.js:

  1. Create new source files (e.g. src/frontpage/frontpage.js + src/frontpage/frontpage.css)
  2. Add the entry point to vite.config.js:
rollupOptions: {
  input: {
    main: resolve(__dirname, 'src/main.js'),
    frontpage: resolve(__dirname, 'src/frontpage/frontpage.js'),  // new
  },
}
  1. Enqueue conditionally in class-{prefix}-assets.php:
if ( is_front_page() ) {
    wp_enqueue_style( '{slug}-frontpage', $dist_uri . '/frontpage.css', [], ... );
    wp_enqueue_script( '{slug}-frontpage', $dist_uri . '/frontpage.js', [], ... );
}

Requirements

| Requirement | Minimum version | Notes | |---|---|---| | Bun or Node.js | Bun 1+ / Node 18+ | Bun recommended for speed; both work | | PHP | 8.0+ | Required by WordPress and the generated theme | | WordPress | 6.4+ | Required for full FSE support (theme.json v3, block templates) |

Inspiration

This project was inspired by _tw and TailPress — both great tools for WordPress + Tailwind development. create-wp-theme builds on that foundation with a focus on WordPress FSE, modern tooling, and a streamlined developer experience.

Features at a glance

| Feature | | |---|---| | WordPress FSE (theme.json, block templates) | Yes | | Tailwind CSS v4 (CSS-first config) | Yes | | Vite (fast builds) | Yes | | Auto HMR (no manual dev/prod switching) | Yes | | Biome (linter + formatter) | Yes | | Deploy-ready builds (folder + zip) | Yes | | Class-based PHP architecture | Yes | | Interactive CLI scaffolding | Yes | | Always latest package versions | Yes | | Works with Bun and npm | Yes |

Acknowledgments

This tool was crafted collaboratively with Claude Code by Anthropic — from architecture decisions and code generation to documentation. Every pattern in the scaffolding (auto HMR detection, class-based PHP, production bundling, the interactive CLI itself) was iteratively designed and refined through that partnership to be as performant and efficient as possible.

License

MIT