Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Alignments Rethinking #20650

Closed
youknowriad opened this issue Mar 5, 2020 · 72 comments
Closed

Block Alignments Rethinking #20650

youknowriad opened this issue Mar 5, 2020 · 72 comments
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Blocks Overall functionality of blocks

Comments

@youknowriad
Copy link
Contributor

youknowriad commented Mar 5, 2020

Current Issues

Alignments in Gutenberg today suffer from several issues, the most important one being the inconsistency between the different block markups.

An image block aligned left/right or center has the following markup

<div class="wp-block-image">
    <figure class="alignleft"></figure>
</div>

but when it has no alignment or wide/full, it has the following markup

<figure class="wp-block-image alignleft"></figure>

Other blocks like buttons, columns, ... use the align support hook which produces the following markup no matter the alignment applied

<div class="wp-block-something alignleft"></div>

This makes it almost impossible to support properly for theme authors without bugs.

There are more or less valid reasons for these differences but instead of going through history, let's try to focus on what we want to support and how we can fix this.

Use cases

Let's assume we want to build a theme that supports all alignments (the most complex case) and all blocks.

excalidraw-202031203645

We'll most likely have a wrapper for blocks whether it's coming from the theme's template files .entry-content or a group block.

Now, let's take a look at each one of these use-cases and see how to implement them in terms of markup and styles.

Regular Alignment (no alignment)

markup

<div class="wp-block-something"></div>

Note that the class here is not always present (for valid reasons I want to leave out of the current issue)

styles

.wrapper > * {
    max-width: 600px; /* width of the regular blocks */
    margin-left: auto;
    margin-right: auto;
}

The margin technique is needed here because we want to support wide/full alignments at the same time. (see later)

Wide alignment

markup

<div class="wp-block-something wp-align-wide"></div>

(don't mind the different alignment classNames, I'll explain later)

styles

.wrapper > *.wp-align-wide {
    max-width: 800px; /* width of the wide blocks */
    margin-left: auto;
    margin-right: auto;
}

Full-width alignment

markup

<div class="wp-block-something wp-align-wide"></div>

styles

We can decide here to revert the default styles for the regular blocks or update its selector to exclude the full width blocks

.wrapper > *:not(.wp-align-full) {
    max-width: 600px; /* width of the regular blocks */
    margin-left: auto;
    margin-right: auto;
}

Floated blocks

Now, this is probably the most complex case. What we want here is a block that is marked as a float. That way, the following blocks, will wrap it but we also want the floating to be contained in the "regular" width area.

I saw some themes achieving this by applying computed margins to the block wrapper (left or right) and floating the block itself. In fact, that's the only possible technique for blocks using the "align support hook" but this approach is buggy since you can't really float multiple blocks in a row properly.

So what we're going to do is to add an extra wrapper around the blocks (like the image block above), that wrapper will have the regular size allowing us to float its content properly. So we end up with markup like that

markup

<div class="wp-block-something wp-extra-block-wrapper">
    <div class="wp-align-left"></div>
</div>

styles

.wp-align-left {
    float: left;
}

Centered blocks

Theoretically, we can center blocks without extra wrappers but we can also use the extra wrapper technique similar to float blocks to center blocks easily

markup

<div class="wp-block-something wp-extra-block-wrapper">
    <div class="wp-align-center"></div>
</div>

styles

.wp-extra-block-wrapper {
    display: flex;
    justify-content: center;
}

Alternatives

I'm personally not certain if there are other alternatives. Let me know if you have any.

Consequences

What does it mean if we have to implement this proposal:

  • Block markup might need to be changed to add extra wrappers when left/right/center alignments are applied.
  • Notice that I've used new classNames for the styling of the alignments. The reason for this is that if we use the current class, current themes will break because of the new markup.
  • With these updates, the alignments styles become simple enough that I think we can consider adding them automatically to the "theme" opt-in styles. Themes authors won't have to write them, we can just provide good defaults. (This needs to be prototyped/tested though)

Notes

The wide/full widths used above are examples, one idea here would be to rely on CSS variables and allow InnerBlocks to disable/enable/customize the widths per container.

@youknowriad youknowriad added [Feature] Block API API that allows to express the block paradigm. [Feature] Blocks Overall functionality of blocks labels Mar 5, 2020
@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

So this is more or less what we currently do for the image block, right?

For left/right floating, a slight variation could be:

markup

<figure class="wp-block-something wp-align-left">
    <div class="wp-aligned-content"></div>
