A few thoughtful layout components can go a surprisingly long way and help you lay out pages faster.
I’m currently having my brain rewired by Every Layout, the online book/course/thing from Heydon Pickering and Andy Bell. It describes how a few thoughtful, robust layout components that use shockingly little code with modern CSS can be combined to build nearly, well, every layout. It’s worth the money, but you can read about the Stack, the Cover, and the Sidebar components for free.
The Sidebar, in particular, is a revelation to me. You end up with a container that will put two child elements side-by-side, but also you can specify that one element should have an “ideal” width with flex-basis and the other element should take up any remaining space but if that remaining element is now taking up less than half the total space, both elements will collapse into a single-column layout and grow to the full width of the container and this stuff used to be weirdly hard but now it’s like 10 lines of CSS and you don’t even need media queries or container queries.
Anyway, while I’m picking up my little brain bits, I thought I’d share some of the layout components that I built at my last job that I found particularly useful to have in my toolkit.
Once built and refined, I used these all the time and they saved a ton of effort and code. At my old job I used React, but here I’ll link out to some implementations that reflect how I would build them today with Svelte. These would also be very doable with some CSS classes and maybe CSS properties and calc() for on-the-fly math, so don’t feel like these are out of reach if you don’t specifically use Svelte.
Like, even on the smallest scale, it’s awful: you have two things and you want some vertical space between them. Should you put margin-bottom on the top one? Or would it be better to put margin-top on the bottom one? What if you add more things? What if you change the order of things and now have to muck with the margins again? What if you remove one of the things and now there’s extra margins that you don’t need? Aw geez I haven’t gotten to the fact that vertical margins collapse OMG.
It doesn’t have to be complicated: set up a wrapper component that inserts space between things for you and stop thinking about margin ever again.
What the <Stack> does is insert the desired gap amount of space between all the direct child elements. That’s all! You’ll use this all the time! Wrap an input and a label and some helper text and put a tiny space between them; insert a big honkin’ space between unrelated things; wrap your main text markup to insert some space between whatever children paragraphs or lists or images or what-have-you.
When I made my own for work, it was coincidentally nearly identical to The Stack in Every Layout, so go check that out. Like that one, I handled the spacing with a * + * lobotomized owl selector and (ugh) margin-top between the children, but today flexbox gap is well-supported so I’d use that.
Here’s a variation that uses flex gap and calc() as a Svelte component. I have it set up so the gap space is based on 1rem, so the spacing scales with root text size. It has some real-world quality-of-life niceties like accepting a class from its parent in case you need to apply some other styles and a fullWidth property that can sometimes get you out of a jam if you’re using a <Stack /> as a child of some other flex/grid bits and it’s shrinking more than you’d like.
This works for both big and little tasks: put an icon next to some text; put an avatar next to a dialog box; align a clump of form buttons; put a pile of menu items in a line, make a quick-and-dirty sidebar + main layout.
<Row /> handles inline spacing between items for you with a gap property because thinking about horizontal margin also sucks. It also has some other quality-of-life niceties:
It uses flexbox for the layout, which means you mostly don’t have to think about if the children are inline or block-level elements because that doesn’t matter so much when they’re flex children.
It exposes flex align-items values (eg align="center") to control vertical alignment of the children.
It has a splitLast property that shoved the last element to the far end of the line, because that came up a lot in our designs. Your requirements may differ but this meant I didn’t have to also expose the justify-content properties.
It has optional wrap (adds flex-wrap: wrap) and overflow (adds overflow-x: scroll) properties as escape hatches when there were more items than could fit in a single line.
A nice bonus from using gap for spacing is you also get vertical spacing between items if they wrap into multiple lines. You can do all that with ugh margins but you have to do some math and use negative margins on the container to correctly handle wrapping and not waste space. Flex gap is simpler and clearer.
In use it looked like this, say, in a page’s header:
Once you’ve built <Stack /> and <Row />, you find uses for them everywhere. However, your designer will inevitably throw you an entirely predictable curveball with a bunch of different spaces in between things. Like they’re using whitespace meaningfully and helpfully instead of just wanting the exact same amount of space between everything ugh.
One way to get mix-and-match spacing is to nest some of our fancy components, but that looks gnarly real fast:
<Stackgap="4rem"><h1>Page title!</h1><!-- ok 4rem space between the title and the below --><form><Stackgap="2rem"><Stackgap="1rem"><Stackgap=".25rem"><label>Label</label><!-- 0.25rem space between these bits --><inputtype="text" /></Stack><!-- 1rem space between these Stacks --><Stackgap=".25rem"><label>Label 2</label><!-- 0.25rem space between these bits --><inputtype="text" /></Stack></Stack><!-- 2rem space between the above Stack and below div --><div><buttontype="submit">Submit</button></div></Stack></form></Stack>
Alright that could be much worse but with a big page and a bunch of different components it gets hard to keep track in the code how much space should be between things.
So when that got annoying or illegible, I’d reach for a simple component that I called <Space />, which is just an empty box that you could assign a height and width to.
So I’d probably unwrap a good chunk of the above like so:
…and that feels a lot nicer to me. It’s easy to scan and trivially easy to tinker with the spaces by just playing with the h properties.
<Space /> is also super-handy when you need, well something that took up some space. Maybe you needed some padding on the bottom of something but it wasn’t super clear on which element you would want to apply margin/padding or which of margin/padding was more appropriate. You don’t worry about that; chuck in a <Space /> and go for a pleasant walk.
The Svelte implementation of <Row /> that I linked to above has a simple <Space /> component that, like the others, uses calc() and CSS properties for the sizing, but, again, your own implementation might use some other system or mathematics for defining what size a typical “space” is.
There’s a little checkbox floating about that you can hit to show the outlines of the various components, for the curious:
Obviously do not really do this! Like, I’m using <Space /> in places where I should probably put some padding and I probably would do something more interesting in the header. And, ok, sure, I had to apply flex-grow: 1 a few places to make a few things expand where they ordinarily wouldn’t.
But this is a perfectly cromulent full page layout that doesn’t embiggen your CSS, it’s (mostly) responsive and functional on phones or big screens with zero media queries, it’s easy to fuss with alignment and spacing right there in the markup, and it came together in just a few minutes. That’s pretty neat!
If you’re looking at this and are all “gross why so many wrappers and unnecessary bits of markup", well… I do admire your sense of purity but have fun with margins!