Skip to content

Style fidelity: translate source CSS button styling onto native core/button attributes#241

Merged
chubes4 merged 1 commit into
trunkfrom
fix/button-style-fidelity
Jun 27, 2026
Merged

Style fidelity: translate source CSS button styling onto native core/button attributes#241
chubes4 merged 1 commit into
trunkfrom
fix/button-style-fidelity

Conversation

@chubes4

@chubes4 chubes4 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Closes #233

Problem

Imported CTA buttons convert to core/button and keep their source class, but carry zero native style attributes (backgroundColor/textColor/style.color/style.border all empty). The resolved source CSS was only carried as a non-canonical inline style string, which WordPress drops on block recovery, so buttons render with the theme default grey styling:

  • Filled primaries fell back to default grey fill.
  • Ghost/outline buttons (correctly mapped to is-style-outline) rendered as a bare default outline with no border/text color (the h44 case from the issue comment).

Fix (generic, theme-independent)

Reuses the transformer's existing CSS-rule resolution — mergedPresentationStyle already merges the matched <style>/linked-CSS rules for a button via matchesCssSelector — and translates those resolved declarations into native WordPress core/button attributes. It keys only off the resolved declarations; no fixture-specific strings.

  • Filled buttons: style.color.background + style.color.text; when declared, style.border.radius/width/style/color, style.spacing.padding, and style.typography.*. Renders with has-background/has-text-color classes + inline style.
  • Outline/ghost buttons (is-style-outline, transparent background): style.border.* + style.color.text and never a background, so they render as a colored outline (border + text color), not a bare default outline. A transparent/none background naturally resolves to no fill, which is what distinguishes outline from filled.
  • Negative: a button with no resolvable color/border CSS stays a default button — no fabricated colors.

This PR is slice 1 (buttons) of the broader style-fidelity workstream; non-button elements are untouched.

Changes

  • New src/HtmlToBlocks/Patterns/ButtonStyleResolver.php — generic declaration -> native-attribute translation.
  • ButtonsPattern.php — resolves the button's CSS into native attributes (preserves existing button conversion + is-style-outline mapping).
  • BlockFactory.php — serializes the native style object into canonical has-* classes and inline style for both <a> and <button> buttons.
  • HtmlTransformer.php — guard structure-signal stringification against the now-object style attribute.
  • Updated existing button parity fixtures to the native output; added filled / outline / unstyled-negative fixtures.

Tests

  • composer test:canonical — pass.
  • composer parity — pass (128 fixtures, no pass/fail regression).
  • composer test (canonical + parity + packaging) — pass.

🤖 Generated with Claude Code

Imported CTA buttons converted to core/button but carried zero native
style attributes (backgroundColor/textColor/style.color/style.border were
all empty); the resolved source CSS was only carried as a non-canonical
inline style string, so WordPress rendered the buttons with default grey
styling after block recovery.

This reuses the transformer's existing CSS-rule resolution
(mergedPresentationStyle -> matchesCssSelector over <style>/linked CSS)
and translates the resolved declarations into native WP block attributes:

- Filled buttons get style.color.background + style.color.text and, when
  declared, style.border.radius/width/style/color, spacing.padding, and
  typography (-> has-background/has-text-color classes + inline style).
- Outline/ghost buttons (transparent background, is-style-outline) get
  style.border.* + style.color.text and never a background, so they render
  as a colored outline rather than a bare default outline.
- Buttons with no resolvable color/border CSS stay default (no fabricated
  colors).

New ButtonStyleResolver does the generic, theme-independent declaration ->
attribute translation; BlockFactory serializes the native style object into
canonical has-* classes and inline style. Existing button parity fixtures
updated to the native output; added filled/outline/unstyled fixtures.

Closes #233

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chubes4 chubes4 merged commit 31ad99d into trunk Jun 27, 2026
1 check passed
@chubes4 chubes4 deleted the fix/button-style-fidelity branch June 27, 2026 23:29
chubes4 added a commit that referenced this pull request Jun 28, 2026
Core blocks were emitted with a raw inline `style` STRING attribute
(e.g. wp:paragraph {"style":"color:..."}, wp:group {"style":"padding:..."}).
Core blocks expect a structured `style` OBJECT + `layout`, so the stored HTML
diverged from each block's save() and the editor flagged "unexpected or invalid
content" for nearly every styled element.

Generalize the ButtonStyleResolver approach (#241) to ALL blocks:

- New Style/StyleAttributeMapper translates resolved CSS declarations into the
  canonical block style OBJECT (style.typography/color/spacing/border) and
  serializes it back to the inline CSS + has-* support classes WordPress emits
  in save(), keeping stored markup in sync with the block.
- presentationAttributes() now returns a canonical `style` object (never a
  string) plus the `layout` attribute; display:flex|grid folds into `layout`.
- BlockFactory serializes the style object for every block (group, heading,
  paragraph, list, quote, columns, icon, ...) via the shared mapper.
- HARD RULE: a raw `style` string is never emitted on a core block. Unmappable
  declarations (position, overflow, transform, background images, aspect-ratio,
  nav anchor/submenu CSS, ...) are dropped and ride on the preserved className.
- Buttons keep resolving from the raw merged CSS via a threaded resolvedStyle
  callback (ButtonStyleResolver unchanged).
- Hidden-state safety (#259): display:none / visibility:hidden / opacity:0 base
  states are not frozen onto content-bearing/interactive elements; they are
  normalized away and surfaced as a frozen_hidden_state diagnostic finding.
  Genuinely decorative / aria-hidden nodes may stay hidden.

Tests:
- New "canonical block style attributes" contract: guard asserting no emitted
  core block carries a raw string `style`; positive cases (styled h2/p/div ->
  canonical typography/color + layout flex); negative case (unmappable props ->
  className, not a raw style string); hidden-nav normalization.
- 15 parity fixtures updated: only the style-attribute SHAPE changed
  (raw string -> canonical object / layout / dropped-to-className); content and
  structure are unchanged. Full suite green.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Style fidelity: translate source CSS styling onto native block attributes (buttons render as default gray)

1 participant