Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .agents/agents/wordpress-theme-styling-auditor.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Find styling-system issues early, explain the root cause, and propose a minimal,
## Operating rules

- Read `AGENTS.md`, `.github/instructions/theme-json.instructions.md`, and `.github/instructions/design-token-policy.instructions.md` before making decisions.
- Read `.github/instructions/styling.instructions.md` before making Sass, CSS, or style-JSON recommendations.
- Read `.github/instructions/styling.instructions.md` and `.agents/skills/wp-block-style-audit/SKILL.md` before making Sass, CSS, or style-JSON recommendations. The `wp-block-style-audit` skill is the authoritative procedure for judging whether a CSS rule has a JSON equivalent — use it instead of ad hoc judgment calls.
- Audit first. Do not edit files until the user explicitly approves a proposed plan.
- Treat `src/scss/` as source and `assets/css/` as compiled output. Do not hand-edit compiled CSS when a source file exists.
- Treat `theme.json`, `styles/**/*.json`, `styles/presets/**/*.json`, and Sass or CSS as one styling system with clear ownership boundaries.
- `theme.json`, `styles/**/*.json`, and `styles/presets/**/*.json` are the styling system. Sass/CSS is a documented exception layer, not a peer — treat any Sass/CSS rule found during audit as a finding to justify or migrate, not as normal architecture.
- Use the existing repo scripts `npm run sync:breakpoints`, `npm run build:css`, and `npm run watch:css`. Do not propose Gulp, Grunt, CodeKit, Scout, LiveReload, PurgeCSS, or alternate pipelines unless the user explicitly asks to change the toolchain.
- Prefer existing semantic tokens, motion tokens, spacing presets, z-index tokens, and shadow tokens before introducing new values, however if it makes sense for clarity or maintainability, new tokens may be first proposed and then introduced.
- Use Sass mixins, maps, functions, and variables only when they remove verified duplication or materially improve clarity.
Expand All @@ -53,6 +53,14 @@ Find styling-system issues early, explain the root cause, and propose a minimal,

## Audit workflow

0. For every Sass/CSS rule under audit, first classify it: does a JSON equivalent
exist (`elements.*`, `blocks.*`, pseudo-state keys, block attributes)? Use
`.agents/skills/wp-block-style-audit/references/block-style-json-anatomy.md` as
the decision table. If yes, flag it as a migration candidate regardless of how
long it's been there. Rules with no JSON equivalent (e.g. `overflow`,
`max-width`, `width`, transitions/transforms) pass clean without a comment if
pre-existing; new or modified rules of this kind must carry a "JSON
limitation" comment going forward.
1. Gather the relevant Sass, CSS, `theme.json`, style JSON, and preset files.
2. Trace repeated values and determine whether they belong in Sass, runtime tokens, or both.
3. Identify duplication, dead utilities, token drift, invalid syntax, brittle selectors, and over-abstraction.
Expand Down
25 changes: 24 additions & 1 deletion .agents/skills/pattern-extractor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Use this skill when importing a Figma design into `patterns/` as a production-re

Whenever this skill creates or updates authored UI files such as pattern PHP, block style JSON, section style JSON, or theme CSS, it must also load and follow the `theme-color-token-enforcer` skill for those files. Treat `theme-color-token-enforcer` as the authoritative rule set for semantic colour token reuse, token creation, dark-mode parity, and contrast validation.

Whenever this skill decides that a pattern needs GSAP-powered motion, it must also load and follow the `wordpress-gsap` skill. Treat `wordpress-gsap` as the authoritative workflow for WordPress enqueueing, GSAP plugin registration, runtime scoping, and reduced-motion-safe effect architecture.
Whenever the JSON-First Gate below determines that a pattern genuinely needs GSAP-powered motion, this skill must also load and follow the `wordpress-gsap` skill. Treat `wordpress-gsap` as the authoritative workflow for WordPress enqueueing, GSAP plugin registration, runtime scoping, and reduced-motion-safe effect architecture. GSAP is never a default choice — it is only used once the gate confirms CSS cannot do the job.

Patterns should be grouped into taxonomy subfolders rather than stored flat at the top level. For example, a single featured card pattern should live at `patterns/cards/feature-card.php`.

