Skip to content

Latest commit

 

History

History

45-btconf-2018

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Box Alignment

For beyond tellerand 2018. This is sort of a transcript of the talk.

Hello everyone! Today's talk is all about boxes. So expect to hear the word ‘boxes’ quite a lot. If you don't like boxes… Why not? I mean, I think boxes are great.

Anyway, my name is Hui Jing. I'm from Malaysia, used to play basketball full-time, fell into working on the web because of it. I have a blog full of CSS posts (among other things) and am a Mozilla TechSpeaker, which is an initiative by Mozilla to support technical evangelists in regional communities around the world by providing resources and funding.

If this sounds like something you're interested, applications for the Winter cohort are now open.

I'm currently a Developer Advocate at Nexmo, which is a completely new role for me. So we'll see how this pans out. Out of curiosity, has anybody heard of Nexmo?

For those who haven't, Nexmo is a platform which makes it easier for developers to integrate communications into their applications by providing APIs for messaging, voice and authentication. If you'd like to find out more, feel free to have a chat with me. I also have stickers.

I visited Berlin for the first time in my life earlier this year at CSSConf.EU. There, I met Lara Schenck, who said this in her talk: that CSS developers are programmers of boxes. I found that a really good description of what we do.

The visual web is made up of boxes.

Many different types of boxes.

Boxes nested within other boxes. And all these different boxes happen to behave differently. In order to make sense of how to arrange these boxes, or align them, we must first understand them.

So where do these boxes come from anyway? Well, they are generated by CSS.

On CSS

CSS has come a long way since 1996, starting out as a simple style sheet mechanism allowing authors and readers to attach style to HTML documents. Today, CSS still does that, it is still a language that describes the rendering of HTML in various media, but the vocabulary of CSS is significantly larger.

I'm around the same age as the web, so by the time I got around to using it, CSS had been around for a couple years. The first browser I ever used was Netscape Navigator 4. I remember a lot of tiled starry backgrounds, and blinking rainbow coloured text, those were big back then. But I am very glad CSS was invented, and has evolved to what it is today. And that the web did not end up being a giant fax machine.

Before we can talk about box alignment, we need to talk a little bit about the boxes themselves. Out of curiosity, I'd like to ask how many of you read CSS specifications? This is a screenshot of section 4 of the CSS1 recommendation published in 1996 and can I just say, I love the ASCII diagrams in that document. And that textured background? Classy.

The box model has been around since the earliest days of CSS, grouping all rendered elements into 2 categories of boxes, block-level or inline. Fun fact: upon further perusal of this document, I discovered that, at the time, CSS was not meant for doing layout, just a little something to think about.

CSS2 had expanded the formatting model, which is what we know now as the visual formatting model. This model determines how user agents process the document tree for visual media. And one particular sentence in the specification stood out to me. It reads: “In the visual formatting model, each element in the document tree generates zero or more boxes according to the box model”.

How do you generate zero boxes? That sentence out of context, seems somewhat bizarre. In order to make sense of it, I dug a bit into how browsers worked to render stuff.

On browsers rendering stuff

Rendering engines parse the source document into objects the browsers can understand, but before it can actually render anything, an intermediary structure, called a box tree, is generated. This is a representation of the formatting structure of the document. CSS will assign a computed value for each CSS property to every element and text node in the source tree.

One of those CSS properties is display, which tells the browser what type of box to generate. So usually, it's a one-element one-box situation, but some values can generate more than one box, like list-item, which generates a principal block box AND a marker box, for the bullet. When we apply the value of none, this causes the element not to generate any boxes at all. And that's why generating zero boxes makes sense. Cleared that up, now.

After the browser generates the boxes, it'll have to lay them out, and the layout of these boxes are dependent on several factors:

  • the box dimensions and type
  • the positioning scheme used
  • relationships between elements in the document tree
  • external information like viewport size, intrinsic dimensions and so on

On positioning schemes and formatting contexts

Positioning schemes. To be honest, until I read the spec, I didn't know this was a thing. But it's important because there's some terminology introduced here that pretty much sets up the remainder of the talk.

There are 3 types of positioning schemes, normal flow, floats and absolute positioning. Boxes in a normal flow will belong to a formatting context. And right now, we have several of those. Relatively positioned boxes remain part of this normal flow.

