A fake newpaper logo, very ornate like you get on broadsheets, saying Ninja Pixel Times.

Newspaper layout, on the web.

Matt Michel in Cambridge

This website is a little playground for me, I’m a developer using it as a tool to become a better designer and one particular medium I’m drawn to for inspiration is print; I’ve been subscribed to WIRED magazine for a few years now and just love their layouts and their graphic design. I’ve dreamed of recreating their pages digitally, along with classic broadsheet newspapers like the New York Times and Financial Times (which I've loosely based the style of this page on). One thing that held me back was figuring out how to incorporate columns of text onto a web page. At first I just didn’t think it was possible to do this in CSS, recently I discovered that there is actually a pretty old property called columns that arranges your content into… columns. You can pass either a number (which will be how many columns your content is arranged in) or a length (which is how wide(ish) you want the columns to be). Additionally, the column-gap property allows you to specify the gap between the columns.

If you read this article on a screen that is greater than about 680px you'll see the page arranged in 2 columns, above about 1,050px and you'll see a 3+ column layout.

.wrapper { columns: 2; column-gap: 16px; }

This CSS will split your content into 2 columns and render something like what you see in the wireframe below:

two columns of text, side-by-side, in a web browser.

You don’t really see columns used on the web, however. The issue being that if a user’s browser is a bit shorter, then they’ll have to scroll down the page to read the end of the first column, then scroll back up to the top to start the next colum (illustrated in the next wireframe image, which has the same content as the previous image but the browser window isn't as tall). Now, you could fix the height of the columns to 100vh, but this would just create a different problem: horizontal scrolling. If your visitor was using anything but an ultra wide desktop monitor, then they’d have to horizontally scroll the page to read the content. Pretty yuk, right?

two columns of text, side-by-side, overflowing the height of a web browser.

In print, the designer knows the page size up-front. There is no worry about content spilling off the page. Obviously, this is not the case with web.

To solve for this, we need to figure out when there isn’t enough vertical room to display all the content in two columns and then fall back to a single column layout, where there is no UX penalty for scrolling. Ultimately, we want to achieve something like what you see in the next wireframe image.

single column of text, overflowing the height of a web browser.

We can use media queries, something like:

@media (min-height: 400px) { .wrapper { columns: 2; } }

But this only works when we know what the content is upfront. Fine for a trivial page, but when the content is dynamic (like an online newspaper or magazine), we don’t have that luxury. I had the idea that I could use container queries here and on detecting that an element was taller than the viewport, I could make sure that the content was rendered as a single column. But with the container-type: size property applied to the element's container, the element will have a height of 0px unless we explicitly set its height, so we're back to where we started. Ideally, we’d do something likecolumns: <column height> / 100dvh. If your content was 1,000px tall and the browser was 500px tall, then you’d have 1,000/500 = 2 columns. But how do you actually do that? If we could get the height of our content in CSS-land (specifically, if it were rendered as a single column), then we could use that value to set the number of columns. We don’t have access to this value, so we’re stuck for a way to do this in CSS. There is an issue in the CSS group working repo, discussing what the next iteration of columns in CSS might look like, you can follow it here if interested.

In the meantime, what to do? Using React, I’ve monkey-patched a component together — "ReactNewspaper" (gist link incoming) — in essence, this component renders the content in a single column layout and measures the height and width of the content to calculate its 'surface area'. It then determines if there is enough room to render it in a multi-column layout, without overflowing the bounds of the page. If the content will spill out of the page, then it’ll revert to a single column layout.

It actually worked better than I expected and it's a hell of a lot of fun to render web content in a multi-column layout!

When I first used this component, it was really frustrating that I couldn't align other content on my page (e.g. the images and video on this very page that you're reading) with the columns themselves. I added an onChange callback to the component, which returns the number of columns that are being rendered but also, crucially, the 'guidelines' for each column, Knowing the start and end pixels for each column lets me align other content to them.

Because I use the first render to calculate the "surface area" of the content, there is a layout shift when it moves to a multi-column layout. I thought it was going to be really jarring, but it actually feels alright, to my eyes at least. I haven't tested it with Google Lighthouse, I imagine it will be unhappy with this.

Another weakness crops up when you are using dynamic font sizing (for example, you may have a smaller font size on mobile devices and let it grow as the device gets bigger, as this page does) — when you enlarge your window the ReactNewspaper component still uses the ‘surface area’ required at the original window width, and won’t realise that more space is required for the increased font size.

I don’t know how the folks who write and implement CSS specs do it, there are so many things to account for, even for simple-looking changes. Props to them.

Column layout is atypical on the web. As such, there are likely new UX issues to consider. The scenario that I am worried about is that a user could easily over-scroll the content, just using muscle memory to move the page along. The closer our component is to the height of the browser, the more likely this is to happen. I can think of two ways to help them out here.

1. Scroll jacking: Put some inertia / stickiness into the component, so that it doesn’t easily scroll off the screen.

2. Visual indication that something is different. Style the background of the component differently from the page’s background, to make it clear that this is a stand-alone component. Perhaps this’ll make it more obvious that there is a fixed-height to this thing and they'll be less likely to over-scroll?

In the “CSS working group drafts” discussion of column overflow, Rachel Andrew presents the idea of handling the container overflow problem by breaking out another set of columns, below the original columns. This would let you specify a max height on your columns container (e.g. 100vh), any any content would get placed into a new element below it.

The CSS working group's idea for handling column layout.

This seems like a really nice solution, and I think it’d always be preferable to my monkey-hack solution of falling-back to a single column layout. Some extra things that I’d love in the new implementation would be:

1.Ability to align other content on the page with your columns. In my React component I use a callback to send an array of “guidelines” to the parent, which allows it to align other items with the columns. Update: I've figured out how to do this in CSS land, see below.

2. Ability to span some of the columns. Currently, the column-span property will allow you to span all of the columns but not a subset of them. I’m thinking that this would be particularly useful for images. I bet there are lots of sharp edges to this idea, for example, spanning an image across two columns may leave you with a blank spot below the image in the first column and and blank spot above the image in the second column. Would we let text flow around the image?

3. Variable gap values. For example, the ability to have a 36px gap between the first two columns and then a 12px gap between all the other columns.

Update on using CSS for alignment

When I initially hacked together this newspaper layout solution, I used JavaScript to determine the width of each column and then made that (width) value available to other – non-column – components on the page, enabling me to size them so they aligned with the columns. If you don't need to switch between single and multi-column layouts (to make sure all your content fits on the screen without scrolling a column layout) then you can ditch the JavaScript and use the following CSS to align general content with your columns. You can see a demo of the following code, here. I have three columns at the top of the page and then some text, an image and a code block beneath, sized to align with the end of the second column. It's all responsive, so if you resize your browser everything will stay aligned.

/* How to align other content with your columns */ .root { /*padding: 42px;*/ --col-count: 4; --col-gap: 20px; --col-width: calc(calc(100% / var(--col-count)) - calc(var(--col-gap) * 1)); --cols-width: calc( var(--col-count) * var(--col-width) + (var(--col-count) - 1) * var(--col-gap) ); font-size: 20px; } .columns { column-count: 4; column-width: var(--col-width); column-gap: var(--col-gap); text-align: justify; width: var(--cols-width); margin-bottom: 50px; } .otherContent { /* this content will be aligned with the first two columns of text */ --end-col: 2; width: calc( var(--col-width) * var(--end-col) + calc(var(--end-col) - 1) * var(--col-gap) ); }