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

code
wonderpress partial create

That 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:

code
wonderpress partial create --name Testimonial --prop quote:string:required

Either way, it scaffolds four things:

  1. A PHP class at src/partials/class-testimonial.php, extending Abstract_Partial from wonderpress-core, with a generated $_properties array.
  2. A view template at partials/testimonial.php, where declared properties arrive as plain local variables ($quote).
  3. A manifest at .wonderpress/manifest/partials/testimonial.json in the theme, the source of truth for the component.
  4. A style stub at static/src/scss/components/_testimonial.scss, created by delegation to Static Kit, token-only, ready for real styles.

Useful variations:

code
# 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:

code
( new \Wonderpress\Partials\Testimonial( array( 'quote' => 'It works.' ) ) )->render();

On an ACF-hydrated page template, pass the field data through wonder_partial_props():

code
( 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 handGenerated: edit the manifest, then sync
partials/*.php view templatessrc/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:

code
# 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.

KeyTypeDescription
$schemastringPoints the editor at ../schema/partial.schema.json. Leave as-is; the CLI stamps it.
namestringPHP class name, capitalized snake case (Headline_Stack). Required.
slugstringKebab-case id ([a-z0-9-]+). Matches the manifest filename. Required.
blockstringGutenberg block name (namespace/slug), present when the partial is exposed as a block.
acf_compatiblebooleanWhether the properties register as ACF fields where the partial is composed.
core_primitivebooleanTrue for a partial shipped by wonderpress-core, such as Link.
propertiesarrayThe fields this partial accepts. Property objects below.
artifactsobjectFiles 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.

KeyTypeDescription
namestringField name. This is the ACF field name and the key on the PHP props array. Required.
typeenumOne of string, boolean, email, select, image, link, post_object, repeater, partial. Required. Value shapes per type are in Property types.
requiredbooleanWhether 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).
labelstringEditor label. Defaults to a title derived from name.
descriptionstringHelp text, shown as ACF instructions.
defaultanyDefault value. For a select, use one of the choice keys.
choicesobjectselect only. Map of stored value to editor label. A select must have choices here or under acf.
whenarrayConditional display. See below.
post_typestring or arraypost_object only. One post type or a list.
formatenumstring only. text or textarea.
rowsintegerstring only, with format: "textarea". Row count.
partialstringpartial only, and required there. Slug of the partial this field embeds.
propertiesarrayrepeater 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.
acfobjectACF-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.

code
"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

code
wonderpress partial list

Lists 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.

code
wonderpress partial add-js Hero

Scaffolds 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.

code
wonderpress partial remove Call_To_Action

Removes 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:

code
wonderpress partial install-manifest link

Embed a primitive inside another partial with type: "partial" in the manifest (see Property types), and render it with:

code
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 an Abstract_Partial is 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.