When we float a box, it is first laid out in the normal flow, then taken out of it and shifted as far left, or right, as possible. When a box is absolutely positioned, it's removed from the normal flow altogether, and given a position with respect to its containing box.

A formatting context is the environment in which a set of related boxes are laid out. And different formatting contexts have different rules for how they lay out the boxes within them. Boxes can generate new formatting contexts, and create a new environment where the layout of its children is mostly not affected by the rules and contents of the environment outside itself.

I say mostly, because certain formatting contexts can allow interaction between their contents. For example, line boxes within an inline formatting context can interact with floats belonging to the block formatting context containing them all.

On the display property

Our next very important property is display. The CSS Display Module Level 3 is meant to replace and extend the display property from CSS2. Some of the things I mention may not be implemented in browsers yet, but do paint a picture of future CSS to come.

It used to be that a single-keyword syntax for display was enough, because we only had block boxes, inline boxes and table boxes. And they could pretty much exist in the world on their own. Flexbox was the first module to introduce the concept of a parent-child relationship between a box and its children. And Grid uses that model as well.

In Display Level 3, the single-keyword syntax is still very much valid, but each of these values have a longer “full” version, that indicates how the box itself behaves, and how its children behave. We have the inner display type, which determines the kind of formatting context the box generates, and this affects the child boxes within it. And we have the outer display type, which dictates how the box itself participates in flow layout.

This table is directly lifted from the specification, and fills up 2 slides. If you pay attention to the ‘full’ column, notice that the outer display property is either block or inline. I'm glossing over run-in for now because its implementation status has been iffy for a long time.

There's a part of the specification that is relevant for browser vendors, probably not so much for web developers, that explain how certain layout effects require the blockification or inlinification of box types to ensure a box is matched to its context. Don't you love invented words?

As far as I know, the multi-value syntax is not supported in any browser, I've only managed to find an entry for it on Firefox's bug tracker, and none from the other browsers but if someone here can verify this for me, I'd appreciate it greatly. Out of this long list of values though, only flow-root is a brand-new value. When a box has its display set to flow-root, it generates a block-level block container that establishes a new block formatting context.

On the block formatting context

Earlier I mentioned that when a new formatting context is established, boxes within this new context are largely shielded from their outside environment. Which can come in pretty handy at times.

There are a number of ways to establish new BFCs:

  • You can float a box
  • Or position it absolutely
  • If you make the box an inline-block, a table-cell or table-caption, that can give you a new BFC
  • Or you could set a block box's overflow value to anything but its initial value of visible
  • And the new way to do it, via display: flow-root

A block formatting context, which I will abbreviate to BFC, is the context that block-level boxes participate in. Boxes are laid out one after another in the block-flow direction, which is determined by a box's writing mode. Margins along the block flow direction between boxes within the same BFC will collapse. This is otherwise known as the infamous collapsing margins phenomenon.

So what good is a new BFC? I'm glad you asked! Remember that I mentioned a new formatting context will generally contain the boxes within it? If collapsing margins isn't your thing, then this might help. Here, both the green and orange boxes have a margin of 0.5em set on them, but the top margin of the first box and the bottom margin of the second box has collapsed into their parent.

Making the parent a new BFC will solve this problem. Any of the methods mentioned earlier will work, so let's try a commonly-used one, overflow: hidden. Right now, both the green and orange boxes are block boxes in the same BFC, but if I apply display: inline-block to the green box, the margins between them no longer collapse.

This is next example is exactly the situation I described earlier where contents within 2 different formatting contexts sort of end up “interacting”. To prevent the inline boxes from poking into the space below the floated box, we can make the p element wrapping the text a BFC.

Let's try a overflow: auto here, and now the text will stay in its own lane. It's probably not the most semantic thing to do, but setting display to table-cell works too. We've essentially made the float and the text belong to separate formatting contexts so their contents are now contained within their own environment.

And lastly, this may be the most common use-case for establishing a BFC, as a technique for containing floats, or sometimes we may say clearing them. This situation happens when all the items within a container are floated, and the parent container ends up with no height. This is because floats are not in the normal flow, and no longer affect the height of the parent.

Establishing a new BFC let's the parent contain the floats, so this time let's try display: flow-root, and now the parent box wraps its children nicely. Applying a float to the parent box, or setting it to display: inline-block also works, but the width of the parent will shrink to fit its children.

On the inline formatting context

