Skip to content

Conversation

@CGastrell
Copy link
Contributor

@CGastrell CGastrell commented Sep 22, 2025

Part of FORMS-220

Proposed changes:

This PR is both a PoC to add layout support to the form block, but also intends to fix and clarify some of our under the hood CSS mess.

image

What it does:

  • adds layout support with default class/styles manipulation via the blocks API
  • accommodates styles to address expected output while retaining old forms styling
  • uses the block registration to add a new stylesheet common to both editor and frontend
  • fixes some double class assignments while moving away from hacks to add classes that are meant to be used by blocks API by default
  • addresses an issue where the name of the container class (due to the above) confuses the layout support process (likely because it's called almost the same? .wp-block-jetpack-contact-form-container)

Why:

  • the form layout was originally designed to be a flexbox when there was no support for it (natively speaking by Gutenberg)
  • getting back in line with current blocks API and support aims for consistency and long term support
  • adding a new stylesheet where both, editor and frontend, can share styles: moving towards having a single markup structure is something we never had with forms, so both editor and frontend stylesheets have always been separate and mostly duplicated. The new one, jetpack-contact-form.css, is meant to be the single stylesheet to hold the entirety of the styles needed, effectively and eventually replacing grunion.css, and leave on editor.scss only what's needed to tweak/hack those styles for a fully wysiwyg experience. This is not to be done in a single blow, but we can agree on start migrating our styles there and eventually deprecate/delete grunion.css
  • some of our hacks include using the same CSS class on more than one ancestry container. This should not be. The changes include some cleanup and removals
  • in order to achieve this editor/frontend parity, a clear/unified container had to be set, yet it could not be .wp-block-jetpack-contact-form-container as it was messing with the layout support and causing the classes to be assigned to the wrong element. So, the container class was changed and the probe selector (view.ts) to match it: .jetpack-contact-form-container

Other information:

  • Have you written new tests for your changes, if applicable?
  • Have you checked the E2E test CI results, and verified that your changes do not break them?
  • Have you tested your changes on WordPress.com, if applicable (if so, you'll see a generated comment below with a script to run)?

Jetpack product discussion

p1758236622558259-slack-C052XEUUBL4
peKye1-1Lr-p2

Does this pull request change what data or activity we track or use?

No

Testing instructions:

Build both Jetpack and Forms:

jetpack build packages/forms plugins/jetpack

Visit the editor and create forms with different layouts. See if they behave as expected.
Visit the frontend and see if they match the editor preview.

@CGastrell CGastrell self-assigned this Sep 22, 2025
@CGastrell CGastrell added [Status] Needs Review This PR is ready for review. [Type] Task [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Package] Forms Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR labels Sep 22, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the add/forms-layout-support branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/forms-layout-support

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions bot added [Block] Contact Form Form block (also see Contact Form label) [Feature] Contact Form labels Sep 22, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add a "[Type]" label (Bug, Enhancement, Janitorial, Task).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

The Jetpack plugin has different release cadences depending on the platform:

  • WordPress.com Simple releases happen as soon as you deploy your changes after merging this PR (PCYsg-Jjm-p2).
  • WoA releases happen weekly.
  • Releases to self-hosted sites happen monthly:
    • Scheduled release: January 6, 2026

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@jp-launch-control
Copy link

jp-launch-control bot commented Sep 22, 2025

Code Coverage Summary

Coverage changed in 6 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/forms/src/blocks/contact-form/class-contact-form-block.php 488/609 (80.13%) -2.30% 17 💔
projects/packages/forms/src/modules/form/view.js 0/280 (0.00%) 0.00% 3 ❤️‍🩹
projects/packages/forms/src/contact-form/class-contact-form.php 928/1322 (70.20%) 0.12% 2 ❤️‍🩹
projects/packages/forms/src/blocks/contact-form/index.js 0/6 (0.00%) 0.00% 1 ❤️‍🩹
projects/packages/forms/src/contact-form/class-contact-form-field.php 1093/1744 (62.67%) 0.02% 0 💚

Full summary · PHP report · JS report

Coverage check overridden by Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR .

@CGastrell CGastrell requested a review from a team September 22, 2025 19:42
$url = apply_filters( 'grunion_contact_form_form_action', $url, $GLOBALS['post'], $id, $page );
$has_submit_button_block = str_contains( $content, 'wp-block-jetpack-button' );
$form_classes = 'contact-form commentsblock';
$form_classes = 'contact-form jetpack-contact-form__form';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the "commentsblock" class? Mostly for backwards compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can, but I haven't seen it in use anywhere, do you know where is that coming from of what is it used for?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is that it would be used by themes that want to style comment forms and we would apply the same styles as them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice guess, but it also raises the question as usual: should we rule styling over our inner blocks? And, even if we allow customization, should a "comments block" style the same as our form blocks? After all, we try hard to flow into the theme's styling. TBH I'm not sure how to outweigh a decision here. I mostly wanted to just have a class specific to our form element, so we always know which is it and can include/exclude on stylings. We can put the commentsblock class back in, but feels pretty much as a tweak or concession instead of a clear definition of what our markup should be or abide by.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I agreed with Enej, but you do have a point, and commentsblock seems very wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove it, let's remove it in a PR that's much easier to revert than this mega PR :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough.

@CGastrell CGastrell force-pushed the add/forms-layout-support branch from 07d2a02 to 74c3d24 Compare October 14, 2025 16:07
@CGastrell CGastrell force-pushed the add/forms-layout-support branch 5 times, most recently from 4802ade to 76a70cf Compare October 27, 2025 18:58
const blockProps = useBlockProps( { ref: wrapperRef } );
const blockProps = useBlockProps( {
ref: wrapperRef,
className: clsx( className, variationName === 'multistep' && 'is-multistep' ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an alternative, might be a bit easier to read:

clsx(
	className, 
	{ 'is-multistep': variationName === 'multistep' }
)


// Extract only valid block registration properties from block.json
// Exclude file-based properties like editorScript, style, etc.
const { editorScript, style, name: blockName, $schema, ...validBlockMetadata } = blockMetadata;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would've been a good separate PR to simplify this one. ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, but I discover the discrepancies while testing this one and creating a new PR trying to explain the reason for the change would have implied some back and forth / delays. Thought I'd just add it here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referencing a change to WIP PRs to demonstrate how it makes dev ergonomics better elsewhere is fine.

It's ok here now tho.

index: 1,
element: (
<>
<div key="phoneFieldControls">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need a wrapper if children already have keys? 🤔 It's unrelated to this PR, so it would've been better in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will separate this one, it was just killing me to get an error on every reload

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 1 to 3
// this styles are common to both editor and frontend
// Gutenberg versions will use different classname strategies
// for layout flex support, cover both possibilities here: frontend and editor.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typos and not very clear comment

Comment on lines 1022 to 1034
// somehow we are getting the class jetpack-field__input-element on the wrong wrapper
// .jetpack-field__input-element is meant to style a div mocking an input element, not its wrapper.
$class_names = preg_replace( '/jetpack-field__input-element /', '', $class_names );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you get to the bottom of why this is happening, or it's a good follow-up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for follow up!

*/
private static function render_error_wrapper() {
$html = '<div class="contact-form__error" data-wp-class--show-errors="state.showFormErrors">';
private static function render_error_wrapper( $classes = array() ) {
Copy link
Member

@simison simison Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an array instead of a string if we only ever pass it one class in the codebase? I would simplify, and if we later need to pass array we can turn it to a string higher up before this function.

Alternatively, pass is_horizontal boolean and manage CSS class directly in the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an array instead of a string

Just because is more future-proof. It can easily be a string. Using a boolean would not simplify as we'd need a ternary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to single string param for extra CSS classes

const fieldId = context.fieldId;
const field = context.fields[ fieldId ] || {};
// when there is only one field, don't show errors until the user tries to submit the form.
// And even then, return false so we only show the general form error message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to indeed improve what we show on submit.

We should instead show the inline form message, which looks much better than the big error badge. This change would've been a good independent PR too to get it in quicker and make testing easier.

Better:
Image

This PR:
Image

Let's also continue invalidate on-blur to save users click for on-submit, that was the whole point of that UX improvement so let's not remove it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm good catch, I need to make sure we only do this on single input, horizontal forms. Will use iApi for it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simison could we do something like this for all of our forms, please? 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CGastrell prolly best we iterate on this in a separate PR to keep individual changes moving?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilonagl, the summary error thing has some purpose on very long forms, where you don't see inline errors above the fold, but I think the UX/design can be improved a lot for sure when pressing "submit".

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simison yes, agreed! We recently had a chat about with this with Enej. Do we have a Linear or Github task where I could start gathering designs and notes?

Jetpack_Forms::plugin_url() . '../dist/contact-form/css/jetpack-contact-form.css',
array(),
\JETPACK__VERSION
);
Copy link
Member

@simison simison Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the logic with two style files? Should everything from grunion.css move here next up, are they loaded separately conditioally, or something else?

Now the two separate stylesheets don't feel like they make much sense because of how they're named.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it doesn't make "much" sense. I can easily merge this onto grunion, but that other one is so messed up it's very difficult to work with. Here we just register the style, then it's the new blocks API in charge of loading it: the frontend loads this CSS on demand (as in, not loaded unless there's a form block present, see class-contact-form-block.php:58). I thought it could become a new file to work with and, hopefully, a place to start moving our CSS while we clean up grunion. Otherwise, options are: merge onto grunion or name it specifically as part of the layout support styles.

CGastrell and others added 28 commits December 4, 2025 19:51
…generic form error suffices and allows for single-field, horizontal forms (subscribe)
Co-authored-by: Mikael Korpela <[email protected]>
@CGastrell CGastrell force-pushed the add/forms-layout-support branch from 0f5e795 to 337f0a9 Compare December 4, 2025 22:52
Copy link
Contributor

@dhasilva dhasilva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests fine for me. Would be wise to get more reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] Contact Form Form block (also see Contact Form label) Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR E2E Tests [Feature] Contact Form [Package] Forms [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] Needs Review This PR is ready for review. [Tests] Includes Tests [Type] Task

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants