Skip to content.


When setting out to design this website, I made an intentional decision to have it strictly component-based. This meant I could home in on each component and make it simple, powerful and responsive, as well as adaptable.

Imagery is a hugely important part of this site. What is telling you stories if we can’t show you the pictures? We want you to feel as part of our story as we are. There are a number of ways in which pictures are served up on the site – singles, popups, grids and, in this article, carousels – or slides.


To begin with it is important that the images for the slideshow work if everything else breaks, so having a simple list of images is important – no flash or fancy libraries. The component needed to be lightweight and responsive. I needed to be brave as well about how to use CSS. If a browser didn’t support the newer stuff, they were simply served a list of images.

CSS has come far and there are simple tricks involving hovers and active states that can give you motion and interaction that’s just as clean as JS – and much lighter in size.

A list of images

So to begin with we have a list of images. One requirement: they all needed to be the same aspect ratio. This is very easy to achieve through Craft’s image transforms, so that was sorted.

<img src="1.jpg" alt="Item 1">
<img src="2.jpg" alt="Item 3">
<img src="2.jpg" alt="Item 3">

Positioning the images

Since the images are the same aspect ratio they can just sit on top of each other. A simple case of positioning:

.c-image.\+slides {
 position: relative;
 height: 0;
 padding-bottom: 56.25%;
 width: 100%;
.c-image.\+slides img {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;

As you can see we use a simple trick used throughout the site: the container has zero height and a percentage padding-bottom, which sets out a container which can change width but keeps its aspect ratio.

Animating between slides

Introducing a lovely combination of keyframes, animation and SCSS mixins. There’s a limit in our carousels of 6. This is for two reasons: to keep the CSS light (you’ll see why) and more than that and it’s asking the page to load a fair number of images that will potentially not be seen.

The animation is created using a mixin which outputs a number of keyframe animations based on how many slides there are. The number of slides is determined by a little bit of twig templating which counts the images added and slots in the correct class: eg. x4, x6 etc

There are two mixins: one which creates the CSS keyframes and another which outputs them into animations for the appropriate classes.

@mixin slidesfade($num:5) {
 $fadein: 100%/$num - 100/$num/2;
 $keyframe: 100%/$num;
 $fadeout: 100%/$num + 100/$num/2;
 0% { opacity: 0; }
 #{$fadein} { opacity: 1; }
 #{$keyframe} { opacity: 1; }
 #{$fadeout} { opacity: 0; }
 100% { opacity: 0; }

A number of things are happening here. We have a number (defaulting to 5). 100% is divided by this to get the keyframe length: so in this case if the slides were 10 secs in total, each slide would take 2 seconds.

Rather than cut between images, a nice fade-in and out is a bit more visually pleasing. So there’s a little of calculation of where the fade-in and out comes in, basicially half of the length of the slide on either side. so it takes 1 second to fade in, the image, it remains full opacity for 1 second, then takes 1 second to fade out.

This mixin is used to output @keyframes which can be used in the second mixin

@keyframes fade1 { @include slidesfade(1); }
@keyframes fade2 { @include slidesfade(2); }
@mixin slides(
) {
 @for $n from 1 through $num {
 img:nth-of-type(#{$n}) {
 animation: (fade#{$num} $dur ($n*($dur/$num) - $dur/$num) infinite;

The second mixin outputs the keyframes into the img tags. It uses the appropriate keyframe depending on the $num value. (Again defaulting to 5). So for our animation, fade#{$num} outputs to fade5. The $dur is the overall length of time for the slides. The second value is the delay. so if we’re on the 4th image it’d be (3×10÷5 — 105), which is, of course, 4 seconds. The animation loops forever.

Finally for this part, we output the classes into CSS. So for our list of 6 available options we can do this:

.c-image.\+slides.x1 { @include slides(1, 4s); }
.c-image.\+slides.x2 { @include slides(2, 8s); }
.c-image.\+slides.x3 { @include slides(3, 12s); }
.c-image.\+slides.x4 { @include slides(4, 16s); }
.c-image.\+slides.x5 { @include slides(5, 20s); }
.c-image.\+slides.x6 { @include slides(6, 24s); }

Ok, so we’ve got our fading image. Very nice. But it would be nice to be able to flick through the images as a user. How can we do this?

User interaction

Again, we need to keep this simple. In CSS we have a useful selector called the sibling” selector: ~

This little selector selects anything that is a sibling of our element. Some elements can have different, user-initiated states: one of them being the <a> tag. For web we have :hover and on mobile we have :active. Tapping or clicking on an a tag can, if we want, cause a change in a sibling element (another element with the same direct parent).

In Craft, like I said previously, we can work out the number of images, so we can output the same number of <a> tags just before our list of images.

<img src="1.jpg" alt="Item 1">
<img src="2.jpg" alt="Item 3">
<img src="2.jpg" alt="Item 3">

We also have a selector in CSS called nth-of-type. If we hover on the second <a> tag we can manipulate the second <img> tag.

Using CSS I changed these <a> tags to large areas on the image. If we have 5 images, we would have 5 tappable areas aligned side-by-side and each taking 20% of the width of the slider. Each area corresponds to an image on the slider. Far left of the slider is the first image, right hand side is the last.

We position our <a> tags using absolute positioning:

a {
 position: absolute,
 top: 0;
 bottom: 0;
 opacity: 0;

We know the number of slides so using x4 or x3, we can determine the width of each <a> tag. and what distance from the left hand side it is. This can be included in our second mixin

a {
 width: 100%/$num;
@for $n from 1 through $num {
 a:nth-of-type(#{$n}) {
 left:$n*100%/$num - 100%/$num;
 &:hover, &:active, &:focus {
 ~ img:nth-of-type(#{$n}) { visibility: visible; opacity:1; }
 ~ img {
 @include animation(none);

As you can see, hover, active and focus activates the corresponding img, changing it from invisible to visible, if it’s not visible already.

For added usability, I’ve included small indicators on the slideshow which are filled in if they are currently visible. This is achieved using little pseudo elements.

a:after {
 @include position(absolute, $base-space 0 0 50%);
 @include round;
 @include size($base-space/2);
 border:1px solid white;
a:hover, a:active, a:focus {
 &:after {

That’s pretty much it. It might look a bit crazy, but it’s fairly logical stuff. And guess what? No Javascript!

Demo Time

Blue BalloonsChocolate SelectionColorful Balloons