divi-knowledge/LESSONS.md

9.6 KiB

Divi 5 + Claude Code: Lessons from First Build Session

Written 2026-05-07 by Stevenson, after building Kate Gathard's hero section as a working reproduction on demo-elysian.willcureton.com. For whoever (Hans, Penelope, the designer agent) inherits this work next.


1. The big realisation

Divi 5 is not Divi 4. It's a complete rewrite. Divi 4 shortcodes ([et_pb_section]...[et_pb_blurb...]) still load for backward compatibility, but Divi 5 silently drops most of their attributes when rendering. If you write Divi 4 shortcode attributes via the REST API, the body text usually shows up but colours, padding, alignment, animations, etc. don't. Don't use Divi 4 shortcodes. Write Divi 5 native blocks.

2. Divi 5 native block format

Pages are stored as post_content containing Gutenberg-style block comments:

<!-- wp:divi/section {ATTRS_JSON} -->
<!-- wp:divi/row {...} -->
<!-- wp:divi/column {...} -->
<!-- wp:divi/heading {...} /-->
<!-- wp:divi/text {...} /-->
<!-- /wp:divi/column -->
<!-- /wp:divi/row -->
<!-- /wp:divi/section -->

You write the same JSON the Visual Builder would write, programmatically. Update via WordPress REST API (POST /wp-json/wp/v2/pages/{id} with content body).

3. The per-breakpoint attribute wrapping

Every attribute value is wrapped three layers deep. Divi 5 stores per-breakpoint, per-state values everywhere:

{
  "attributeName": {
    "subPath": {
      "desktop": {
        "value": "..."   // ← the actual value lives here
      }
    }
  }
}

Common breakpoint keys: desktop, tablet, phone. Common state keys: value (default), hover, sticky.

Forgetting this wrapping is the #1 cause of WordPress 500 errors. The PHP renderer expects an array; if you pass a flat string, you get Argument #1 must be of type array, string given.

4. Module names — match the underlying directory

Module block names match the directory in /wp-content/themes/Divi/includes/builder-5/server/Packages/ModuleLibrary/. Hyphenated, not underscored:

Right Wrong
divi/contact-form divi/contact_form
divi/contact-field divi/contact_field
divi/signup (different module — for Mailchimp/AC integration)
divi/heading (use this for h1/h2/h3, not divi/text with a <h1> inside)
divi/text (paragraph blocks; uses content.innerContent, not title.innerContent)

Don't guess module names — list the directory first.

5. Common attribute paths that work

Verified in this build:

What you want Attribute path
Heading text content title.innerContent.desktop.value
Heading level (h1/h2/etc.) level.desktop.value
Heading colour title.decoration.font.font.desktop.value.color
Heading font family title.decoration.font.font.desktop.value.family
Heading font size title.decoration.font.font.desktop.value.size
Heading font weight title.decoration.font.font.desktop.value.weight (use string "300", "400", "700")
Heading text-align title.decoration.font.font.desktop.value.textAlign
Text body content content.innerContent.desktop.value (HTML string with <p> tags)
Text body alignment module.advanced.text.text.desktop.value.orientation (NOT module.decoration.text.text)
Text body font family module.decoration.bodyFont.body.font.desktop.value.family
Text body colour module.decoration.bodyFont.body.font.desktop.value.color
Section background video module.decoration.background.desktop.value.video.mp4
Section background image module.decoration.background.desktop.value.image.url
Module width module.advanced.sizing.width.desktop.value
Module max-width module.advanced.sizing.maxWidth.desktop.value
Module padding module.decoration.spacing.padding.desktop.value.{top,bottom,left,right}
Module border radius module.decoration.border.desktop.value.radius.{topLeft,topRight,bottomLeft,bottomRight,sync}
Module border style module.decoration.border.desktop.value.styles.all.{width,color,style}
Module animation module.decoration.animation.desktop.value.{style,direction,duration,delay,intensity,startingOpacity,speedCurve}
Free-form CSS css.desktop.value.freeForm (a string of raw CSS)

Decoration is a sibling of innerContent under each named element, not nested inside.

6. Free-Form CSS — the canonical escape hatch

When an attribute path doesn't exist (or you can't find it), use Free-Form CSS via css.desktop.value.freeForm. The string takes raw CSS. The keyword selector is replaced by the rendered module's specific class at runtime. Examples:

selector { max-width: 400px; margin: 0 auto; }
selector::before { content:""; position:absolute; inset:0; background:rgba(0,0,0,0.3); }
selector .et_pb_button:hover { background-color: rgba(255,255,255,0.35) !important; }

