Partials
A partial is the unit of markup in Wonderpress: a PHP class, a view template, and a JSON manifest. Buttons, sections, quotes, heroes. Partials render properties into HTML, compose inside other partials, and optionally surface in the editor as blocks.
Creating a partial
wonderpress partial createThat runs the wizard: it asks for a name, walks you through properties, and offers the opt-ins. Any flags you pass pre-answer its questions rather than being asked again, so the fully headless version of the same partial is:
wonderpress partial create --name Testimonial --prop quote:string:requiredEither way, it scaffolds four things:
- A PHP class at
src/partials/class-testimonial.php, extendingAbstract_Partialfrom wonderpress-core, with a generated$_propertiesarray. - A view template at
partials/testimonial.php, where declared properties arrive as plain local variables ($quote). - A manifest at
.wonderpress/manifest/partials/testimonial.jsonin the theme, the source of truth for the component. - A style stub at
static/src/scss/components/_testimonial.scss, created by delegation to Static Kit, token-only, ready for real styles.
Useful variations:
# expose it in the editor toowonderpress partial create --name Hero --block --js# ACF-compatible, with a repeaterwonderpress partial create --name Testimonials --acf \ --prop items:repeater --sub items:quote:string:required# from a JSON spec instead of flags (good for agents)wonderpress partial create --json @spec.json
--js also scaffolds a JS behavior class (delegated to Static Kit). Most partials have no behavior, so it is opt-in. --block adds a Gutenberg wrapper; --acf marks the manifest ACF-compatible. The combinations --block --no-manifest and --acf --no-manifest are refused, since both features depend on the manifest existing.
Rendering a partial
From a page template or another partial:
( new \Wonderpress\Partials\Testimonial( array( 'quote' => 'It works.' ) ) )->render();On an ACF-hydrated page template, pass the field data through wonder_partial_props():
( new \Wonderpress\Partials\Hero( wonder_partial_props( 'hero' ) ) )->render();Views read flat properties and never branch on where data came from. Whether a value arrived from block attributes or from ACF, the same $quote shows up in the view. Required-property validation happens in Abstract_Partial::render().
Manifest-first: where to edit what
Every partial is defined by its manifest, a JSON file at .wonderpress/manifest/partials/<slug>.json in the theme. Field definitions live there, and the PHP class $_properties and block.json are generated from it. The full story (anatomy, who reads them, syncing, drift) is on the Manifests page; the working rules are:
| Safe to edit by hand | Generated: edit the manifest, then sync |
|---|---|
partials/*.php view templates | src/partials/class-*.php ($_properties) |
SCSS and JS stubs under static/ | blocks/<slug>/block.json attributes |
| Helpers, services, custom PHP outside generated classes |
Do not add fields by editing $_properties or block.json by hand. They will drift, and partial sync will overwrite them.
The workflow:
# 1. edit .wonderpress/manifest/partials/hero.json# 2. regenerate derived fileswonderpress partial sync Hero# 3. reload the block editor; re-save a page if ACF groups changed shape
Preview without writing with --dry-run, and guard CI with wonderpress partial check-drift --all, which exits non-zero when generated files no longer match their manifests. wonderpress lint runs the same check.
Partial manifest reference
The complete key-by-key reference for hand-editing a partial manifest. Everything here is enforced twice: by the CLI’s validator, and by the generated JSON Schema your editor reads through the manifest’s $schema key, so completion and inline errors match what the commands accept. Manifests are strict JSON: no comments, no trailing commas.
Top-level keys
Required keys: name, slug. No other keys are allowed at the top level.
| Key | Type | Description |
|---|---|---|
$schema | string | Points the editor at ../schema/partial.schema.json. Leave as-is; the CLI stamps it. |
name | string | PHP class name, capitalized snake case (Headline_Stack). Required. |
slug | string | Kebab-case id ([a-z0-9-]+). Matches the manifest filename. Required. |
block | string | Gutenberg block name (namespace/slug), present when the partial is exposed as a block. |
acf_compatible | boolean | Whether the properties register as ACF fields where the partial is composed. |
core_primitive | boolean | True for a partial shipped by wonderpress-core, such as Link. |
properties | array | The fields this partial accepts. Property objects below. |
artifacts | object | Files written for this partial, relative to the theme. class is required; view, block, render, style, script appear when they exist. Managed by the CLI; you rarely touch this. |
Property objects
Used in a partial manifest’s properties, in a repeater’s sub-properties, and in a page-template manifest’s fields rows. Required keys: name, type.
| Key | Type | Description |
|---|---|---|
name | string | Field name. This is the ACF field name and the key on the PHP props array. Required. |
type | enum | One of string, boolean, email, select, image, link, post_object, repeater, partial. Required. Value shapes per type are in Property types. |
required | boolean | Whether the editor requires a value. Not allowed on boolean, link, or partial (ACF treats a required true/false as “must be checked”, and copies a required group onto every sub-field). |
label | string | Editor label. Defaults to a title derived from name. |
description | string | Help text, shown as ACF instructions. |
default | any | Default value. For a select, use one of the choice keys. |
choices | object | select only. Map of stored value to editor label. A select must have choices here or under acf. |
when | array | Conditional display. See below. |
post_type | string or array | post_object only. One post type or a list. |
format | enum | string only. text or textarea. |
rows | integer | string only, with format: "textarea". Row count. |
partial | string | partial only, and required there. Slug of the partial this field embeds. |
properties | array | repeater only, and required there. One level of sub-fields; a sub-field cannot itself be a repeater. Allowed sub-types: boolean, email, image, link, partial, select, string. |
acf | object | ACF-only display options. See passthrough keys below. |
The type-scoped keys are enforced: format, rows, post_type, partial, and properties are each rejected on any type other than their own. post_type, format, and rows live on the property root because both ACF and the block editor read them; do not nest them under acf.
when conditionals
Show a field only when sibling fields match. The outer list is OR; each inner list is AND. field names a sibling property in the same group.
"when": [ [ { "field": "style", "operator": "==", "value": "card" }, { "field": "show_image", "operator": "==", "value": "1" } ]]
Operators: ==, !=, >, <, >=, <=, contains, !contains, pattern, !pattern. ACF booleans compare as the string "1". Conditionals are authoring-only: they hide editor fields, and saved values are retained. In repeater rows, when evaluates against the row.
acf passthrough keys
ACF-only UI options merged onto the compiled field. Allowed keys: choices, default_value, ui, return_format, preview_size, library, layout, wrapper (object with width, class, id), allow_null, multiple, placeholder, min, max, step. Nothing else passes through, and block wire formats stay fixed per type regardless of what you set here.
After any hand-edit to properties, run wonderpress partial sync <Name> so the generated class and block.json catch up.
Listing, extending, removing
wonderpress partial listLists every partial (name, slug, wrapping block if any), indexed from the manifests. Blocks the CLI did not write are flagged (no manifest): WordPress registers those too, and the CLI cannot manage or remove them.
wonderpress partial add-js HeroScaffolds a behavior class onto an existing partial that has a view and no script yet. The file is not auto-imported anywhere; wire it into the page JS entry that renders the partial. Auto-wiring would pull the component into pages that never use it and break per-page tree-shaking, so the import is a deliberate act.
wonderpress partial remove Call_To_ActionRemoves the partial and every artifact its manifest recorded (class, view, style, behavior), then the manifest itself. Accepts a class name or a slug. If a block wraps the partial, removal is refused: run wonderpress block remove first, or pass --with-block.
Core primitives
wonderpress-core ships a small set of primitive partials, such as the rich Link (Wonderpress_Core\Partials\Link). Install a primitive’s manifest into your project when you want to reference it:
wonderpress partial install-manifest linkEmbed a primitive inside another partial with type: "partial" in the manifest (see Property types), and render it with:
wonder_render_partial_ref( 'link', $this->cta );Scope namespaces in CSS
By convention, the class prefix on a component communicates where it lives:
theme-<slug>is theme-level: reusable across pages. The natural default for a partial, since anAbstract_Partialis reusable by construction.<page-slug>-<slug>is page-level: defined in that page’s entry and used only there.
A page’s SCSS entry composes both: the theme-level partials it reuses and any page-level styles specific to it. This naming convention is separate from the project-wide block namespace set at init, which is about editor content rather than CSS.