lexyeevee
@lexyeevee
  1. 1

    text stuff mostly
  2. 2

    boxes, transform
  3. 3

    positioning, clicking stuff
  4. 4

    flexbox and grid
    🌟 your here 🌟
  5. 5

    animation and other nonsense
Are YOU a CSS BABY who just wants to know how to put stuff in a row already?
fox
eevee's web zone β€” Mozilla Firefox
βˆ’
Γ—
← β†’ ⟳a> β―ƒ
πŸ”’ https://eev.ee/
Are your friends
making entire freaking websites in their posts, and leaving you feeling left out?
hey guys! finally finished the new layout, hope you enjoy!
Β© 1997 twilight sparkle
THIS IS A HENTAI FREE ZONE
Done

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

thegoodstuff

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

  1. tried absolute positioning, just giving each thing like width: 33.3%; left: 33.3%; and kinda hoping it didn't fucking break

  2. 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

  3. 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.

flex: 1;
flex: 3;
flex: 2;
<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 31+3+2=12.

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:

flex: 1
flex: 1
flex: 1

delicious! well, a little cramped, because i removed the padding. which is a great segue into how the flexing actually works.

  1. 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.

  2. add up the "base" size of each child.

  3. subtract. however much space is left over, divide it among the children, proportional to their flex values. 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:

flex-shrink: 1 but also some extra text to make us wider
flex-shrink: 1 but also some extra text to make us wider
flex-shrink: 0 but also some extra text to make us wider

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 necessary
none
flex: 0 0 auto;
take up only the space i need, and do not flex at all
auto
flex: 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:

flex: 1 plus extra text
flex: 1
flex: 1
flex: auto plus extra text
flex: auto
flex: auto

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:

flex-start
start
center
end
flex-end
stretch

"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.)

big text
regular text
bigger text
regular text
more regular text
small text
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?

candle emoji How To Summon A Succubus candle emoji

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.

candle emoji How To Summon A Succubus candle emoji

okay, but the text looks kinda goofy, and there's one other problem, if the text gets longer or the window gets narrowerβ€”

candle emoji How To Summon A Succubus candle emoji

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...

candle emoji How To Summon A Succubus candle emoji

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...

candle emoji How To Summon
A Succubus
candle emoji

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!

candle emoji How To Summon
A Succubus
candle emoji

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.

candle emoji How To Summon
A Succubus
candle emoji
candle emoji How To Summon A Succubus candle emoji

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.

candle emoji How To Summon A Succubus candle emoji

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!

wow! science is amazing

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.

wow! science is amazing. you know, this reminds me of a story from my youth

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.

wow! science is amazing. you know, this reminds me of a story from my youth

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;:

width: 20%
width: 30%
width: 20%
width: 20%
width: 60%
width: 40%
width: 20%
width: 30%

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.)

flex: 1;
flex: 3;
flex: 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:

first
second
third
fourth
fifth

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 to stretch, so all the cells are the full height of the row.

  • justify-items is 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-content is 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 using align-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-content is 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.

center
flex-end
flex-end flex-start
flex-start

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.

tada!

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:

hello
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-area at all. grid-row: 1 / 3 will start in the first row and span two rows (between lines 1 and 3), as will grid-row: 1 / span 2. also, negative numbers count backwards from the other side of the grid (not counting auto rows/columns), so grid-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 named foo creates a pair of foo-start and foo-end lines 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 subgrid on the smaller grid. it goes in place of a list of rows or columns... or both, as grid: 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 a
    thing b
    longer and more complicated thing a
    thing b which is still aligned
    short thing a
    this time thing b is longer and more complicated and it all just sort of works

    the 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 masonry layout, 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 use span in 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 span multiple cells, but as we've seen, it might have to skip ahead β€” and leave empty cells behind. if you use auto-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, order works on grid too.

  • i feel like minmax() has more secrets i am yet to discover...

BONUS FEATURE:
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

width: 40%;
aspect-ratio: 1;
width: 80%;
aspect-ratio: 2 / 1;
aspect-ratio: 16 / 9;
height: 5em;
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. 1

    text stuff mostly
  2. 2

    boxes, transform
  3. 3

    positioning, clicking stuff
  4. 4

    flexbox and grid
    🌟 your here 🌟
  5. 5

    animation and other nonsense

You must log in to comment.

in reply to @lexyeevee's post:

No. You can never use it. Would you like to use WebUSB instead. We made a laptop that only runs a browser and we're going to fuck the whole web up instead of admitting that was maybe a dumb idea

Hey. I sometimes work on writing the code in browsers that powers all of this CSS stuff. And while I haven't worked on grid, my understanding (and hearing from folks who know more about this) is that Firefox took shortcuts when implementing grid that make it simpler than the spec says, and implementing subgrid there was easier. Whereas Chrome didn't take those shortcuts, and making subgrid work was very complicated.

It's not a property – IIRC you can have a circular dependency between sizes, and in those cases the spec says the layout should be run in two passes, and Firefox does only one. And now that I look at the spec, this might be something specific to subgrid, rather than general to grid, so Chrome might have wanted to get it right when implementing it for the first time.

Chrome has also spent the last ~6 years slowly rewriting their layout engine (https://developer.chrome.com/articles/layoutng/), to make things like multi-pass layout not have to choose between slowness and bug-proneness. And I suspect they wanted to have the new layout system well in place before starting work on subgrid.

i thought i had a decentish knowledge of grid and flex, but this is what i wrote when i wanted the children of an element to all be equal width hours ago:

display: "grid",
gridTemplateColumns: `repeat(${number_of_children_easily_calculated}, 1fr)`,

which is bad (especially compared to display: flex and then flex: 1 on the children), but what's even worse is that my go-to before was to precalculate the widths in percentages. this is lame and bloats the css for no reason, but it works. except for the occasional one pixel gaps which (and i can't find where i did this?) i've fixed with outlines in the past which is just really eugh

