Describe the bug
At times, it's useful to reference the className
property of a block in the save function. This is currently not possible, as the className
value is not passed to the save function for a block, even though several core blocks expect to receive a className
argument (e.g., cover image, subhead, verse).
For example, if you want to use the default className
value in your save function to dynamically make your block use a BEM like class syntax, you might write something like this:
save( { attributes, className } ) {
const { content, title } = attributes;
return (
<div>
<h3 className={ `${ className }__title` }>{ title }</h3>
<p className={ `${ className }__content` }>{ content }</p>
</div>
)
}
However, className
is undefined, so this doesn't work, even though several core blocks (as noted) show this exact argument signature. In those cases, the undefined className
value is hidden by the fact that it is passed, along with other class names to the classnames()
function or as the className
attribute of a RichText.Content
component, both of which hide the fact that this value is undefined.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The className
value should be passed as an argument to the save function.
@joemcgill Can you update the description to include a representative snippet of code?
even though several core blocks expect to receive a
className
argument (e.g., cover image, subhead, verse).
What happens in this scenario?
@danielbachhuber I've updated the description with a reduced case and explain how the few core blocks that include a className
attribute are hiding the fact that this value is undefined.
In my code example above, I don't know if there is a workaround that wouldn't require explicitly passing the className
value to the save
function of the block or if this should simply not be supported by the blocks API, but if the latter, the core blocks that are currently expecting className
should be updated to remove that value.
Thanks @joemcgill
Not sure this directly solves it, but if you add className
as an attribute on your block, it gets passed around as an attribute.
(This can lead to another issue where blocks that don't explicitly support className
as an attribute can break attribute validation when a class is added to the Additional CSS Classes field, which is always visible. I don't have a test case for this but can submit an issue once I do.)
Thanks for the suggestion @chrisvanpatten. I guess specifically duplicating the className as an attribute is an option, but seems duplicative. A nice workaround for the moment though.
@joemcgill looking at the Cover Image block, it looks like the className is being passed in as attributes.className
. Not sure how it's actually getting serialised since obviously the top-level className
prop is undefined
, and attributes.className
isn't referenced at all in the save()
callback, but there you go. Some weird magic under the hood…?
@joemcgill looking at the Cover Image block, it looks like the className is being passed in as
attributes.className
. Not sure how it's actually getting serialised since obviously the top-levelclassName
prop isundefined
, andattributes.className
isn't referenced at all in the save() callback, but there you go. Some weird magic under the hood…?
All correct @chrisvanpatten. This is how you can reference className
in the save
function. By default is always injected to the root element. If you want to use it elsewhere, this is a way to go.
@gziolo Can you elaborate on this? Should this work or not? When using save( { attributes, className } ) { }
then className
is always undefined. I tested this in a custom block but also with the cover/subhead/verse blocks. It's also mentioned in the attributes docs.
@ocean90 I _think_ the way it works now is
save( { attributes } ) {
console.log(attributes.className);
}
So className
isn't a top-level prop, but located under attributes
. (I could be wrong on this, but that's what I've been doing!)
(So potentially, this should get a Documentation flag.)
@chrisvanpatten Unfortunately there's also no attributes.className
.
I think that className
is something you can fill in in the sidebar in Advanced section. Once you provide it, it should be exposed under attributes.className
. In the case of generated class name, it's applied after save
method is called:
https://github.com/WordPress/gutenberg/blob/master/packages/blocks/src/api/serializer.js#L74
using filter:
https://github.com/WordPress/gutenberg/blob/master/packages/blocks/src/api/serializer.js#L86
So you can't really access it. The idea is that the generated class name is always applied to the root element.
Related: #2035 (cc @aduth)
@gziolo Thanks for your answer! So the linked references should probably be updated?
The generated className isn't auto-added in the save
handler anymore, and certainly wouldn't work for server rendered blocks.
I think there are a few potential paths:
attributes
~I think either way we should audit the core blocks to be sure they aren't pulling in undefined className
props as it sounds like they may still be using this legacy method.
Sounds like option 2, plus an audit of the blocks that are _doing_it_wrong
is the way forward :)
The documentation has since been updated in #11605. Should this then be closed?
Otherwise, what are the specific action items of said audit? (What measure can be made for closure of the issue?)
@aduth merging #11605 will close this.
Oops. This can be closed! Just saw it was merged.
I know this was closed because classNames
was removed from the save methods of core blocks but what do you do if you want access to the classes in the save method to allow BEM like class syntax without manually having to add a class in the editor?
For example, if you want to use the default
className
value in your save function to dynamically make your block use a BEM like class syntax, you might write something like this:save( { attributes, className } ) { const { content, title } = attributes; return ( <div> <h3 className={ `${ className }__title` }>{ title }</h3> <p className={ `${ className }__content` }>{ content }</p> </div> ) }
@mrmadhat You can just specify className
in your block code, e.g. const className = 'my-class-name';
Or use const className = getBlockDefaultClassName( 'your/block-name' )
. It's part of @wordpress/blocks
.
@ocean90 TIL… that's super handy!
Thanks @ocean90 that's perfect
I've found another good use for ocean90's approach, but this time for the edit
side of the matter, specially if you're trying to achieve a BEM like syntax like @joemcgill and @mrmadhat were asking about (I was trying to achieve exactly that myself too, so thanks a lot @ocean90 for that :)
If you're working with block styles and you attempt something like this:
<h3 className={ `${ className }__title` }>{ title }</h3>
The output will look something like this (once you've chosen a style):
<h3 class="wp-block-my-blocks-example is-style-default__title"</h3>
The className
variable brings along the style class too, which is definitely not what I needed. But somehow the getBlockDefaultClassName
method does not do this, and that's how I managed to keep things dynamic and in the way that I needed to.
@rodrigodagostino Nice addition to the solution of @ocean90, I was facing this exact same issue today. Thanks!
@jlambe really glad I could help, my friend :)
Most helpful comment
Or use
const className = getBlockDefaultClassName( 'your/block-name' )
. It's part of@wordpress/blocks
.