Getting started
In this walkthrough you set up a block theme called my-theme and give it a first block: a teaser with a title and a text that content editors type directly into the editor canvas, and a switch in the inspector that highlights it. Along the way you see every file a consumer of the framework needs and why it is there.
Plugins and child themes with code of their own are set up the same way. Boot and modes, Plugins and Child themes describe what differs.
What you need
You need PHP 8.5, Composer and Node 24. The simplest way to get them is wp-dev-env: it runs them in a container together with a local WordPress, so you only need Docker on your machine.
The files of the theme
When you're done, the theme looks like this:
style.css, theme.json and templates/index.html are the same as in any block theme. composer.json and functions.php bring in the framework and start it. package.json and webpack.config.js build the JavaScript for the editor and the stylesheet. Your own code lives in src/; the stylesheet lives in assets/, the directory for everything that belongs to no PHP class.
composer.json
Composer installs the framework and tells it where your classes are. The framework comes from Packagist like any other package; the lock file pins the exact version. The PSR-4 namespace Acme\MyTheme\ maps to src/, and every class in there is something the framework can use – a block, a hook, a service.
A few entries deserve a closer look:
typetells the framework's build what kind of project this is.wordpress-themecovers themes and child themes,wordpress-pluginplugins.- The two scripts give you short commands for the framework's tools:
composer build-manifestchecks your code, andcomposer releasebuilds a ZIP for production. extra.wp-app-framework.namespacenames your blocks, REST routes and settings: the blockteaserbelow is registered asmy-theme/teaser. Choose it once; content refers to it (Concepts explains why it is separate from the directory name).extra.straussconfigures the prefixing of the release build. A release renames the framework and all dependencies into your own namespace (hereAcme\MyTheme\Vendor\), so they cannot collide with other themes or plugins. Release explains each value.
style.css
The theme header works as usual. Version: is the version the release build puts into the ZIP's file name:
functions.php
functions.php loads Composer's autoloader and boots the framework. A release ships the prefixed dependencies in vendor-prefixed/ instead of vendor/, so the file picks whichever exists:
That is the only call into the framework you write by hand. Everything else – blocks, hooks, routes – the framework finds through the attributes on your classes.
package.json and webpack.config.js
The block editor needs JavaScript: the framework's editor runtime, which makes your blocks editable, and any editor scripts of your own. Blocks with front-end scripts need them built as well. The framework ships only the sources of the runtime. Your theme builds them together with its own scripts and its stylesheet, using @wordpress/scripts and a webpack helper from the framework (see Editor).
The helper needs the slug of the theme, which is its directory name:
assets/styles.css
Blocks are styled with Tailwind CSS. If the theme has an assets/styles.css, the build compiles it with Tailwind, and the framework loads it on the front end and in the editor canvas. The stylesheet maps Tailwind's tokens to the values of theme.json. Here bg-primary uses the color primary, which theme.json defines:
Styles explains each line.
A first block
A block consists of three parts: a class that describes the block and renders it, a class that holds its attributes, and a Twig template with the markup. They sit next to each other in src/Block/Teaser/.
The block class carries #[Block] with the block's name, title, category and icon. Its constructor asks for the TemplateRenderer, which the framework provides, and render() passes the attributes to the template:
The attribute class lists what content editors can set. Each constructor parameter becomes a block attribute. #[PlainText] and #[RichText] mark text that is edited directly in the canvas; the plain bool gets a toggle in the block inspector automatically.
The template renders the markup. block_wrapper_attributes() goes on the root element and adds the classes WordPress needs. field('title') marks the element that shows the title, so that the editor can make it editable; on the front end it prints nothing.
The template switches between two complete class names instead of building one from the attribute. That is deliberate: Tailwind finds class names by reading the templates, so it only generates classes that appear there in full.
Blocks covers all of this in depth.
Build and run
Install the dependencies and build the assets. Run composer install first, because the file: dependency in package.json points into vendor/:
composer build-manifest is optional here, but useful: it checks your attributes and services and reports problems with class and parameter names.
Now activate the theme in WordPress and add the teaser to a page. You don't need to build anything else while you develop. The framework builds its manifest itself and caches it in wp-content/cache/my-theme/ (see Boot and modes), and npm run start rebuilds the editor scripts and the stylesheet whenever you change them.
When you are ready to ship the theme, Release shows how to build a ZIP that runs in any WordPress installation.