If block-level boxes do block formatting contexts, then naturally, inline-level boxes will participate in an inline formatting context, which I will also abbreviate to IFC. The rectangular area that contains the boxes which form a line is referred to as a line box. You can think of a paragraph as a stack of line boxes. Line boxes can be a bit tricky, because we can't directly see them from the source, they are generated based on how the content needs to be laid out.

This is an example of a block box that contains 5 inline boxes, the block box in this case, being the p element. For now there is enough room so all 5 boxes fit into a single line box. But when there isn't enough room, inline boxes can be split across multiple line boxes. Splitting an inline box is like slicing a Snickers bar. The cut part won't have any chocolate coating on it, so margins, paddings and borders don't apply to the “split” part of the box.

Only margins and padding along the inline axis have any visible effect on the inline box. I'll put some background colour on so you can see that even though padding is applied all around, the distance between the line boxes do not change. Though there could be some interesting things from a design perspective that might work here. Not sure, maybe throw in some blend-modes (refer to screen).

On inline alignment

Now that we're about halfway into the talk (I think), it's probably time to move onto the alignment part of things. Aligning boxes along the inline axis, is a reasonably straight-forward affair. We can use the text-align property, and its values of left, right, centre and justify.

Just note that there has to be excess space within the line box for this to work. Also, text-align comes into play only after all the required line boxes have been generated and all the inline boxes have been laid out accordingly.

A line box will always be tall enough to contain all the boxes within it. It can even be taller than the tallest box it contains. Shorter inline boxes can be aligned along the block-axis with the vertical-align property. vertical-align is one of those properties that trips up people who are new to CSS, because it is only applicable to inline-level and table-cell elements.

But, come on, there's no way you'd know that if you read it at face value, right? And it's key to remember that vertical-align controls the alignment of the inline boxes within the line box, not the alignment of the line boxes themselves.

The height property does not apply to inline boxes. The height of an inline box is determined by their font, and their line-height. Here's where the spec gets a bit laissez faire about things because user agents are free to decide if they want to use the em box or the maximum ascender and descender of the font to calculate this value.

You can use absolute length values, like ems or pixels, to set the height of an inline box. But we can also use unit-less numbers, which the browser will multiply by the element's font-size to get a computed value. If you use a percentage, it will also be multiplied by the element's font-size, not the height of the parent element, which is a more typical resolution for percentage values.

So let's have an illustrative example. Before Flexbox became widely adopted, there were a number of techniques for centring things along the block-axis. One of them was this inline-block technique I first discovered from a CSS Tricks article by Chris Coyier, called Centring in the Unknown. It involves aligning the target block with a pseudo-element that is as tall at the parent container.

An inline-block is an inline-level block box, also known as atomic inline because it participates in an IFC as a single opaque box. Setting both the pseudo-element and the child box to inline-block means they are both in an IFC. Because the pseudo-element is as tall as the wrapper, the vertical-align property can be used with the block of text.

On the flex formatting context

And now, we move on to another formatting context, the flex formatting context. Different formatting context, different set of layout rules. Some highlights of the flex layout model include in the fact:

  • the flex container's margins will not collapse with those of its children
  • float and clear do not apply to flex items
  • vertical-align, no effect on flex items either
  • flex items can still be taken out of flow with absolute positioning
  • flex items are flex-level boxes, not block-level boxes

I've been using the terms block direction and inline direction for the entirety of the talk thus far. And now I'm going to introduce a new type of direction, called the flex direction.

Think of the flex direction as the direction which flex items flow. Flex layout is fun because flex items can flow in any physical direction depending on the interplay between the flex-flow, flex-wrap and writing-mode values of the flex container. The main axis is the primary axis flex items are laid out, and the cross axis is always perpendicular to it.

Flex items are laid out within flex lines. These containers are a little bit similar to line boxes, in that you can't see them in the source, but they are used for positioning and alignment of flex items. We can have single-line containers or multi-line containers based on the flex-wrap property.

A single-line flex container will lay out all its children in one line, even if that means overflow, and this is the default flex container behaviour if you don't explicitly set a flex-wrap value. Setting it to wrap will cause flex items to break across multiple flex lines. Additional lines are stacked along the cross-axis, and the direction of this stacking is affected by the flex-wrap property.

