Ditching Manual Height Math at the Bottom of the Page
The taller brand column needed no height math and no JavaScript at all: CSS grid stretch plus one auto margin did the entire job.
TL;DR
While refactoring a site footer, the author nearly wrote JavaScript to measure column heights before realizing CSS grid's default stretch already equalized them. Pinning the company line to the bottom took only flex column plus mt-auto. The lesson: inspect defaults first, since two class names beat any runtime measuring script.
It was 00:46 when I pushed commit f4091ea, a refactor of site-footer.tsx on a corporate website. The plan looked trivial: a brand block on the left, link columns on the right. The catch sat in the first column. Logo, certification badges, and a company line pinned to the bottom, while the link lists next to it stay short. My first instinct was embarrassing in hindsight: measure the tallest column with JavaScript and force the others to match.
I almost wrote a useEffect that reads offsetHeight and stuffs the value into state. I had done that in older pre-grid projects, and my fingers remembered the way. Then I opened DevTools instead, checked the computed styles of the grid container, and felt a bit silly.
Stretch Was Doing the Job All Along
Grid columns stretch by default: the initial value of align-items is stretch [3], so every column already matches the height of the tallest one. No script, no fixed heights. The only missing piece was vertical anchoring inside the brand block. Make it a flex flex-col container, put mt-auto on the company line, and the browser pushes it to the bottom of the stretched column. There is no justify-self on the flex main axis; auto margins are the documented way to shove an individual item around [3].
I tested it in the inspector: remove the auto margin and the company line floats up next to the badges; add it back and it snaps to the bottom edge. Two class names replaced the whole measuring hack I was about to write.
The check is boringly repeatable, which is the point. Open the inspector, click the grid container, look at computed styles. If align-items still reads stretch and the columns refuse to match, something is overriding it: an align-self on one item, or the container is actually flex with alignment changed. Those are the symptoms that send people hunting for a JavaScript fix, when restoring the default is the entire fix.
Below the desktop breakpoint the whole thing reflows: two columns at medium widths, a single stack on phones, brand block first. Nothing about the pin changes. The company line sits at the bottom of its cell whether that cell is a quarter of the screen wide or the full width, because stretch and the auto margin do their work at every size.
Why Bother Structuring It at All
The Nielsen Norman Group calls this area a cost-free addition to the user experience [1]: it never interrupts people who found what they needed up top, and it quietly serves those who did not. Their research also notes that users often scroll straight down hunting for contact details, and that repeating the global navigation down there, the doormat pattern, is legitimate on long pages. Semantics come free too: a <footer> element as a direct child of body applies to the whole page and typically carries copyright and contact information [2].
One distinction kept me honest here. Repeating the primary navigation at the bottom is a doormat, and it works because the top already carries the same links in context. Duplicating every page on the site down there is a different thing entirely: a sitemap dumped into a column, which nobody scans and search engines learned to ignore years ago. The commit trimmed instead of added, three focused link groups instead of a directory.
The Decisions Worth Keeping
Equal columns felt wrong once I laid out the hierarchy: the brand block needs breathing room, the link lists do not. So the grid went asymmetric, lg:grid-cols-[1.4fr_1fr_1fr_1fr], an arbitrary value that Tailwind documents for exactly this kind of precision [4]. Column headings dropped to text-xs with tracking-widest so they stop competing with content, and every group stays a real list, because structured lists are how assistive tech gives orientation [5]. The utility bar splits cleanly: copyright left, certification chips right, all of it at a readable size, because tiny illegible text down there just trains visitors to stop looking.
The lesson I kept from that night: check the defaults before reaching for a script. Stretch plus one auto margin covered a pattern I had assumed needed runtime measuring. The measuring hack never got written, which is my favorite kind of code review.
Sources
[1] Nielsen Norman Group: Footers
[3] MDN: Aligning items in a flex container