Button: Migrate to width block support#74242
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Size Change: +2.08 kB (+0.03%) Total Size: 6.87 MB
ℹ️ View Unchanged
|
SirLouen
left a comment
There was a problem hiding this comment.
Test Report
Environment
- WordPress: 6.9
- PHP: 8.3.28-dev
- Server: PHP.wasm
- Database: WP_SQLite_Driver (Server: 8.0.38 / Client: 3.51.0)
- Browser: Chrome 143.0.0.0
- OS: Windows 10/11
- Theme: Twenty Twenty-Five 1.4
- MU Plugins: None activated
- Plugins:
- Gutenberg 22.3.0
- Test Reports 1.2.1
Testing Steps
- Set to 500px the
widthfor the Button Global Style - Created a post with two buttons
- First button set to 100%
- Second button, left default
Actual Results
- ✅ PR is working
Additional Notes
⚠️ Added one improvement for the code.
Supplemental Artifacts
Backend
Frontend
|
Appreciate the speedy reviews everyone 🙇
It definitely isn't but the blocking review was more just a slight difference in process etc. There's been some discussion with @SirLouen on other PRs e.g. #74189 (comment), so I think we're all a little more aligned on that now. That said, it was just an oversight when I was juggling a few things and that duplication definitely shouldn't be there. I'll get it cleaned up soon. Same for the failing e2e test. |
|
Quick update: I've removed the duplication but still have one last failing e2e locally that needs some further debugging. This might not be ready for further review until tomorrow. |
Feedback has been addressed and was non-blocking too
|
Nice work. 🚀 |
|
Thank you for the reviews and testing here 🙇 I'm thinking I might hold off on merging this one for the moment. I'd like to possibly add default width presets for the Button block to match the old toggle group controls UX a little. This could just be a follow-up although I'll be offline for the next week or so and there's no time crunch on landing this. |
|
Looks like there's an edge case with creating presets for individual block types. Defining I'll have to dig into this one further. |
|
Quick update: As noted above the CSS vars are defined via the preset styles generated by the theme.json class. This heavily reuses other block metadata for selectors etc. I don't think there's much of a clean path to tweaking this such that the It might be that for this edge case, it will be best to skip the serialization of the width block support such that the preset classes are applied to the same inner element that block's root selector points to. None of this seems particularly ideal but at the same time I don't think this scenario will be common and probably doesn't warrant a bespoke mechanism within theme.json implemented just to handle this. Edit: It might be possible to pass through the selectors in the setting nodes and pull out a specific selector for a given feature. I'll look further into this tonight and next week. |
098f19d to
3f80577
Compare
|
After wrestling with theme.json CSS variables creation, I think I have something that works now. I might leave this PR as is for now to ease testing, then split it up for better traceability, in case the changes around CSS var generation need reverting for some reason. As it stands now though, defining default |
There was a problem hiding this comment.
I really like the work here, and overall it's going to be really powerful to support fully customisable widths while also retaining backwards compatibility for the existing widths 👍
I feel like this isn't too far off, but in testing the main issue I ran into is that it looks like the inline width: 25% rule is winning out over the existing classname-based presets that ship with the button block. This mostly appears to be an issue when going for layouts that are intended to all sit on the same line. I.e. 4 25% blocks, or 2 50% blocks or 1 25% block next to a 75% block. Here's a view side-by-side:
(left is this PR, right is trunk):
(The cause of the issue being that the existing classname-based rules factor in the gap when setting width)
Not too sure what the right fix would be, i.e. whether we can skip inlining the style if the classname is applied, or adjust specificity? Right now the inline style wins out:
What do you think?
Some button block markup from trunk
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Button A</a></div>
<!-- /wp:button -->
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Button B</a></div>
<!-- /wp:button -->
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Button C</a></div>
<!-- /wp:button -->
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Button D</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button {"width":25} -->
<div class="wp-block-button has-custom-width wp-block-button__width-25"><a class="wp-block-button__link wp-element-button">Button A</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":25} -->
<div class="wp-block-button has-custom-width wp-block-button__width-25"><a class="wp-block-button__link wp-element-button">Button B</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":25} -->
<div class="wp-block-button has-custom-width wp-block-button__width-25"><a class="wp-block-button__link wp-element-button">Button C</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":25} -->
<div class="wp-block-button has-custom-width wp-block-button__width-25"><a class="wp-block-button__link wp-element-button">Button D</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button {"width":50} -->
<div class="wp-block-button has-custom-width wp-block-button__width-50"><a class="wp-block-button__link wp-element-button">Button A</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":50} -->
<div class="wp-block-button has-custom-width wp-block-button__width-50"><a class="wp-block-button__link wp-element-button">Button B</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":75} -->
<div class="wp-block-button has-custom-width wp-block-button__width-75"><a class="wp-block-button__link wp-element-button">Button C</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":25} -->
<div class="wp-block-button has-custom-width wp-block-button__width-25"><a class="wp-block-button__link wp-element-button">Button D</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button {"width":50} -->
<div class="wp-block-button has-custom-width wp-block-button__width-50"><a class="wp-block-button__link wp-element-button">Button A</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":50} -->
<div class="wp-block-button has-custom-width wp-block-button__width-50"><a class="wp-block-button__link wp-element-button">Button B</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":25} -->
<div class="wp-block-button has-custom-width wp-block-button__width-25"><a class="wp-block-button__link wp-element-button">Button C</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":75} -->
<div class="wp-block-button has-custom-width wp-block-button__width-75"><a class="wp-block-button__link wp-element-button">Button D</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button {"width":100} -->
<div class="wp-block-button has-custom-width wp-block-button__width-100"><a class="wp-block-button__link wp-element-button">Button A</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":100} -->
<div class="wp-block-button has-custom-width wp-block-button__width-100"><a class="wp-block-button__link wp-element-button">Button B</a></div>
<!-- /wp:button -->
<!-- wp:button {"width":100} -->
<div class="wp-block-button has-custom-width wp-block-button__width-100"><a class="wp-block-button__link wp-element-button">Button C</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->| export function getWidthClasses( width ) { | ||
| const percentageWidths = [ '25%', '50%', '75%', '100%' ]; | ||
|
|
||
| if ( ! width ) { | ||
| return {}; | ||
| } | ||
|
|
||
| if ( percentageWidths.includes( width ) ) { | ||
| const numericWidth = parseInt( width, 10 ); | ||
| return { | ||
| [ `has-custom-width wp-block-button__width-${ numericWidth }` ]: true, | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| 'has-custom-width': true, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Does this logic also need to check if it's been given a preset rather than the exact percentage values?
If I set a block using the preset slider, then it seems that I get <div class="wp-block-button has-custom-width" style="width:var(--wp--preset--dimension--25)"> on the site frontend, without the wp-block-button__width-25 classname.
There was a problem hiding this comment.
I'm not sure on the best approach here either 🤔
I've done some digging today into possibilities. Here's a little of what I've come up with so far in terms of options.
Presets
We could lean into preset widths, being those that automatically accommodate gap spacing. That inconsistent application seems off to me, though. We'd still also need to skip the serialization of the block support to conditionally update the inline styles.
Percentage widths
There might be another imperfect option. We could have a generic CSS rule that takes a new CSS var for the width value and adjusts it for the block gap spacing. We'd still skip the serialization on the block instance, this time replacement the width style with a definition of the CSS var for the width e.g. --wp--block--button--width.
We could collapse the existing CSS styles into one generic one for horizontal and vertical layouts in the buttons block.
Any non-% value would simply be that defined width no consideration for block gap spacing.
We might still need to keep the existing wp-block-button__width-* classes and add a new one e.g. wp-block-button__width, so that the generic formula CSS style could hook onto it for any % width. We should be able to have situations then like assigning three 33% buttons on a single row etc. along with more obscure combinations as the user desires.
I'll try and get something functional towards the second idea tomorrow and see how it works in reality. If this looks like it might be a hold up for width adoption on the button block, I'll split out the per-block-type preset definition fix for blocks using inner elements as their root selector.
There was a problem hiding this comment.
Nice one, thanks for digging in. Sounds like a good avenue to explore to me, and happy to test further as soon as you're ready 👍
There was a problem hiding this comment.
I think I've found something that automatically covers the block gap spacing when we have percentage widths but still works for explicit widths. I haven't had the chance to test it deeply yet but I'll get to that tomorrow.
As it stands now I think we have something that:
- works as you'd expect,
- allows the Button block greater flexibility in terms of widths,
- maintains backward compatibility,
- and sets an example for other blocks that might have complex requirements and need to skip serialization
There was a problem hiding this comment.
I'm now seeing the following classes wp-block-button has-custom-width wp-block-button__width wp-block-button__width-25 on the button element now. Is that now expected?
|
Thank you very much for all the reviews and testing 🙇
The best way to answer this might be that I didn't even think much on it. I knew what I wanted to do and this tool fit the job nicely. It was pretty frictionless to achieve the desired result. So big props for all the work on this @dmsnell
Yeah, there could be some nice ergonomic gains and avoiding of edge cases with a CSS API. Something like that would be nice whenever it comes around,.
Apprecaite the review @ramonjd 👍 I've addressed the latest feedback. Given it was pretty minor, all the tests pass, and I've smoke tested again for good measure. I'll get this merged. Anything else that pops up we can iterate on. |
|
Hello! Is it possible to turn off/disable the button width controller through theme.json? |
|
This works for me. {
"settings": {
"blocks": {
"core/button": {
"dimensions": {
"width": false
}
}
}
}
} |
|
@ramonjd I tried that multiple times and it does not work for me. What version number of theme.json do you have and what version of WP did you test this on? |
Oh sorry, I should have been more specific 😄 It's a new feature and not one yet shipped to WordPress Core, so I was testing on latest Gutenberg trunk. |
|
I think the topic of layout controls was touched upon in #76998 (comment) |
I missed that discussion, unfortunately. It shouldn't have been too hard to replace the custom width controls with layout; I did the same for the Spacer block in #49362 (conditionally, as it's not always a flex child). Even if it proved to be complex I believe it would be worth prioritising user experience over ease of development in this case. |
|
Thanks for the feedback, even if belated. Happy to explain some of what I recall from the thinking at the time 🙂
A few reasons, and I think on balance width block support is still the stronger choice for the first step in enhancing the Button block. The original controls offered a fixed set of percentage width values (25/50/75/100%) that tiled in a specific way. Width block support is a direct and natural evolution of that model. It:
That last one was probably the biggest reason, to allow a default width for all Buttons site-wide. Unfortunately, layout child controls don't offer that. The different sizing model they use (flex-basis) doesn't map cleanly onto the original ad hoc width options. The one thing layout child controls would add here is
By default, yes, the Button block's block.json declares
Spacer is a different situation. There's no real use case for setting a width on all Spacers globally. They're inherently one-off layout tools. Button is the opposite: a theme absolutely might want to define a default Button width. That's a meaningful UX capability that only width block support can deliver at the moment.
I'll be charitable in reading this but width block support wasn't, hands down, the easier path, but it was the more capable one. The size of the discussion on this PR probably speaks for itself on the "ease of development" front. The fill behaviour, as well as ensuring an improved UX at the block instance level with all the layout child controls capabilities, is definitely a worthy follow-up and on the cards as noted. |
There seems to be as it has been requested as far back as #27614. Given how Button text length can vary, defining it at a theme level seems less likely than at the individual instance level. You could have a sitewide default width, which would still be useful to use global styles for, but I don't think it's a case of Button being a more likely candidate for width defaults than Spacer.
All too often things we think will be simpler spiral into rabbit holes, so I can certainly believe that 😅 |
I'll stand corrected 👍 The feedback I'd seen in recent times was unsure of the value of the global spacer width given it might not be the best tool to reach for, for consistent spacing etc.
No arguments here. The UI does need iteration and is tracked. There was also this comment from the original width support PR that indicated what some of these changes might be and also how perhaps, with a rename, these controls may not conflict so much. |
|
The "no core sync required" must have been added by mistake; the changes to the theme json class need syncing to core. |
|
Thanks for catching that 😬 I believe when the label was added it was after the initial carving up of this migration when it was thought to be much simpler and only touched block library files. I'll try and sort out the backport for what was missing here tomorrow. |
|
I've create a backport for this PR specifically in WordPress/wordpress-develop#12046. In the process of testing that I found there's another backport required for work previously split out from this PR. That backport will be required to be merged before the new one above. I've left the new backport in a draft status for the moment. I'll get both of these backported, tested, approved, and comitted tomorrow. |




What?
Migrates the Button block from using an ad-hoc
widthattribute to thedimensions.widthblock support.Why?
How?
widthattribute from block.jsondimensions.widthsupport with default controls enabledstyle.dimensions.widthformatcalc()formula that subtracts a proportional share of the block gap — matching the block-instance-level behavior instyle.scss— so that buttons tile correctly on a row (e.g. 4 buttons at 25% fit side-by-side without overflowing)Testing Instructions
settings.blocks.core/button.dimensions.dimensionSizeslevel and confirm they workcalc()formula, not a plainwidth: 25%)UX Change
The width control changes from a toggle group with preset buttons (25/50/75/100%) to a dimension control that accepts any value. Default dimension sizes for the Button block have been added in
lib/theme.jsonso that the preset 25/50/75/100 values are there.The core difference now is that, instead of a toggle button group, the dimension control renders the presets as a segmented slider control.
Backward Compatibility
wp-block-button__width-25, etc.) are preserved for percentage valuesScreenshots or screencast
Screen.Recording.2025-12-27.at.12.16.10.pm.mp4