1
text stuff mostly2
boxes, transform3
positioning, clicking stuff4
flexbox and gridπ your here π5
animation and other nonsense
Are you holding out for
A hero by the end of the night
She's gotta be <strong> and she's gotta be <em>?
I have excellent news. Your patience has been rewarded. We have finally, finally arrived at
FLEXBOX
oh, flexbox.
you gotta understand. putting things Next TO Each Other was the white whale of CSS. we had been through the goddamn wringer; at this point we had
-
tried absolute positioning, just giving each thing like
width: 33.3%; left: 33.3%;and kinda hoping it didn't fucking break -
tried floats, with each thing having
width: 33.3%; float: left;and completely falling apart if the browser rounded a pixel in the wrong direction -
tried
display: inline-block;for entire chunks of the page because hey it puts things next to each other. it also causes a lot of problems with whitespace and whatnot since now you're relying on text layout. so, you know. that's fun.
man. i remember being so excited for firefox 3 to be released, because it had a major css refactor that finally added support for inline-block.
that was june 2008.
apparently that same year is when flexbox was first conceived β by david baron, even, the same mozilla dev who did the css refactor. fascinating! i also didn't realize it was originally derived from ideas in XUL, the weird XML-for-UI thing that firefox used to use. firefox had an implementation of this very early on β which, uh, makes sense.
then in 2011 our very own @tabatkins took over! how about that. small world.
i'm telling you this because i... i need you to understand. i knew about flexbox so early on and it sounded like it would solve all the problems in the world. the first draft was published in 2009 but it was not until late 2015 that safari unprefixed it and we could finally, finally, finally use flexbox like it were a real feature.
i hope you can appreciate why this was a big deal as i tell you what it is. because, like. imagine trying to do all of this with fucking absolute positioning
what the hell is it
as previously alluded, flexbox is a way to put things next to each other. here is a flexbox with some children.
<div style="display: flex; height: 4em; gap: 0.5em;">
<div style="background: hsl(30, 90%, 70%); flex: 1; padding: 0.5em;">flex: 1;</div>
<div style="background: hsl(0, 70%, 80%); flex: 3; padding: 0.5em;">flex: 3;</div>
<div style="background: hsl(90, 60%, 70%); flex: 2; padding: 0.5em;">flex: 2;</div>
</div>and that's it. they go in a row. each box has a flex property which says how "flexible" it is, i.e. how much of the space it would proportionally like to take up. so the pink box in the middle, with flex 3, is three times wider than the orange box with flex 1, and overall it takes half the available width, because .
if you've done any UI programming in like, literally fucking any environment, this might be familiar! the idea of dividing space up between children seems to be fairly common. and now in the year of our lord 2015, only some 20+ years into having web browsers, we can do it in those too!
of course there are some details, and also some other things you can do with it. (and as usual there are longer guides but this is a whirlwind tour through the important stuff.)
the basics
all you really have to do is slap display: flex; on something, and you get a flexbox. specifically you get a block flexbox, so like any other block, it will default to filling all the available horizontal space. but of course, you know how to change that.
you can also do display: inline-flex; to get an inline flexbox, which is rather less common, but there's no reason not to allow it so here we are. i could paste that earlier thing right in the middle of this paragraph.
see, isn't that lovely.
speaking of display: note that once a box is a child of a flexbox, it doesn't matter what its display property says any more. it's not laid out like a block or like text; it's placed as a flex item.
of course with no other properties you'll just get everything crammed together. let's pare down the initial example:
well that's not very exciting. see, by default, flex items kind of huddle up to be as narrow as possible. since none of those item need to be any wider than zero pixels, they all end up... zero pixels.
this is often not what you want, so a common starting point is to slap flex: 1; on all the children. that means they all end up the same size:
delicious! well, a little cramped, because i removed the padding. which is a great segue into how the flexing actually works.
-
figure out how much space is available to the whole flexbox. this is usually the size of the container, unless you give a width yourself.
-
add up the "base" size of each child.
-
subtract. however much space is left over, divide it among the children, proportional to their
flexvalues. or if there's not enough space, take it away proportionally.
makes sense, right? ok good because i glossed over some details that we have to go into.
sizing and flexing
so the flex property is actually shorthand for three properties: flex-grow, flex-shrink, and flex-basis. these are the flexibility when sharing extra space; the flexibility when giving up space; and the base size. i am making all three of them your problem right now.
above i did flex: N;, which sets flex-grow to N, so we've already seen the effect of that. i'm lying a little; give me a sec.
flex-shrink is similar, but it's used when the items take up too much space. if you set it to zero, items will refuse to shrink any smaller than their base size (which might cause the whole flexbox to overflow its container, as visible with the following example on mobile). but you can set it to a number to get proportional shrinking:
the third item refuses to shrink beyond its base size, but the other two shrink in equal amounts. generally flex-shrink is either 0 (don't shrink) or 1 (whatever).
and that brings us to: what is the base size, anyway? it can of course be given as a length (or percentage), which makes it just be a... uh, length. so that case is easy.
but you see, well, the flex shorthand is a little bit clever, and it sets flex-basis in a way that probably does what you want, which has hidden an interesting secret from us so far. here are the more common forms of the shorthand:
- nothing, or
initial flex: 0 1 auto;
conservative default: take up only the space i need, and shrink if necessarynoneflex: 0 0 auto;
take up only the space i need, and do not flex at allautoflex: 1 1 auto;
start with the space i need, and share any space, but i don't care too much about the details- a single number
flex: N 1 0;
ignore how big i am; size me proportionally
so when i did flex: 1;, i was setting the base size to zero. that might seem counter-intuitive, but the reason is so you can do exactly what i did in the very first example: make some items whose widths are set to a specific proportion. remember, only the excess space is shared flexibly β so if you want all the available space to be shared flexibly, the simple solution is to make all the space be excess space, which means setting the base sizes (i.e. the non-flexible parts) to zero.
the others set the base size to auto, which means to use the item's width... but if it doesn't have one set, to use the width of the item's content. this generally means max-content (albeit with a couple extra flexbox-specific considerations). remember that guy? yeah. you can also set flex-basis to the special value content to explicitly get this behavior regardless of the item's width, or you can just use special width values like min-content or max-content directly. oh, and min-width and max-width factor in here too, somehow. lots of knobs to play with.
this is begging for a diagram, so here's a comparison between flex: 1; and flex: auto;, which i hope clarifies the difference:
everything here has a flex-grow of 1. in the first diagram, the entire items are all the same size, because their flex-basis is zero. in the second diagram, the extra space is the same size, because the flex-basis is auto, which means it's the width of the items' contents.
and of course you can mix and match these approaches however you like within a given flexbox.
alignment
flexbox defaults to stretching every item to be as tall as the box itself, but you can also let the items be their natural heights and align them vertically with align-items on the flex container. you can also use align-self on individual items to override that, which i'm doing here so you can compare them:
"wait, why are there both start and flex-start?" i hear you cry. it doesn't matter much here because we're aligning vertically, but in a moment we'll get to justifying horizontally. the difference is that bare start uses the text direction of the flexbox element β so, for english text, that means left-to-right and top-to-bottom. (if you were writing in arabic, it would be right-to-left.) on the other hand, flex-start uses the direction the flexbox is filled β which for all the rows we've seen so far is also left-to-right and top-to-bottom.
but! you can reverse the order a flexbox is filled, and then flex-start would be on the right. which i think is more intuitive. so just use that one. and all of this applies to end as well, just, at the other end.
there's one more alignment that's slightly more complicated: baseline. have we talked about the baseline? i don't remember. it's an invisible line that text sits on β like if you were writing on lined paper. descenders, like the curly bit on a "y", extend under the baseline. even if the text size changes over the course of a paragraph, everything sits on the baseline.
and that's kind of what align-items: baseline; does: align using the baseline of the first line of text. (i'm sure there are some interesting rules for what happens if a flex item starts with a block, since blocks don't have a baseline, but i don't know them offhand.)
more regular text
so it's nice for if your flex items all have a little title or something, and you want those to line up.
as for the other direction, there's justify-content.
the names might be a little hard to keep straight. the "justify" is because it's about what to do with any leftover space, similar to justifying text: you might want to shove everything to the left, shove everything to the right, or spread the space around. (of course, if you give the extra space to the items with flex, there won't be any left and this won't matter.) and as long as we're sticking with this metaphor, "items" are like words, and "content" is like lines.
a diagram is worth a hundred words:
flex-start
center
flex-end
space-between
space-around
space-evenly
the difference between space-around and space-evenly is that for space-around, the space at the edges is half the size of the space between items (so it's like the two end spaces add up to one item space), whereas for space-evenly, all those spaces are equal.
you can also use the gap property to add a gap between all elements, before flexing or justifying are considered at all. i've done that several times here so you can tell items apart. (margins on flex items also work to add gaps on individual items.) you can also give two values to specify row and column gaps separately, or use the individual row-gap and column-gap properties.
how could there be row gaps if everything goes in one row, you ask? oh ho ho! just you wait.
an interlude on how great this is
hopefully by now it's clear why flexbox is a nice tool to have. there are a lot of things you could do with it that you could do in other ways, but often with some surprise drawback, like something going wrong when text is longer than you expected.
for example, say you want a header buttressed by an icon on each side. hmm. well, absolute positioning can do that, right?
uh, right. absolute positioning means the images don't affect the box's height. well, no problem; we can give it a min-height equal to the image heights (though this only works because we already know the image heights). and some padding to leave room around the images.
okay, but the text looks kinda goofy, and there's one other problem, if the text gets longer or the window gets narrowerβ
okay, we can fix that by adding horizontal padding for the images to sit in, equal to the width of the images plus space on either side for them... which, again, we can only do because we know in advance how wide the images are...
great. but back to the text. there are two options here: we can set the line-height to be equal to the height of the whole box (because then the line of text spans the whole box, and the characters will be roughly centered within that line), but then there's a new consideration: if the text wraps...
A Succubus
well, that big gap in the middle isn't right. and the candles aren't centered either, come to think of it.
we can fix the candles, though. what we do is give them top: 50%;, which puts their top edge exactly halfway down the box. then we give them a negative top margin of half their height (which, again, only works if we know it in advance), in this case -1rem, and that aligns their centers with the box's center. absolute positioning can do a lot of tricks!
A Succubus
okay! but about the text. we can fix that by setting the line height back to something reasonable (say, 1.33), and then working out how much vertical padding it would take to center the text in an unwrapped box. the box's minimum height is 3rem at the moment, and the font size is 1.2em... we could either do some math ourselves or just use calc((3rem - 1.33 * 1.2em) / 2) to get the padding. and if we ever change the line height, hopefully we'll remember to change the padding, too.
A Succubus
and we did it, using only pretty old CSS features! (we also could've put the images as backgrounds to make the centering easier, but... well, see, multiple background images is relatively recent too...)
but a lot of what we did is kind of tangled together and interrelated, so it's not obvious at a glance why some of it is there... and if we hadn't thought to try shrinking the window, we may not have fixed some of these issues at all... and a lot of it depended on knowing the image heights, so if those change, all the numbers have to change, and if we don't know them in advance, none of this would've worked at all!
and then you remember that HTML tables could do this with much less fuss, and you start to wonder what the point of all this is.
OR
we could use flexbox. make the padding whatever. add a nice gap just in case. use align-items: center; to center everything. put the text in a <span> with flex: 1; text-align: center; so it eats all the excess space.
and that's it. done. the text is even centered better than before, and the whole thing is shorter than doing it with tables. now you can spend all that time you saved on nice little decorations like drop shadows.
centering things in a container
one more diversion! i think this is worth calling out separately, because until flexbox, it was the white whale of css. the general guideline was that you just could not vertically center one box inside another box unless you knew the size of something ahead of time, as we have just glimpsed with our absolute positioning adventures.
but now! try resizing either of these things!
wow! easy. all you have to do is slap display: flex; align-items: center; justify-content: center; on the container, and you get a single-item flexbox that centers its one child in both directions β no matter how big either of those are.
in fact, you can do even better... in some cases. see, any run of plain text inside a flexbox also becomes a flex item. so you can vertically center a string of text with only a single element. even if it wraps!
of course if you want to do any formatting, you'll need an extra element to wrap it β because hey <em>there</em> ladies is three flex items.
there is one other consideration! if you're doing this on truly arbitrary contents, especially some with a minimum width, like perhaps a string of text with white-space: nowrap;... you might run into a problem.
oops! we overflowed. worse, we overflowed to the left, where even overflow: auto; will not take us. that's no good.
there's an easy solution: justify-content: safe center;, which will fall back to being start if there's overflow.
ahh. much better.
wrapping
so flexboxes can also wrap. if you have more stuff than will fit in one row, you can just have them spill onto another row. remember that stuff i said comparing flex items to words in a line? well, this is like word wrap.
the flex and justify-content space-sharing apply to each row individually. here's some boxes with varying widths in a flex container with flex-wrap: wrap; and justify-content: space-between;:
you may notice that the 60% and 40% boxes didn't end up in the same row together. that's because 60% plus 40% plus the gap is more than 100%, so they wouldn't fit. that's a good reason to prefer flex to percentage widths! unless you're trying to demonstrate how wrapping works.
anyway this is where flexbox really made some eyes light up, because finally we could make, say, a reliable thumbnail grid that adapts to screen size. you sure as hell can't do this with tables:
Lexy
Cerise
Clover
Eve
Catarina
Gumdrop
0220
well. almost. flexbox doesn't care about aligning things into columns, any more than word wrap cares about lining up spaces in the middle of different lines. even a wrapped flexbox is, after all, still functionally one long row of things, just split into pieces.
reordering
it's a bit more obscure, but flexbox also lets you rearrange the order of the items, using the order property (which just takes a number). so even if your HTML has elements A B C, you can have them arranged as C A B or whatever else you like.
there are two reasons to care about this. one is that the pie-in-the-sky dream of CSS is to be able to radically redesign a page, without changing the HTML at all β a point emphasized by the css zen garden. and of course one major sticking point is that the order of elements in your HTML often determines the order of elements on the page. if you want to move your sidebar from the left side to the right, well, you probably have to move it from the top of your document to the bottom.
the other big reason is accessibility. see, most sidebars are on the left side of the screen, which generally forced them to come first (before the actual contents of the page) in the actual HTML. but that meant that screen readers would read the entire sidebar, on every single page, before reading any of the page itself. many ludicrous attempts to solve this with floats were... um, floated... but flexbox just chucked it in as a real feature.
this is not very relevant to CSS crimes, but it's the only other major feature of flexbox i haven't mentioned, so it felt weird to skip.
oh, and you can also just reverse the entire box with flex-direction: row-reverse;. items will start filling the box from the right, and if there's any space left over, it will be on the left. here's that very first example again, exactly the same, but with the direction reversed. (remember, it was originally 1 3 2.)
oh and this all works vertically too
you can do flex-direction: column;, turn everything i just said 90Β° (or, rather, reflect it across the main diagonal...), and it still works: alignment, justification, wrapping, reordering, etc. all the properties just switch directions β align-items now aligns items horizontally, because they're being arranged vertically, and so on. but that's it really.
(also, flex-flow is shorthand for both flex-direction and flex-wrap. no tricks for this one; you can just slap both property values together, like flex-flow: column wrap;. or even flex-flow: column;.)
the main difference in a column flexbox is that there's no "natural" height for the flex container, the way there's generally a natural width for a row. so the stuff dealing with excess space won't have any excess space to work with, unless you specifically give the container a height. but if you're making a flex column... you probably have that in mind already.
and that's it. that's flexbox. that's pretty much every feature it has. there's really not much to it, but it is remarkably powerful β especially when you start nesting flexboxes.
but you know what?
it's baby garbage. we already have something so much better.
interlude
but first! a property i didn't mention before: the misleadingly-named vertical-align. many a CSS beginner, and even expert, has fallen afoul of imagining that you could use this to vertically align things. and technically you can, just, not in the way flexbox does.
vertical-align aligns inline boxes, relative to the baseline. it's for stuff you're putting in a line of text, like an image or an inline-block. it has a bunch of values, including the somewhat maddening middle which almost does what you expect β it aligns the middle of the box with the middle of the letter "x" in the line's font. which is not quite in the middle of the line. which might be exactly what you want, or might be distinctly off. (of course, you can use margins or negative positioning to nudge it around.)
you can also give a length, which will put the baseline of the element that distance above the baseline of the surrounding line. which, again, is often... almost... what you want.
here's a few examples of inline blocks and how they get positioned, with lines through the centers of the boxes as well: middle and text-top and text-bottom and super and sub and... well, baseline is pretty much what you'd expect.
just thought i should mention that, since baseline alignment came up with flexboxes. now where was i. oh yeah.
GRID
this is the good stuff right here.
see, HTML tables were a pain, but one thing they would do pretty reliably is keep stuff arranged in a grid. which is a thing you often want for all kinds of reasons.
for a long time, CSS didn't have a good answer for that, so there was endless discourse about why aren't we just using tables then? until @tabatkins ended it once and for all by gifting us something that works better.
the idea of CSS grid is that you use display: grid; (or inline-grid) to make a grid container, and you define an invisible grid within it (though browser dev tools can make the actual grid visible). then you can arrange the item's children on that grid however you want.
now, CSS grid can do a lot. MDN has an entire lengthy tutorial series about it, and the fundamental grid property has so much possible syntax as to be nigh incomprehensible. but the good news is that simple cases are so simple that you can just say what you want and probably get what you expected β in fact i'm so used to "it just works" that i'm having to scrounge up details to make sure i'm getting them correct.
(honestly, you know what, i'm having an easier time understanding how this works by just reading the spec. that's kind of a poor reflection on MDN's reference docs.)
so i think it would be better to just give you some examples and explain how they work. if you want to delve into the gritty details of everything you can do, you're certainly welcome to do some extracurricular reading.
and the best part is that i've already used several grids in this post, so i have some samples ready to go! wow maybe this is better anyway; i feel like most of the demos i see are weirdly artificial and don't resemble anything i actually want to do.
fixed grid
this post began with a filmstrip sort of thing that, stripped of its styling and contents, looks like this:
grid: 1fr / repeat(4, 1fr);
let's break this down. (also if you're using devtools to examine these examples: there might be a button somewhere to show the grid. in firefox it's a little grid guy next to the element itself, in the inspector)
the rows are defined as 1fr. that's a list of row heights, with one element. (this is the rows template, corresponding to grid-row-template.) the fr is a new kind of unit, specifically for grid layout, that acts much like the flex property β but as a unit, it can be used freely in the container rather than needing to be on individual items.
so here that means one row, which is tall enough to contain all the items' contents. there won't be any extra space vertically (as with a column flexbox with no explicit height), so there's nothing to share, and the flexiness doesn't really come up.
(in the example above, i used 4em, since there are no contents to expand around.)
the columns are defined as repeat(4, 1fr). this is just shorthand for 1fr 1fr 1fr 1fr, as you might have guessed. this is like having four children with flex: 1;, so it splits all the available space up evenly.
the effect is to create a row of four equally-sized cells. so i could've done this as a flexbox, too. but i, uh, didn't.
i don't do anything special to specify where in the grid the children go, so i get the default behavior: the children are placed in order from left-to-right, top-to-bottom, one per cell. you can also have them go columnwise (top-to-bottom, left-to-right) using grid-auto-flow: column; β i don't think there's a way to do this via the grid property alone with a fixed-size grid. of course, in this case, the result would be the same.
swapping out flexbox
as a sort of intermediate step, you can replace a flexbox whose children all have flex: 1; with a single-row grid very easily:
grid: auto / auto-flow 1fr;
here, that first auto is being used as a height, so we're defining a single row. in a grid auto means... well, "do whatever is least surprising", in sort of the same way it does as a flex-basis. you'll get a row that's big enough to hold everything in it. (of course, in the actual example i used 4em again, but i wanted to show this too because it's probably more natural as a height than 1fr.)
but the columns look a bit different. auto-flow means to keep creating new "auto" columns, automatically, to fit however many children there are. i put five children, so i get five columns, and they each get the width 1fr.
so the result is like a flexbox with five children, all with flex: 1;. except i didn't have to put any properties on the children at all β the sizing is all controlled by the grid! whether this is good or not is up to you.
but the grid approach can get a little interesting, e.g. by listing more than one width for the auto columns:
grid: auto / auto-flow 1fr 2fr;
the whole pattern gets repeated. i think that's kind of cool.
keep in mind that since i'm using auto columns here, the grid will try to fill by columns rather than rows β it wants to avoid generating new columns until as late as possible. so if i had more than one row, i'd get this:
thumbnail grid
at last, we can create a thumbnail gallery, in its true and purest form. behold!
Lexy
Cerise
Clover
Eve
Catarina
Gumdrop
0220
hot damn. let's cut that down and see what's happening.
grid: auto-flow / repeat(auto-fill, calc(128px + 2em));
justify-content: space-evenly;
that's a little more complicated, but not too much. it's still rows slash columns. you may also notice that align-items and justify-content work on grid as well, in pretty much the same way.
here i've used auto-flow to make as many rows as necessary. (it also implies filling the grid by row, this time.) i didn't give any row heights at all, so each row will just be auto, which means... you know... just do it. just make a grid, in a normal manner.
and then the columns are repeat(auto-fill, calc(128px + 2em)), which is mostly stuff we've seen before. i used calc() here because the thumbnails are a fixed pixel size, but the padding on them is in ems. so the only surprise is auto-fill, which is fun β it means to repeat the given columns as many times as will fit in the grid. which is exactly what we want!
you can't use auto-fill with a less concrete width like 1fr β something, somewhere, has to be concrete. but if you want to share the remaining space, you can use the special minmax() function i mentioned in an aside above:
grid: auto-flow / repeat(auto-fill, minmax(calc(128px + 2em), 1fr));
what's actually happening here is that every column always has a minimum and maximum width. if you just give a regular width value, they're the same. the minmax() function exists for when you want to make them different. and that's what we want here: the minimum needs to be a concrete value for the sake of auto-fill, but the maximum can be a flex value to share whatever space is left once we know how many columns we have. (you could also use auto and justify-content: stretch;.)
(i have always been kind of puzzled by the way MDN describes minmax() and it only really clicked just now when i was reading the spec and it spelled out that there's always a min and a max. in retrospect, again, i don't know why. i'm curious if this description makes sense to you!)
entire goddamn website
welcome to the cool part.
see, one of the killer features of HTML tables was that you could have cells span multiple rows and columns. replicating that in CSS was... hard. even flexbox can't do it, not really.
but now...
grid:
"header header"
"sidebar body"
"footer footer"
/ 1fr 3fr;
holy moly what is this!
it's secretly all the same stuff. each of those quoted strings is defining a row; the heights are just missing. in fact i once again filled some in for the sample:
grid:
"header header" 4em
"sidebar body" 8em
"footer footer" 2em
/ 1fr 3fr;
the quoted strings define "grid areas". but you don't really have to care about that; the important part is that you can draw a diagram of where you want stuff to go. i put header twice, so the "header" area spans two columns.
the one catch is that the usual auto-population thing doesn't care about named areas at all and will still keep trying to put children in one cell at a time. so the actual contents of my demo look like this:
<div style="grid-area: header;"></div>
<div style="grid-area: sidebar;"></div>
<div style="grid-area: body;"></div>
<div style="grid-area: footer;"></div>
but if you're doing this, you probably only have a few big pieces to place, so that's not a big deal. and hey, this make a cool point: we can place things in a grid in any order we like, regardless of their order in the document. far out, man. tubular.
the 1fr 3fr after the slash is, of course, a list of column widths, and works exactly like it has before.
definition lists
HTML has this <dl> element, see. it takes alternating <dt> (t for term) and <dd> (d for definition) children. it's pretty slick for any kind of key-value list, and i've used it a couple times so far in this post.
by default, it looks more or less like this.
- cat
- has four legs, meows
- dog
- has four legs, does not meow
which, you know. sucks. this is just begging to be arranged in a grid. surely CSS can help us.
one wrinkle is that nothing is stopping you from having <dt>s or <dd>s in a row, and in fact this might be reasonable to do, if two items share the same definition or one item has several definitions.
- cat
- has four legs, meows
- common linux utility
- dog
- horse
- has four legs, does not meow
i've done this with floats before, and it's okay, but it's... not pretty. behold, grid:
- cat
- has four legs, meows
- common linux utility
- dog
- horse
- has four legs, does not meow
grid: auto-flow / 1fr 2fr;
the trick is to use grid-column: 1; and grid-column: 2; to stick the children into the right column. the auto-placement rules don't like to go backwards, and they'll still kick in to figure out the right row even if you give an explicit column.
(rows and columns are actually identified by the grid lines, not the space between them. which is kinda weird. in this case it doesn't matter, since "put this after the first column line" is the same thing as "put this in column 1".)
the borders are a bit goofy though. had to use some negative margins there. and i feel like there is surely a nicer way to do the column sizing that's more content-aware, but i don't have a clear idea in mind.
alignment again
so since there are now two full directions to contend with, there are two more alignment properties: align-content and justify-items. oh boy!
one little gotcha: flexboxes have directions (row or column), but grids don't. a grid might be filled one column at a time, but that doesn't change how it's oriented. so align-items on a grid always acts like it does on a row flexbox.
with the full four properties, though, i think it's easier to reason about:
-
"items" refers to the things being placed in the grid (which makes sense, since i've been calling them flex items and grid items), and the properties are about how to place them within rows and columns. the default is
stretch, so so far we've seen items expand to fill the grid cell, but they don't have to!"content" refers to the (invisible) rows and columns of the grid itself.
the set of values is different for items vs content.
-
"align" is in the sense of
vertical-align: it's, well, vertical. "justify" is in the sense of justifying text: it's horizontal.
they tried to make these names completely agnostic as to both the writing direction and the way the grid is filled, and it shows! here's how the four properties differ, assuming a typical row-filled grid:
-
align-items, like flexbox, is about where to place shorter cells within a row. it takes the same values. also like flexbox, it defaults tostretch, so all the cells are the full height of the row. -
justify-itemsis new, but it's about where to place narrower cells within a column. it takes the same values, and has the same default. hopefully it makes sense that this doesn't apply to flexbox β with everything in a single row, there will never be two items in the same column to align! -
align-contentis new...ish. it's about how to distribute the extra space between rows, if the grid happens to be taller than all the row heights.in fact, i secretly had to use this already β on both thumbnail grids, because i made them resizable! the default is also
stretch, so without usingalign-content: flex-start;on them, resizing the container would cause the rows to all become proportionally taller. and that's clearly ridiculous.but that's right, i said both, and the first thumbnail "grid" was actually a flexbox! this one does work on flexboxes too, in the very specific case of a wrapped flexbox with extra space available. that is such an uncommon case that i didn't even know you could use this property on flexboxes until i needed it for this post.
-
justify-contentis about how to distribute the extra space between columns. so it's just like flexbox, too, if you think of every flexbox item as its own column.
there are also now shorthand properties: place-items to give both item properties at once, and place-content to give both content properties at once.
you can also use align-self, justify-self, and place-self on individual grid items to override the defaults. of course, these take the same values as the "items" properties, since they apply to... items!
it's hard to show all of the possibilities off without absolutely ballooning the size of this post, but here is a fixed-size grid (300Γ300) with fixed-size rows and columns (100px, two each), align-content: center;, and justify-content: flex-end;. all that extra space becomes gutter outside of any cell, sort of like the space from gap.
on the other hand, this grid has no set height (and its width is min-content), but its rows and columns are all 120px. the items, however, are 80px, so they can be aligned within the rows/columns using place-self. i've also snuck a second grid underneath it, with the same size, so you can see where the cells actually are.
hmm, yeah, so you can give two values for place-self, to set align-self and justify-self at the same time. but that means you're putting the vertical value first, which... feels... weird. if i wanted them different i think i'd just use the two properties.
oh yeah, that thing about how bare strings of text also become flex items? true for grid as well. so now we have a new and even shorter way to center within a box: display: grid; place-items: center;. that will give you a grid with a single cell that centers its child within it.
if the text might wrap, you'll also want text-align: center; so the lines of text are centered relative to each other too.
of course, place-content: center; also works. hmm.
(the difference is that place-items will center the child within the grid cell, which remains the full size of the grid. place-content will shrink-wrap the grid cell to match the size of the child, and then center the cell within the whole grid. same result, here, but worth meditating on the difference.)
grab bag
there's various other stuff you can do and i don't have a cool demo that covers all of it, so here's something totally artificial that flexes a few miscellaneous properties:
grid: 4em / 1fr 2fr 1fr;
grid-auto-rows: 2em;
the grid itself is normal, but here i've also used the grid-auto-rows property separately. if you say grid: auto-flow 4em / ...;, what actually happens is that grid-auto-rows gets set to 4em. but it's entirely possible to have template (fixed) rows and have auto rows that are different; you just can't cram it all into the grid property alone. so here we get a grid that will always have a row 4em tall, but any newly-created rows after that will be 2em tall.
(really, you always have both template and auto rows. the grid has to do something if you put too many items in, after all. the auto sizes just default to, well, auto.)
the blue item has grid-column: span 2;. this is like colspan in an HTML table, and it basically does what grid areas do, just, sort of ad-hoc. it doesn't say which column to put the blue item in, so that's still automatic β but it makes the item span two columns, starting wherever it starts. the red item has the same property, but because there weren't two more columns left in the row, auto-placement skipped to the next row and put the red item there instead.
then there are some gray items with nothing special going on, so you can see the default sizes. each extra row is 2em tall because of grid-auto-rows, and the columns are 1:2:1 because of the 1fr 2fr 1fr in the grid property.
the yellow item has width: 50%;, making it half as wide as the cell. that gives it some wiggle room, so it also has justify-self: center; to center it within its column. (i actually typed the wrong property here the first time! these things are like USB-A: i always seem to get them the wrong way round on the first try.)
the pink item is similar, but it has height: 150%;, making it taller than the cell it's in. i've made the background translucent so you can tell it's overlapping the item above. CSS grids do make a strong effort to keep their contents contained within the grid, but they don't force it on you.
the pink item also has align-self: flex-end; to align its bottom edge with the bottom of the row.
the green item has no given width or height, but it does have place-self: center;. that makes it shrink-wrap and be centered within the cell, like the centering i mentioned above.
next is the orange item, which spans two rows this time, with grid-row: span 2;. this is mostly just to demonstrate that the three gray items after it get filled in like you expect; the grid doesn't consider a row-spanning item to have skipped the rest of the row it starts in, or anything.
and of course, if you don't fill the grid up, the remaining cells are empty.
but wait, there's more
grid can do a lot. like a lot. this post is already gigantic and i feel like i've touched on maybe a third of the features they crammed into this thing. here is a brief glance at some other stuff, on the off chance it tickles your imagination:
-
repeat()can take more than one size and will repeat the whole set of them -
you can do explicit placement, with spans, without using named areas or
grid-areaat all.grid-row: 1 / 3will start in the first row and span two rows (between lines 1 and 3), as willgrid-row: 1 / span 2. also, negative numbers count backwards from the other side of the grid (not counting auto rows/columns), sogrid-column: 1 / -1;will span the entire grid regardless of how many columns it has. -
you can name the grid lines, which might be more readable (or editable!) than using numbers, by putting the name in square brackets before the size:
grid: [foo] 1fr [bar] 1fr / .... this is how areas work under the hood! an area namedfoocreates a pair offoo-startandfoo-endlines in each direction.there is actually a Lot Of Stuff about grid line names but i am deleting what i'd written because it feels a bit out of scope here. i just learned a few things though
-
if you have large grid containing a smaller grid as an item, then you can use
subgridon the smaller grid. it goes in place of a list of rows or columns... or both, asgrid: subgrid / subgrid;. then you'll share the outer grid's rows or columns. (of course, this is mostly useful if the smaller grid spans a number of the outer grid's rows/columns.) this is handy for aligning things between what look like separate elements:thing athing blonger and more complicated thing athing b which is still alignedshort thing athis time thing b is longer and more complicated and it all just sort of worksthe padding on the containers even nudges things around inside the subgrid! the inner grid can have different gap sizes! this is even cooler than i thought actually
this has been in firefox since 2019 but unfortunately was only recently added to chrome, and is still behind a flag there, so it's not quite practical to use yet. sorry i think google is too busy making it possible to send faxes with javascript or what the fuck ever so they can sell more chromebooks, they don't have time for little things like arranging text on a screen
-
there is also the
masonrylayout, which is experimental in both firefox and chrome so i won't even bother with a demo, but it basically does the thing a tumblr archive does, where items get stacked snugly in columns (so there are no obvious rows). that's pretty cool and basically not doable without js otherwise. -
auto-placement will avoid putting multiple items in the same grid cells, of course, but no one said you can't do that. in fact i did this above to overlay the two grids in the "content" example. they will stack as expected, and can be rearranged depth-wise with
z-index. you can also usespanin creative ways to make items partially overlap. you can do many of the same things with absolute positioning, but grid might be more intuitive in some cases. -
auto-placement still works on items that
spanmultiple cells, but as we've seen, it might have to skip ahead β and leave empty cells behind. if you useauto-flow dense, each item will go in the first available space, thus filling those empty cells if possible... even if it means the items no longer appear in order. -
oh, yeah,
orderworks on grid too. -
i feel like
minmax()has more secrets i am yet to discover...
BONUS FEATURE:
aspect ratio
aspect ratio
i can't believe i forgot to mention aspect-ratio anywhere
remember back in part two, where i showed you a goofy-ass hack to force a box to be a square? yeah that's just a real thing now. if one of width or height is concrete (or enforced somehow, like by a block being the width of its available space), the other will be forced to whatever preserves the aspect ratio. basically like how images work
aspect-ratio: 1;
aspect-ratio: 2 / 1;
aspect-ratio: 4;
you can even write them as fractions. sick
that's all for now
you should now be empowered to arrange and space and align things to your heart's content!! you are now officially a CSS Adult
at this point all that's left is animation and a couple other oddball features! you have a functional understanding of CSS. if you want to test it, go back through the series so far and work out how all the non-examples work β stuff i didn't explain in depth, like the goofy stuff i put before the read-more, or the asides in this post. better yet, see if you can recreate them on your own just by looking at them!
but it's okay if you can't. christ, i'm not sure i could either. i spent way too long making a tiny fake website for this post oh my god
also this post is now over 120kb so i should probably Stop
1
text stuff mostly2
boxes, transform3
positioning, clicking stuff4
flexbox and gridπ your here π5
animation and other nonsense