</figure>

styles

.wp-align-left .wp-aligned-content  {
    float: left;
}

The benefit of this would be that we consistently have figure at the top level, and consistently have the wp-align-* class at the top level.

In not sure if it would be valuable, but we could consider always adding the extra inner div wrapper. It might not be useful for basic usage, but it could potentially be used by themes if they'd like some more complex positioning. Additionally it gives more consistency across all alignments.

@youknowriad
Copy link
Contributor Author

The benefit of this would be that we consistently have figure at the top level, and consistently have the wp-align-* class at the top level.

Good idea especially if we decide to go with our own style and separate classNames.

In not sure if it would be valuable, but we could consider always adding the extra inner div wrapper. It might not be useful for basic usage, but it could potentially be used by themes if they'd like some more complex positioning

Same here, I agree, though only for blocks with align support.

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

Cc @jasmussen for feedback. Here's his codepen: https://codepen.io/joen/pen/zLWvrW.

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

Same here, I agree, though only for blocks with align support.

Right, only for blocks that support alignment, but the block would always have the extra div wrapper, regardless of the alignment selected.

@jasmussen
Copy link
Contributor

I absolutely have thoughts on this and thank you for the ping. I will return with a more in depth comment, but overall I definitely agree this is an area where we can make things way better.

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

Another challenge:

When the outer block wrapper (top level element) is not floated, but rather the extra div element within, it would mess up the selection outline and toolbar position. Currently, we're handling this by creating an exception for alignments. In this case, we'd look if there's a .wp-aligned-content element, and use that to position the toolbar and add the outline to. It would we nice if we didn't have to make an exception, but it seems like there's no other way.

@youknowriad
Copy link
Contributor Author

@ellatrix right I don't see any other way for that one :(

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

@youknowriad Just exploring some ideas... What if we keep/extend alignment in block support, and internally wrap the block in an "alignment wrapper" element, which get automatically added around both the save and edit component?

  • No need for the block to implement anything, only add support. No need to add classes, wrappers...
  • No need to do any special handling for selection styles and toolbar positioning.

markup

<div class="wp-align-wrapper wp-align-left">
    <figure class="wp-block wp-block-image">...</div>
</div>

@youknowriad
Copy link
Contributor Author

Sounds interesting, I believe in that case the Block.* wrapper is the internal element right? I wonder if has any impacts on its behavior.

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

@youknowriad No, the Block.* wrapper is still implemented by the block, so in this case:

<div class="wp-align-wrapper wp-align-left">
    <Block.figure class="wp-block wp-block-image">...</Block.figure>
</div>

The wrapper around the block would be added internally to both edit and save output.

I'll quickly explore this idea and see if it's interesting.

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

This is the markup I'm proposing in the last few comments:

No Alignment

markup

<div class="wp-align-wrapper">
    <figure class="wp-block-image"></figure>
</div>

The wrapper could be omitted here, but personally I like the consistency, and it allow themes to create more complex layouts.

styles

.wrapper > * {
    max-width: 600px; /* width of the regular blocks */
    margin-left: auto;
    margin-right: auto;
}

Wide Alignment

markup

<div class="wp-align-wrapper wp-align-wide">
    <figure class="wp-block-image"></figure>
</div>

styles

.wrapper > *.wp-align-wide {
    max-width: 800px; /* width of the wide blocks */
    margin-left: auto;
    margin-right: auto;
}

Full Alignment

markup

<div class="wp-align-wrapper wp-align-full">
    <figure class="wp-block-image"></figure>
</div>

styles

.wrapper > *:not(.wp-align-full) {
    max-width: 600px; /* width of the regular blocks */
    margin-left: auto;
    margin-right: auto;
}

Left/Right Alignment

markup

<div class="wp-align-wrapper wp-align-left">
    <figure class="wp-block-image"></figure>
</div>

styles

.wp-align-left > * {
    float: left;
}

Center Alignment

markup

<div class="wp-align-wrapper wp-align-center">
    <figure class="wp-block-image"></figure>
</div>

styles

.wp-align-center {
    display: flex;
    justify-content: center;
}

@youknowriad
Copy link
Contributor Author

I like the consistency here. Awesome proposal @ellatrix

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

Question: isn't no alignment the same as centre alignment? If not, what's the difference?

@youknowriad
Copy link
Contributor Author

It's not for me. no alignment means "left alignment" but not floated. while center alignment is centered.

@ellatrix
Copy link
Member

ellatrix commented Mar 5, 2020