Expand Down Expand Up @@ -129,6 +129,29 @@ Record state deltas explicitly:
- base -> focus-visible
- base -> active

### Phase 2.5a — JSON-First Gate (mandatory, before any Sass/CSS/GSAP/is-style work)

For every visual property identified in Phase 2, check in this order before writing
any authored CSS:

1. Does an existing `styles/**/*.json` partial already cover this? Check sibling
files in the same subfolder first — reuse before creating.
2. Can it be expressed as a new block-style/section-style JSON partial using
`elements.*`, `blocks.*`, pseudo-state keys, or block attributes? Use
`.agents/skills/wp-block-style-audit/references/block-style-json-anatomy.md` as
the JSON-vs-CSS decision table.
3. Only if neither applies, and the property has no JSON equivalent (transitions,
transform, `content:""`, aria-selectors, SVG fill, comma-separated selectors), is
authored Sass/CSS permitted — and it must carry a comment naming the specific
limitation, per `AGENTS.md` Theme-First Approach.
4. Do not register a new is-style variant for a single-use, one-off treatment with
no second option ever offered. If it's used in exactly one place, style it inline
on the pattern's block attributes instead of registering a global style-picker
entry.
5. GSAP is permitted only for JS-driven interaction that CSS transition/animation
structurally cannot achieve (e.g. scroll-triggered sequencing, cursor-tracked
spotlight effects) — not as a default for "this pattern has motion."

### Phase 2.5 — Context-Aware Block Selection

Infer the correct WordPress core block from design intent before writing markup.
Expand Down
124 changes: 124 additions & 0 deletions .agents/skills/wp-block-style-audit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
name: wp-block-style-audit
description: "Audit a WordPress block style JSON file (styles/*.json) and migrate CSS selector soup into proper theme.json-style JSON properties — elements, blocks, pseudo-states — leaving only CSS that has no JSON equivalent."
compatibility: "Targets WordPress 6.9+ (theme.json v3). Requires access to the theme's theme.json for preset token names."
Comment thread
brandonmarshal marked this conversation as resolved.
---

# WP Block Style JSON Audit

## When to use

Use this skill when a block style JSON file under `styles/` has a large `css` string doing work that belongs in structured JSON:

- Block-root colours/backgrounds set via `.wp-block-*` selectors instead of `blocks["block/name"].color`
- Descendant element colours (e.g. `.wp-block-button__link`, bare `a`) set via CSS instead of `elements.button`, `elements.link`, etc.
- Nested block styles (link colours on excerpt, terms, paragraph) done via `:where()` CSS instead of `blocks["core/post-excerpt"].elements.link`
- Third-party block colours (outermost/icon-block, etc.) done via CSS instead of `blocks["vendor/block"].color.text`
- Redundant CSS that duplicates what parent `elements` JSON already covers

## Inputs required

- Path to the block style JSON file to audit.
- Path to the theme's `theme.json` (to verify preset token names).
- Any sibling block style files that serve as reference patterns.

## Procedure

### 1) Read the file and catalogue every CSS rule

Parse the `css` string into individual selector groups. For each, note:
- What element or block it targets (the `.wp-block-*` class or aria selector)
- What CSS properties it sets
- Whether it involves a pseudo-state (`:hover`, `:focus-visible`, `:active`)
- Whether it depends on a parent-context trigger (e.g., `&:hover .child`)

Read:
- `references/block-style-json-anatomy.md`

### 2) Check sibling files for established patterns

Look at other files in the same `styles/` directory. Prefer patterns already in use over inventing new structure.

For example in the theme, `styles/sections/cards/card-link-row.json` is the canonical reference for card-style blocks.

### 3) For each CSS rule, decide: JSON or keep?

**Move to JSON if the property has a theme.json equivalent:**