Combinations of the writing-mode property, the flex-direction and the flex-wrap property will determine how your flex items will flow.

  • flex-direction -> row, row-reverse, column and column-reverse
  • flex-wrap -> wrap-reverse, then together with row-reverse
  • writing-mode -> vertical-rl, vertical-lr, sideways-lr

So really, many many possibilities.

Also wanted to point out that there are right-to-left languages, so just multiply everything by 2, and voila, 64 potential combinations. If you do take the time to try out every combination, don't worry, you won't get 64 different results, because a number of combinations do end up giving you the same end result.

On auto-margins

Alright, time to align some boxes. I'm going to start with something that is somewhat direction agnostic. For flex layouts, margins are distributed before alignment properties kick in.

Any positive free space will be distributed to auto margins in their respective dimensions. So if I set a margin-left: auto, Boxie ends up all the way on the other end of the container. And if I do a margin-top: auto, Boxie gets sent all the way down.

If we don't specify a dimension, then any free space will be equally distributed on either side of the flex item. And I love showing this example to people who have just started out with CSS and ran into a wall when it comes to centring items.

Set a margin: auto and boom, 1-line solution to all your centring problems. A small caveat here is that this trick works best if you have only 1 flex child.

This is one of my all-time favourite talks by Elika Etemad AKA fantasai, at CSS Day 3 years ago. She works on CSS specifications, and is one of the few people in the world that can answer any question you could possibly have about them. So this talk is definitely worth watching.

A major evolution in web layout is the ability to position and align content in both the inline and block directions.

Alignment along the inline axis was generally well supported from the start, especially for languages that were read horizontally from top-to-bottom. Moving text or blocks of content horizontally wasn't too complicated, we had text-align, we could use auto-margins to centre blocks.

But vertical alignment required a lot of workarounds, a lot of hacks and frustration. Luckily, CSS is not a fixed technology. Changes were introduced, improvements were made, and now we have a suite of tools for two-dimensional alignment and layout.

So back to the flex formatting context. For the most part, we are going to have multiple flex items within our flex container. Box alignment gives us 4 properties to align and/or distribute these flex items. justify-content helps distribute free space left over after flexible lengths and auto margins are resolved. Like this space I have on the right that is not quite 5ems. The initial value is flex-start but we can position the content with flex-end and center, as well as distribute the space between flex items, using space-around, space-between and space-evenly.

Just some illustrations to show how the values work.

So the next thing is to align along the cross axis. Here we have flex children of various different heights so there is space within the flex line for them to be aligned.

align-items sets the default alignment for all the flex children at once. And the initial value is flex-start, but again, we can do flex-end and center. And we can also stretch out the children to fill up the available space.

Another value that can be used is baseline, which aligns the baseline for every flex item so all your text lines up rather neatly. If we want to tweak the positions of individual flex items, then we'll make use of align-self which does the same thing, but for a single flex item.

Another bunch of illustrations to show how things work. I really didn't know what to do about the baseline value, so this it, sorry.

This header was lifted directly from the specification, because I couldn't think of anything better. If a flex container has more space along the cross axis than necessary to contain all the flex children, the flex lines stretch to fill up all the space. Flex lines, not flex children.

The property that controls this is align-content. Only multi-line flex containers will ever have free space along the cross-axis for us to do alignment stuff. Flex lines in single-line containers stretch to take up all the space.

If we don't want the default value of stretch, which sometimes cause unwanted behaviour like in this example, we can pack the flex lines and position them accordingly within the flex container (flex-start | flex-end | center). Or adjust the distribution of the flex lines within the flex container (space-around | space-between | space-evenly).

On the grid formatting context

I'd be remiss if I gave a talk about box alignment without mentioning how it works for Grid. The layout rules for a grid formatting context are somewhat similar to that of a flex formatting context.

  • the grid container's margins also do not collapse with the margins of its children
  • float and clear don't really do anything to a grid item
  • neither does vertical-align
  • grid lines form the boundaries of each grid items containing block

And these are the terms used whenever we discuss CSS grid. Grid lines are referred to by their numerical index, which starts at 1 and not 0. You can use a negative index or even name grid lines, if you want to.

Eventually, the CSS working group decided it made sense to come up with a cohesive and common box alignment model that could be shared across all the different formatting contexts. And that's the CSS Box Alignment Module Level 3, which define the 6 properties that control alignment of boxes within other boxes.

