191 lines
9.6 KiB
Markdown
191 lines
9.6 KiB
Markdown
# 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```css
|
|
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:
|
|
|
|
```css
|
|
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:
|
|
|
|
```css
|
|
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:
|
|
|
|
```css
|
|
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
|