Pure CSS Horizontal Scrolling

Avatar of Pieter Biesemans
Pieter Biesemans on (Updated on )

The web is a rather vertical place. You read a web site like you read a physical page: left to right, top to bottom. But sometimes, you want to step away from the verticality of it all and do something crazy: make a horizontal list. Or even crazier, a horizontal site!

I’d be nice if we could do something like this:

/* This isn't real */
div {
  scroll-direction: horizontal;
}

Unfortunately, that’s not going to happen. It’s not even on the roadmap for CSS.

That’s too bad, as at the company I work for this would be quite useful. We do quite a few web presentations. Presentations are a very horizontal thing – usually slides have a 4:3 or 16:9 radius. This means we always have a struggle between the horizontality of presentations and the verticality of web technologies. And by we, I mean me. But if there’s one thing I like, it’s a challenge.

Another Use Case

The specific use case that led to me digging into this idea that a customer wanted to show all their products on a single slide. Of course, their product catalog was way too big to put in a single view. So we decided to split them up into three categories, each horizontally scrollable. So the three most prominent product in each category were visible and less important products were still easily accessible.

A Non-JavaScript Way

There are, no surprise, numerous ways to do this in JavaScript. Some of them are on this very site.

I was curious if it was possible to do in pure CSS. The solution ended up being fairly straightforward:

  • Create a container with items
  • Rotate the container 90 degrees counterclockwise so the bottom is to the right
  • Rotate the items back to correct-side up

Step 1) Set up the container

Make a <div>, and make a bunch of child elements.

In this example, our side-scrolling container will be 300px wide, with 8 items of 100×100px each. These are arbitrary sizes; they could be anything.

<div class="horizontal-scroll-wrapper squares">
  <div>item 1</div>
  <div>item 2</div>
  <div>item 3</div>
  <div>item 4</div>
  <div>item 5</div>
  <div>item 6</div>
  <div>item 7</div>
  <div>item 8</div>
</div>

The height of the container will become the “width” and vice-versa. So below, the “width” of our container will be 300px:

.horizontal-scroll-wrapper {
  width: 100px;
  height: 300px;
  overflow-y: auto;
  overflow-x: hidden;
}

Now the children:

.horizontal-scroll-wrapper > div {
  width: 100px;
  height: 100px;
}

Step 2) Rotating the container

Now we rotate the container -90 degrees with a CSS transform. And there you have it: a horizontal scroller.

.horizontal-scroll-wrapper {
  ...
  transform: rotate(-90deg);
  transform-origin: right top;
}

There’s just one tiny issue: our children have rotated too, and now anything within is on its side.

Step 3) Rotate the children back upright

How would we go about getting the children upright again? Rotate them back using another, opposite CSS transform.

.horizontal-scroll-wrapper > div {
  ...
  transform: rotate(90deg);
  transform-origin: right top;
}

Step 4) Fixing the positioning

It’s starting to look alright, but there are still some issues.

By rotating the wrapper using the top right as an anchor point, our left side has shifted by the width of the container. If you find this difficult to understand, just put your finger on the top right corner of a page and rotate it. The solution: shift it back with translateY.

Better. But the first item is still missing, due to the same phenomenon happening to the items. We could fix this by giving the first child a top margin of its width or by translating all items the same way we did the wrapper. The easiest way I’ve found though is to add a top padding to the wrapper equal to the item width, creating a kind of buffer for the items.

.horizontal-scroll-wrapper {
  ...
  transform:rotate(-90deg) translateY(-100px);
  ...
}

Demo

See the Pen Horizontal scroll (simple example) by Pieter Biesemans (@pieter-biesemans) on CodePen.

Here’s another where you can see non-square children:

See the Pen Horizontal scroll (extensive example) by Pieter Biesemans (@pieter-biesemans) on CodePen.

Compatibility

I have tested on the devices immediately available to me.

Device OS Browser works?
Desktop Win10 Chrome 54 Y
Desktop Win10 Firefox 47 Y (w scrollbars)
Desktop Win10 IE11 Y (w scrollbars)
Desktop Win10 Opera 41 Y
Desktop Win10 Vivaldi 1.4 Y
Laptop (touch screen) Win10 Chrome 54 N
Samsung Galaxy S3 Android 4.3 Chrome Mobile 52 Y
Samsung Galaxy S6 Android 5.0 Chrome Mobile 52 Y
Nexus 6P Android 6 Chrome Mobile 52 Y
iPad2 iOS Chrome Mobile 52 N
iPad2 iOS Safari Mobile 9.0 N
iPad Air 2 iOS Safari Mobile 9.0 N

Desktop

Since the styling of scrollbars is currently only supported by WebKit/Blink, Firefox and IE still show the ugly gray ones. You could sniff this out with JavaScript and hide them completely, but that’s stuff for another tutorial.

Using the mouse scroll wheel works great on desktops. My laptop was a different matter, though. Both the touchscreen and the touchpad acted as though the div was not rotated.

Mobile

I was kind of surprised to find that Android actually understood that the container had been rotated, and let you scroll sideways by swiping left and right.

iOS on the other hand did not play nice. It acted like the container did not get rotated, so you have to swipe up and down to scroll sideways, which of course is counterintuitive. Also, swiping left and right moves the items up and down in their wrapper, which is unexpected and weird. Setting the overflow to hidden does not alleviate this issue.

Conclusion

According to Can I Use, CSS transforms are currently supported by over 93% of users (at the time of this writing, November 2016), so there’s no issue there.

Beware of using this in production, though. I have tested this on some devices, but not at all extensively or in depth.

The greatest issue is with touch inputs that requiring you to swipe up and down to go left and right. A possible solution would be to include a message on your site explaining this, but you’d have to rely on people actually reading your message. And even then it’d still be counterintuitive. Another possible solution would be to capture the touch input with JavaScript on those devices, but then you’d be better off just doing the whole thing in JavaScript and foregoing this CSS hack completely.