anyway thank you so much for this post

thinking about it made it a lot clearer in my mind how grid and flexbox have control at opposite levels (sort of), which i think that emphasizes well. idk i've spent like half an hour on the last sentence, so grain of salt etc etc

I'm crying salty tears because this post is an incredible resource, and I can't use more than half of it because I'm stuck in the bullshit environment of "doing web for games UI" called Coherent Gameface (yes, that's really what it's called), which is a browser of sorts that has:

  • No display: grid πŸ˜₯
  • Yes display: flex! πŸ˜„
  • No support for gap of any kind 😨
  • minmax()? Buddy, we don't even support calc() if you try to combine % and px!

I have to cobble together solutions from Twitter Bootstrap circa 2013 and blogposts from before the grid era and it suuuucks. So thank you for the random asides in your post, because they actually help a lot!!

You're right. I messed up in that example. But let's say --font-size is 1.2em, and you try to get 50% of that variable with calc(), that's definitely a scolding and a finger-wagging from Coherent Gameface.

You're right again, I was thinking of addition. At the risk of "posting through it," here's what the documentation says:

CSS calc() support
Not supported within @keyframe definitions
Mixing β€œ%” and other dimensional units is not supported (e.g. 50% - 20px)

You should scroll down that page if you want to see some absolute insanity, by the way.

Excellent excellent post Eevee, this rules.

Really nothing to comment about in the main post, you did great. At the very end:

  • As Andreu said on another comment, the existing subgrid impls aren't correct (in cases involving intrinsic sizes) and the test suite didn't cover nearly enough interesting cases. We're doing it correctly, and part of that involved waiting for the refactor of our entire layout engine to be finished. (Also the layout team is different people than whatever other specs your complaining about. Talking about the merits of those other specs isn't useful in this post's comments tho, so I won't.)
  • Masonry is exciting in theory but we're pretty sure it's wrong to actually build it on top of Grid. Ian has an alternate proposal we just recently put forth for the WG. It'll still look pretty Grid-ish, tho.

i couldn't find anything specifically wrong with gecko's subgrid impl; it currently doesn't do a second pass on grid sizing, but that's not subgrid specific. (also i had to check the spec to work out a case where it failed, and i found some hints that fixing it might be a priority for this year.)

no shade on people doing layout work. but absolutely shade on google as an organism for having all kinds of ulterior motives and very little to dissuade it from pursuing them.

ok yeah just the ideas bandied around in the OP here make it sound like cramming masonry into grid is a clumsy hack. which i guess it is

fun fact: in looking through bugzilla earlier, i found someone requesting that the track count limit be raised... because they were faking masonry by spanning 1px rows, and only 1000 of those doesn't get you very far apparently

I'm trying to make side-by-side flex boxes with one box being a thumbnail of an image and the other being a text box, but no matter what I try there's a big awkward gap above the image.

<div style="display: flex; height: 12em; gap: 0.5em;">
  <div style="flex: auto; padding: 0.5em; align-items: top;"><a target="_blank" href="https://i.imgur.com/bGaosRT.png">
  <img src="https://i.imgur.com/bGaosRT.png" alt="Cain Juntley" style="width:200px">
</a>
</div>
  <div style="background: hsl(90, 60%, 70%); flex: auto; padding: 0.5em;"><b>Cain Juntley (Deceased)</b><br>A space adventurer who chose to come to Earth to fight ADVENT. May the knowledge there is non-hostile life out there be remembered.<br>
<i>Dan's fun facts: Cain Juntley was submitted by my friend Patrick AKA Shadowsandbag. He is from a tabletop RPG oneshot.</i></div>
</div>
<br>
<div style="display: flex; height: 8em; gap: 0.5em;">
  <div style="flex: auto; padding: 0.5em;"><a target="_blank" href="https://i.imgur.com/3vhw8w0.png">
  <img src="https://i.imgur.com/3vhw8w0.png" alt="Dennis 'Thin Man' Notanalien" style="width:200px">
</a></div>
  <div style="background: hsl(40, 60%, 70%); flex: auto; padding: 0.5em;"><b>Dennis "Thin Man" Notanalien</b><br>
Dennis is a normal human from a normal human city in the country of France and is NOT a "thin man" in disguise. That is all.</div>
</div>

Additionally, here is an attempt to make a 4-image grid.

<div style="display: grid; grid-template-columns: auto auto; margin: 0px auto;">
  <div style="padding: 5px;">
    <img src="https://i.imgur.com/qPuaazI.jpeg" alt="A sectoid using mind control">
  </div>
  <div style="padding: 5px;">
    <img src="https://i.imgur.com/usEaIIq.jpeg" alt="The Wanderer getting mind controlled.">
  </div>
  <div style="padding: 5px;">
    <img src="https://i.imgur.com/3JOwzta.jpeg" alt="The Wanderer shooting Cain Juntley.">
  </div>  
  <div style="padding: 5px;">
    <img src="https://i.imgur.com/3uJi7NR.jpeg" alt="Cain Juntley's dead body getting knocked to the ground by gunfire. He disturbs the foliage and lands on the body of an ADVENT troop.">
  </div>
</div>

Big awkward image gaps have been endlessly confounding in my attempts to display images.