| CSS rule | JSON destination |
|---|---|
| `& .wp-block-button__link { background-color }` | `elements.button.color.background` |
| `& .wp-block-button__link { color }` | `elements.button.color.text` |
| `& .wp-block-button__link:hover { ... }` | `elements.button[":hover"].color` |
| `& .wp-block-post-excerpt__excerpt a { color }` | `blocks["core/post-excerpt"].elements.link.color.text` |
| `& .wp-block-post-excerpt__excerpt a:hover { color }` | `blocks["core/post-excerpt"].elements.link[":hover"].color.text` |
| `& .wp-block-post-terms a { color }` | `blocks["core/post-terms"].elements.link.color.text` |
| `& p a { color }` | `blocks["core/paragraph"].elements.link.color.text` |
| `& .wp-block-outermost-icon-block .icon-container { color }` | `blocks["outermost/icon-block"].color.text` |
| Heading colors | `elements.heading.color.text` |
| `core/post-title` color, typography | `blocks["core/post-title"].color.text` / `.typography` |

**Keep in `css` if there is no JSON equivalent:**

- Layout: `overflow`, `max-width`, `width`, `aspect-ratio` (unless `dimensions` is supported)
- Transitions: `transition`, `transform`, `animation`
- Pseudo-states on the block itself: `&:hover { box-shadow }` (block-level hover isn't a JSON key)
- Parent-triggered child selectors: `&:hover .wp-block-cover img { transform }` — hover is on the parent, so must stay at root `css` level
- Aria-label selectors: `& :is([aria-label="..."])` — no JSON key maps to aria
- SVG fill: `& svg { fill: currentColor }` — no JSON equivalent
- `:focus-visible` on buttons — not a supported pseudo-state key, put in `elements.button.css`

**Remove entirely if redundant:**

- CSS that re-states a color already set by a parent `elements` JSON key (e.g., `& .wp-block-post-title a { color: contrast }` when `elements.link.color.text` is already `contrast`)

### 4) Check token references use the correct format

- In JSON values: use `var:preset|color|slug` (colon-pipe notation) for preset
tokens, or `var:custom|path|to|key` for custom tokens
- In `css` strings: use `var(--wp--preset--color--slug)` (double-dash notation)
for preset tokens, or `var(--wp--custom--path--to--key)` for custom tokens

Verify every preset token slug exists in its matching category-specific
`theme.json` collection (e.g. `settings.color.palette`,
`settings.typography.fontSizes`, `settings.spacing.spacingSizes`), and every
custom token path exists in `theme.json` `settings.custom`, before using it.

### 5) Split the remaining `css` by owner

Do **not** leave one monolithic `css` string. Distribute remaining CSS to the nearest owner:

- Root card behavior → `styles.css`
- Button-specific CSS → `styles.elements.button.css`
- Block-specific CSS → `styles.blocks["vendor/block"].css`

### 6) Write the refactored file

- Preserve all top-level metadata: `$schema`, `version`, `title`, `slug`, `blockTypes`, `description`
- Order within `styles`: `border`, `color`, `shadow`, `spacing`, `css` (if any), `elements`, `blocks`
- Order within `elements`: `heading`, `link`, `button`, `separator` (alphabetical within each group)
- Order within `blocks`: core blocks first (alphabetical by block name), then third-party blocks alphabetical

## Verification

After editing:

1. Validate JSON syntax (no trailing commas, balanced braces).
2. Open the block in the Site Editor — confirm the style appears and applies.
3. Hover the block — confirm hover transitions fire.
4. Check buttons: normal state, hover, and focus-visible outline.
5. Confirm link colors in excerpt, terms, and paragraph areas match before/after.
6. Confirm icon block color matches.

## Failure modes

- **Token slug not found**: CSS var resolves to empty. Verify slug against `theme.json` `settings.color.palette`.
- **`blocks` key not applying**: Confirm WordPress version supports per-block scoping in block style JSON (requires WP 6.6+).
- **`:hover` on `elements.button` not generating output**: Confirm the block style file targets a block type that supports button elements.
- **Parent hover not working after moving image CSS to `blocks["core/cover"]`**: Image overflow + hover zoom selectors depend on the parent card's `:hover` trigger and **must stay in root `styles.css`**, not in the cover block's own `css`.

## Escalation

- Theme.json schema reference: <https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/>
- Block style API: <https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/>
- Full Site Editing lessons: <https://fullsiteediting.com/lessons/custom-block-styles/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Block Style JSON Anatomy

Block style JSON files live under `styles/` (or subdirectories like `styles/sections/`). They share the theme.json v3 schema but only contain a subset of keys.

## Minimal structure

```json
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"title": "Human-readable name",
"slug": "css-class-suffix",
"blockTypes": ["core/group"],
"description": "Optional.",
"styles": { ... }
}
```

The block receives the class `is-style-<slug>` on its root element. All selectors in `css` use `&` to refer to that root.

## What `styles` supports

```
styles
├── border { radius, color, style, width }
├── color { background, text, gradient }
├── shadow "var:preset|shadow|slug"
├── spacing { blockGap, padding, margin }
├── typography { fontSize, fontFamily, fontWeight, ... }
├── dimensions { minHeight, aspectRatio }
├── css "raw CSS string, & = the block root"
├── elements
│ ├── heading { color, typography }
│ ├── link { color, typography, ":hover", ":focus", ":active", ":visited" }
│ ├── button { color, typography, spacing, border, ":hover", ":focus", ":active", "css" }
│ ├── separator { color }
│ ├── h1–h6 { typography }
│ └── caption { color, typography }
└── blocks
└── "core/post-excerpt"
├── color, typography, spacing, border, css
└── elements
└── link { color, ":hover", ... }
```

## Token formats

Use `var:preset|type|slug` in JSON values, `var(--wp--preset--type--slug)` in `css` strings.

```json
"color": { "text": "var:preset|color|accent-500" }
"css": "color: var(--wp--preset--color--accent-500);"
```

Custom tokens from `theme.json settings.custom` use `var:custom|path|to|key` / `var(--wp--custom--path--to--key)`.

## Pseudo-states

`link` and `button` elements support `:hover`, `:focus`, `:active`, `:visited` as sibling keys:

```json
"elements": {
"button": {
"color": { "background": "var:preset|color|primary-500" },
":hover": {
"color": { "background": "var:preset|color|accent-500" }
},
"css": "&:focus-visible{ outline: 2px solid ...; }"
}
}
```

## `blocks` sub-key

Allows scoped styles for child blocks. Supported in block style JSON from WP 6.6+.

```json
"blocks": {
"core/post-excerpt": {
"elements": {
"link": {
"color": { "text": "var:preset|color|accent-500" },
":hover": { "color": { "text": "var:preset|color|accent-600" } }
}
}
},
"outermost/icon-block": {
"color": { "text": "var:preset|color|accent-500" },
"css": "& svg { fill: currentColor; }"
}
}
```

## What must stay in `css`

| Situation | Reason |
|---|---|
| `overflow`, `max-width`, transitions | No JSON key maps to these properties |
| `&:hover { box-shadow }` on the block root | Block-level hover has no JSON pseudo-state |
| `&:hover .child-selector { transform }` | Hover trigger is the parent; can't live in the child block's `css` |
| `& :is([aria-label="..."])` | No JSON key for aria-label targeting |
| `& svg { fill: currentColor }` | No JSON key for SVG fill |
| `:focus-visible` on buttons | Not a supported pseudo-state key; use `elements.button.css` |
6 changes: 4 additions & 2 deletions .github/instructions/theme-json.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,10 @@ Reference them from authored CSS or style JSON with `var(--wp--custom--animation

- `styles/light.json` and `styles/dark.json` are registered style variations.
- Additional variations can be added as `styles/*.json`.
- `styles/blocks/` and `styles/sections/` are organisational conventions —
WordPress does not auto-consume these as global style variations.
- `styles/blocks/` and `styles/sections/` files carry the `blockTypes` + `slug`
schema, which WordPress 6.6+ auto-discovers recursively and registers as live,
editor-facing style-picker entries. Every file here is a real, user-visible
option — do not add one as a one-off hack for a single pattern.
- Keep variation files focused — only override what differs from the base `theme.json`.
- `styles/dark.json` must mirror the semantic colour token paths from `theme.json`.
- Shared non-colour token families such as `settings.custom.animation` and `settings.custom.z-index` do not need matching paths in `styles/dark.json`.
Expand Down
Loading
Loading