I'm not sure if "no alignment" being left aligned ever worked. And what about right alignment, but not floated?

@youknowriad
Copy link
Contributor Author

And what about right alignment, but not floated?

It could be a potential alignment but its need is very small. no-alignments is just any regular inline-block or block element. When you put it inside div, it just stays where it is. The difference is that we don't have a div around it because we want to support full/wide.

@ZebulanStanphill
Copy link
Member

(In case anyone is confused about floats and wide/full being called alignments, see #19672.)

The max-width + auto margin approach for no alignment will result in a lot of blocks just appearing centered, won't it? If you had an image element with a smaller width than the content width, and you used those styles, it would just end up being centered, wouldn't it?

Themes that implement wide/full widths via negative margins don't have the centered-by-default issue. Instead, of course, they have to deal with the clunkiness of negative margins, which tend to be a pain to deal with, particularly for the full width when trying to account for the scroll bar, which is not taken into account by vw units. (I know from experience. 😒)

Another approach a theme might use is CSS Grid to handle position/alignment, but of course in that context you can't use floats.

It's also worth noting that some WordPress themes use the float left and float right options for a pull-out position, where lots of negative margin is used to take the floated element completely out of the main content column. Perhaps that should be treated as its own separate option in addition to the existing positions/alignments/widths/etc?

As pointed out in #19672, it's important to note that wide/full widths and left/right floats are technically not even alignments in the first place. So perhaps the technical approach for widths vs floats vs actual alignments should be different, rather than attempting to use the same approach for all of them.

@youknowriad
Copy link
Contributor Author

If you had an image element with a smaller width than the content width, and you used those styles, it would just end up being centered, wouldn't it?

No, I don't think so. the alignment-wrapper will be centered but not its content.

So perhaps the technical approach for widths vs floats vs actual alignments should be different, rather than attempting to use the same approach for all of them.

Treating them differently means you can apply both at the same time, which is not the case for any of these right?

@ZebulanStanphill
Copy link
Member

@youknowriad

No, I don't think so. the alignment-wrapper will be centered but not its content.

Would blocks have an alignment wrapper even when not aligned? That seems like a lot of additional markup.

Treating them differently means you can apply both at the same time, which is not the case for any of these right?

I was more thinking along the lines of not having to use a wrapper for width variants like wide/full, or something like that. I think it's fine to keep the options mutually exclusive in the UI. (Though I do think they should each have more descriptive labels like "Stretch to full width" or "Float left" rather than the misleading and generic "Align ___".)

@strarsis
Copy link
Contributor

strarsis commented Mar 5, 2020

"No alignment" just means that no alignment is forced, that the defaults are used.
An example: On a RTL-directional page the default wouldn't be left but right.
Also some themes may want to have everything aligned to center as the default alignment - or left, maybe the theme got some decoration on the left side that should be shown more prominently.
Everything that gives the theme designer more freedom and consistency is a good thing IMHO.

@Netzberufler
Copy link

It was mentioned a few times that there are basically two approaches for alignments:

  1. Using max-width and margin auto
  2. Using negative margins with vw units

For my themes I went with negative margins because I think the first approach is not very robust.

Any custom block or custom block style from third-party plugins which accidentally overrides the margin value breaks the theme layout.

For example:

p.is-custom-style {
    margin: 0 0 3em;
    ....
}
.wp-custom-block {
    margin: 0;
    ....
}

Not sure if you have already plans to prevent that but I thought I mention it here.
It was one issue I had while initially adding styles for wide and full block alignments.

@mrwweb
Copy link

mrwweb commented Apr 22, 2020

As a theme author who's been consistently working the block editor for a year+ now, I just want to weigh in on the primary issue I've run into with alignment classes.

In this situation:

<div class="wp-block-image">
    <figure class="alignleft"></figure>
</div>

I can't write the selector .alignleft + * to apply to the block following a floated element. I often need to do this to deal with margin/alignment issues. Just adding a class to the wrapper indicating the contained alignment would solve all my problems:

<div class="wp-block-image is-aligned-right">
    <figure class="alignleft"></figure>
</div>

@strarsis
Copy link
Contributor

strarsis commented Apr 25, 2020

@youknowriad: There is one thing I find quite confusing: In backend/editor there is also the wp-block class. Can this class be used to replace selectors like > * as used in frontend? Typographic elements like paragraphs don't have the wp-block class assigned in frontend, so wp-block only reliably select all blocks in backend? Or should I rather use * > .editor-styles-wrapper for a (separate, unwrapped) editor stylesheet? Is this an extra nice-to-have feature for backend styling? .wp-block got max-width by default backend styles, hence it can interfere with any frontend styles that impose their own max-width.

@youknowriad
Copy link
Contributor Author

There is one thing I find quite confusing: In backend/editor there is also the wp-block class. Can this class be used to replace selectors like > * as used in frontend?

right now wp-block is only an editor class and it's important to support customizing the editor widths https://developer.wordpress.org/block-editor/developers/themes/theme-support/#changing-the-width-of-the-editor

Ideally, we'd just rely on something like .entry-content > * but themes are inconsistent so if we use entry-content on the editor, it's not certain the editor styles will match.

If we want to solve the alignment CSS in core and apply them automatically on the frontend like this issue suggests to make it easier for theme authors to support alignments, this need to be solved somehow.

@paaljoachim
Copy link
Contributor

What other actionable items is needed for this issue?

@markhowellsmead
Copy link

Moving my comments from #28964 to this issue, as requested. Additional related issue: #28966.

(As an aside: we've been using the parent-based rules from the original code examples of this issue since the beginning of 2019. This approach is extremely lightweight and has worked very well thus far.)

.wrapper > * {
    max-width: 600px; /* width of the regular blocks */
    margin-left: auto;
    margin-right: auto;
}

.wrapper > .alignwide {
    max-width: 1200px;
}

.wrapper > .alignfull {
    max-width: 100%;
}

This issues come when the HTML on the frontend is markedly different to the HTML in the editor. Because of the discrepancies, we're having to write more and more fallback code for the editor after each Core update. This is becoming difficult to manage and means that older Themes need to be reworked more and more often, causing us to run into problems when the Theme is uses across various installations and various versions of Core.

For example:

The HTML for a separator block on the frontend of the website is a simple hr with appropriate CSS class names. If the element is set to alignwide then the appropriate class name is directly on the hr. This is expected behaviour and allows me to adjust the styling appropriately.

In the editor, the hr stands alone if it is not set to alignwide or alignfull - i.e. it is a direct child of the blocks root container. If it is set to alignwide or alignfull, it is then wrapped with a div featuring a data-align attribute. The class names alignwide and alignfull aren't used on these direct children of the root container anywhere.

Because the wrapper isn't specific to the separator block (<div class="wp-block" data-align="wide">), it cannot be targeted with appropriate CSS styling rules. For example, using different CSS rules for a separator block which is set to alignwide.

I have added my own block using apiVersion 2 to output just an hr and the same problem applies. The editor wraps the hr with a div and the alignwide or alignfull class name isn't applied, in favour of the data attribute on the wrapping div. This means that even in the simplest example, we have to duplicate a fair amount of our CSS code; once for the editor and once for the frontend, each with different selectors.

@strarsis
Copy link
Contributor

@markhowellsmead: But what if an element of normal (constrainted) width should contain a widely or fully aligned element?
The element has to break out of the normal width element. This would only work with some CSS positioning and transform.

@markhowellsmead
Copy link

@strarsis Sure, the CSS for that is simple. I'm not sure of the design logic for this example and I haven't come across it in any of the Gutenberg projects we've worked on over the last two years, but it should be achievable. If the HTML logic is simplified and consistent across all blocks, the editor, and the frontend, then the CSS would be pretty easy to solve. The problem comes when the HTML nesting logic is completely different in the editor and in the frontend, which is currently the case.

@strarsis
Copy link
Contributor

strarsis commented Feb 15, 2021

Example for this kind of layout: https://css-tricks.com/full-width-containers-limited-width-parents/

@mtias mtias mentioned this issue Mar 3, 2021
12 tasks
@davidwebca
Copy link

I'll be watching this issue. I have the same problems as Mark over and over again. Align classes cannot be consistent through all blocks since they're not applied to all of them (ie wp-block-group) and when I add [data-align] css styles just for the editor, I run into specificity issues and have to backtrack. I now find myself needed to code multiple separate edge cases for the editor and front-end.

@youknowriad
Copy link
Contributor Author

Now that we landed this #29335 I consider this issue done. The new behavior is triggered by the presence of the "experimental-theme.json" file to avoid breaking changes for existing themes.

This also means classic themes can't be update yet to use it since it's still "experimental" but experimental-theme.json file is meant to be made stable in WP 5.8 (so soon).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Blocks Overall functionality of blocks
Projects
None yet
Development

No branches or pull requests