When using Grid, all 6 properties are relevant, however, when using Flexbox, self alignment along the main axis, is not applicable. justify-self and justify-items have no effect in a flex formatting context, because there is more than 1 item in the main axis. In the future, these box alignment properties will be applicable to block formatting contexts as well, making alignment a much more straight-forward process than what we have now.

How many people have trouble remembering which value affects which axis? Justify versus align? Is there anyone here who has not used Microsoft Word or any word processing software before?

If you used such software before, I will make the bold assumption that you have seen these 4 lovely icons before, and you know they are used to justify text. That is, to move it along the inline axis. Since there are only 2 values for 2 axes, if justify is along this direction, then align must be for the other direction.

The justify-content and align-content properties are known as content-distribution properties. And we've already seen them in action in a flex formatting context. This table is a summary of the values we've covered thus far for content distribution.

The values that we covered for flexbox are applicable for grid as well, except that instead of flex-start, we'd use start instead. If you do end up using any flex-prefixed values in a non-flex formatting context, the browser will resolve away the prefix. Let's just verify that works. You're free to use the start and end for flexbox though.

And now that we've sorted out the align versus justify situation, I want to bring your attention to a short-hand for setting both values in a single declaration. This short-hand uses the word “place”. So in this example, it will be place-content.

The first value sets align-content, while the second sets justify-content. If you only enter a single value, then justify-content will default to the same value as align-content.

The justify-self and align-self properties are known as self-alignment properties. These properties allow us to control how the content of each grid item is aligned within the grid area it's been allocated to. The default state is stretch, where the content stretches to fill the entire grid area.

We can adjust the alignment of content within the grid item with start, center and end. Once we apply any of these values, the grid item shrinks to fit its contents along the respective axis of alignment. The place-self short-hand is also available here.

Finally, we have the align-items and justify-items properties, which set the default align-self and justify-self behaviour of all the items within the grid.

Similar to flexbox, except that justify-items does not apply to flexbox. And here you can also use the baseline value to make the text within your grid items to line up neatly. The place-items property is available, if you'd very much prefer a single-line declaration.

Alignment keywords, or the values that we use in these various alignment properties, can be classified into 4 categories. We've covered distributed alignment, touched on baseline alignment, as well as gone through positional alignment.

There are also keywords for managing overflow. Let me explain. If the contents within a container are larger than the container itself, overflow occurs. That's when we get scrollbars, depending on the value of the overflow property.

But there might be instances where, an alignment mode sends part of the box out past the viewport's start edge, where you can't even scroll to get to it, like this example right here.

To exercise some semblance of control over such behaviour, we can make use of the safe and unsafe keywords. So if I set the safe keyword here, it will override the alignment mode and make it behave like start in an overflow situation. Using unsafe means the browser will respect the specified alignment mode regardless.

With all these additional tools we now have in our CSS toolbox, the standard of graphic design we can now achieve on the web has vastly improved. I'll attempt to back up this claim with an example. This is a page from László Moholy-Nagy's book, Malerei, Fotografie, Film.

When I first saw this poster I thought to myself, this looks really grid-able. It's a striking design with bold black borders, and content aligned in different configurations. And here's where all the alignment properties that I covered at length come in really handy.

The code shown here is abridged to show mainly the layout code, but even the gear and arrow can be made with pure CSS, with some help from the box-shadow property. At first glance, it might be tempting to reach for the self alignment properties, apply them to the grid items and call it a day.

However, that approach will not work for in this case. In order to align the internal content of each grid child without disrupting the rendering of those thick black borders, we'll need to make each grid child a flex parent.

Because by default, the value of a grid item's alignment is stretch, where it fills up the entire space of the grid cell. Once an alignment is applied to the grid item's contents, it will shrink to fit its content. Making each grid item a flex container allows us to use box alignment properties to adjust the position of the grid item's content while keeping the borders at the edge of the grid cell.

Wrapping up

The very first time I gave this talk, I wasn't really sure how much I could talk about box alignment, but that was because I hadn't really dug into what box alignment was all about. The more I dug, the more I realised, hang on, I'd been using all these techniques without really understanding how they worked.

So I don't know about you, but this has been a great experience for me. But I do hope that you find some of the things I covered useful to you and that you enjoyed this talk as much as I enjoyed giving it.

Here's a list of links and resources.

Thank you all for your kind attention.