The official Elegant Themes pattern for an overlay over a video background is:

selector::before { content:""; display:block; position:absolute; top:0; left:0; width:100%; height:100%; background-color:rgba(0,0,0,0.5); z-index:1; }
selector .et_pb_row { z-index:2; }

Free-Form CSS is what makes Divi 5 fully programmable. Most "I can't find the attribute path" problems collapse into this.

7. CSS — render the page first, find the real class names

Divi 5's rendered CSS classes are not always what you'd guess from the block attribute paths. Always check the actual HTML before writing CSS.

Examples from this build:

Looks like Actual class
Field input .input (not .et_pb_contact_field — that's the wrapper div)
Button wrapper .et_pb_button_wrapper
Contact form bottom container .et_contact_bottom_container
Section overlay target .et_pb_row (z-index:2 lifts content above the ::before overlay)

Workflow: curl the rendered page, find the element, read its actual classes, write CSS targeting those.

8. Hover state on buttons

Divi adds an arrow on button hover via ::after { content: "5"; opacity: 0 → 1; padding-shift }. To replace with a clean overlay-on-hover effect:

selector .et_pb_button::after,
selector .et_pb_button:hover::after,
selector .et_pb_button:focus::after {
  display:none !important; content:none !important; opacity:0 !important;
  visibility:hidden !important; width:0 !important; margin:0 !important; padding:0 !important;
}
selector .et_pb_button:hover {
  background-color: rgba(255,255,255,0.35) !important;
  padding-left:20px !important; padding-right:20px !important;  /* prevent shift */
}

The padding-left/right override on :hover is needed because the default Divi hover shifts padding to make space for the (now-hidden) arrow.

9. CSS specificity warnings

Divi inlines a lot of styles. To win specificity battles, prefix with body or use !important. From CJ Simon's Divi5-ToolKit:

body .et_pb_button { background-color:#000 !important; }   /* Standard override */
body .et_pb_button.my-btn { ... }                          /* With custom class */

10. Workflow: pair-programming

What worked best: human in the Visual Builder UI, AI driving block JSON via REST API. Human directs verbally ("centre that, make it bigger, change the colour"), AI translates to attribute paths, edits, returns. Refresh, iterate.

When the AI gets stuck on an attribute path, the human can do it in the UI to expose the saved JSON, then the AI reads from there to learn the pattern. This is how I learned the divi/contact-form block structure — Will did it in the UI first, I read the JSON.

11. Tools to layer on Claude Code

  • CJ Simon's Divi5-ToolKit (free, MIT-ish on GitHub): comprehensive Divi 5 selector reference + CSS patterns + module knowledge. Cloned to /opt/wordpress-demo/plugins/Divi5-ToolKit/. Registered globally in /root/.claude/settings.json. Slash commands available next session boot.
  • Respira (€95/year, paid plugin live on demo-elysian): MCP server with 172 tools, duplicate-before-edit safety, builder-aware module operations across 11 builders. We installed it but mostly used the underlying REST API directly during this session.
  • wordpress-rest-api skill at /mnt/skills/user/wordpress-rest-api/: generic CRUD on posts/pages/products via WordPress REST API + Application Password.

12. What I haven't yet figured out

  • The full attribute schema for every module — only verified the ones used in this build. The PHP *PresetAttrsMap.php files are partial (preset-only); the canonical full schema lives in the React/JS Visual Builder bundle.
  • The Divi 5 design preset / variable system — touched briefly via attribute paths but didn't drive any preset linkage.
  • Composable Settings (Divi 5.2+) — toggle-any-design-option-on-any-sub-element. Probably the cleanest path for fine control once the core schema is mapped.

13. The empirical loop

For any attribute you don't know:

  1. Do it in the Visual Builder UI yourself
  2. Read the saved post_content via GET /wp-json/wp/v2/pages/{id}?context=edit
  3. Note the JSON structure
  4. Reproduce that structure in subsequent builds

Faster than reading source code in most cases. The Divi developer docs are reliable for capability surface but rarely show the exact attribute names.


Closing

The architecture works. The pair-programming pattern works. Free-Form CSS is the universal escape hatch. The 101 modules will mostly behave the same way once you internalise the per-breakpoint wrapping.

Don't fight the schema — read what the Visual Builder writes and copy that. Use Divi5-ToolKit for selector reference. Use Respira when safe-edit matters. Build empirically and iterate.

— Stevenson