How To: Pure CSS Carousel
The carousel can be pretty daunting. There are so many options, features, devices/browsers to please. Do you even need a carousel? 😕
“No!!! Seriously, you really shouldn’t” — shouldiuseacarousel.com(love the inline comments in the markup, check the
console
😂)
If you decide you need a carousel, you don’t have to create one yourself. You might look at slick carousel or something from a large UI framework such as Bootstrap.
And this is where the disclaimer comes in 👉 If you’re looking for a feature rich carousel, a pure CSS
“carousel”(heavy emphasis on the quotes) is not for you. You can close the browser tab, continue with your carousel and most likely no kittens will come to harm 😉
For those intrigued or looking to save kittens, let’s explore! 🔍
For those in camp TL;DR, Here’s a demo!
Check out this pen by Jhey (@jh3y) on CodePen.
A Background for Pure CSS Components permalink
Doing things in pure CSS
as opposed to with a hint of JavaScript
is not something new. I’ve written before about thinking CSS first.
The capability to create this “carousel” example isn’t something new that’s sprung up. A new CSS
feature hasn’t landed. What it is, is another case of thinking outside the box a little and getting CSS
to do more heavy lifting for us. Most pure CSS
tricks come from observing how elements respond to user interaction 😎
The most simple pure CSS
solution most will be familiar with is to show something like a tooltip or a menu. Hover state provides an opportunity to alter render state without touching any JavaScript
.
The “carousel” trick 🎩 permalink
So, what’s the trick for getting this “carousel” to work?
The use of the label
element in combination with input
elements of type radio
. In particular we leverage the for
and checked
attributes of each. We use the sibling combinator(~
) and preprocessor looping. We can use these to generate nth-of-type
selectors for the number of slides we are showing.
We are going to explore two styles of “carousel”. One will give us the sliding effect from side to side. The other will be more of a fade in/out deal(check the demo quick to see what I mean).
Consider the following markup which will give us the latter style.
NOTE; The only difference in markup structure is that we introduce a “track” element. The track wraps our slide elements for the sliding style.
<ul class="carousel my-carousel">
<input class="carousel__activator" type="radio" id="A" name="activator" checked="checked"/>
<input class="carousel__activator" type="radio" id="B" name="activator"/>
<input class="carousel__activator" type="radio" id="C" name="activator"/>
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="B"></label>
<label class="carousel__control carousel__control--forward" for="C"></label>
</div>
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="C"></label>
<label class="carousel__control carousel__control--forward" for="A"></label>
</div>
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="A"></label>
<label class="carousel__control carousel__control--forward" for="B"></label>
</div>
<li class="carousel__slide">
<h1>A</h1>
</li>
<li class="carousel__slide">
<h1>B</h1>
</li>
<li class="carousel__slide">
<h1>C</h1>
</li>
<div class="carousel__indicators">
<label class="carousel__indicator" for="A"></label>
<label class="carousel__indicator" for="B"></label>
<label class="carousel__indicator" for="C"></label>
</div>
</ul>
Got an idea of how it might work? If not consider this demo that lays out the elements with no “carousel” styling. Click some of the elements, in particular, the label
s. It should make sense and you might be able to proceed with implementing it yourself based off this 👍
Clicking about a bit we see that clicking a label
element will check the related radio
button. The link is between the radio ID
and the for
attribute.
The sibling combinator ~ permalink
The order in the DOM
is important. We welcome back the sibling combinator that I’ve written about before.
The sibling combinator enables us to select adjacent elements. Thos adjacent elements follow a given element.
Each time our input
type radio
changes, a new input
becomes :checked
. At this point we can say “Hey! show the relevant slide and controls that follows this particular checked input”. For this reason our input
elements are first within the carousel.
Take this example;
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
display: block;
animation: showSlide 0.5s forwards;
}
Here we are saying, “when the first input is active, then make the first slide visible”.
The use of the sibling combinator is key here.
nth-of-type selector permalink
The other important CSS
feature we are leveraging is the nth-of-type
selector. This selector gives us a little more freedom with the structure of our markup.
In this example we cater for five slides so we go up to nth-of-type(5)
. We can leverage CSS
preprocessor looping to generate them. For a more robust solution we may tell our preprocessor that the number of slides can go up to 10
.
The code used to generate these rules would be something like;
$panels = 10
for $num in (1..$panels)
.carousel__activator:nth-of-type({$num}) ~ .carousel__slide:nth-of-type({$num})
display block
animation showSlide 0.5s forwards
This particular snippet is using Stylus
. Using other preprocessors like SASS
/LESS
would be no issue though.
Styling it up — Fade in/out permalink
As mentioned before, we are going to produce two styles of carousel.
Our markup structure before was;
<ul class="carousel my-carousel">
<input class="carousel__activator" type="radio" id="A" name="activator" checked="checked"/>
<input class="carousel__activator" type="radio" id="B" name="activator"/>
<input class="carousel__activator" type="radio" id="C" name="activator"/>
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="B"></label>
<label class="carousel__control carousel__control--forward" for="C"></label>
</div>
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="C"></label>
<label class="carousel__control carousel__control--forward" for="A"></label>
</div>
<div class="carousel__controls">
<label class="carousel__control carousel__control--backward" for="A"></label>
<label class="carousel__control carousel__control--forward" for="B"></label>
</div>
<li class="carousel__slide">
<h1>A</h1>
</li>
<li class="carousel__slide">
<h1>B</h1>
</li>
<li class="carousel__slide">
<h1>C</h1>
</li>
<div class="carousel__indicators">
<label class="carousel__indicator" for="A"></label>
<label class="carousel__indicator" for="B"></label>
<label class="carousel__indicator" for="C"></label>
</div>
</ul>
By default, for the fade in/out style we will hide all our slides using the display
property. We also position
them absolute
ly within our carousel element.
.carousel__slide {
height: 100%;
right: 0;
position: absolute;
display: none;
overflow-y: auto;
top: 0;
left: 0;
}
This ensures that our slides fill out the carousel element.
We then toggle the visibility of our selected slide using the ~
combinator;
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
display: block;
animation: showSlide 0.5s forwards;
}
We can add an animation to our slide content so that they fade in also at this point to give a nice fade in effect.
It’s important to note that whilst showing a slide, we also show relevant controls for that slide. This applies to both styles of carousel.
.carousel__activator:nth-of-type(1):checked ~ .carousel__controls:nth-of-type(1) {
display: block;
}
All that’s left to do from here with this style is add some extra nice touches and style themes that we desire.
Styling it up — Sliding permalink
The sliding style carousel works different to our fade in/out style. As such it requires a small tweak to the markup. We need to wrap our carousel__slide
elements inside a container. We will call this element the carousel__track
.
<div class="carousel__track">
<div class="carousel__slide">
<h1>A</h1>
<div>
<div class="carousel__slide">
<h1>B</h1>
<div>
</div>
The “trick” to making the slide work is to line up all slides side by side within the track. We then slide the track element within the carousel using CSS
transforms. This will give the effect of our slides sliding from side to side.
.carousel__track .carousel__slide:nth-of-type(1) {
transform: translateX(0%);
}
.carousel__track .carousel__slide:nth-of-type(2) {
transform: translateX(100%);
}
.carousel__track .carousel__slide:nth-of-type(3) {
transform: translateX(200%);
}
When a radio input
becomes :checked
we translate the track on the x-axis, scaled by the nth-child
value. For example, to show the second slide we will translate our track on the x-axis by -100%
.
.carousel__activator:nth-of-type(2):checked ~ .carousel__track {
transform: translateX(-100%);
}
The scale is;
transform: translateX(-(($num - 1) * 100%))
We do need to make sure our track element has the transition
property set. This is so that are sliding is not instant and we actually get the animated effect;
.carousel__track { transition: transform 0.5s; }
That’s all there is to it for sliding specific styles. Both styles share many of the styling rules.
Extra nice touches permalink
We can add some nice touches like showing indicators and extra controls. For example, here are the indicators for our carousels;
.carousel__indicator {
height: 15px;
width: 15px;
border-radius: 100%;
display: inline-block;
z-index: 2;
cursor: pointer;
opacity: 0.35;
margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
opacity: 0.75;
}
And this is how you show them as active when the appropriate slide is being shown.
.carousel__activator:nth-of-type(1):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(1) {
opacity: 1;
}
All that’s left to do is position things where you desire and theme them up!
A note on accessibility permalink
Accessibility is important when developing for the web. With pure CSS
carousels we can strive to make things pretty accessible 👍 We are providing a list of content. We make a slight alteration to our implementation. We make it so that all content is always visible but not always in the carousel viewbox. That way, a screen reader such as OSX
’s Voiceover Utility
will read out all our slides to the user as intended.
Our slides before had top
, right
and left
defined for them with absolute
positioning. Let’s make it so that only the visible slide gets this positioning and they are all visible at all times;
.carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
top: 0;
left: 0;
right: 0;
animation: showSlide 0.5s forwards;
}
.carousel__slide {
height: 100%;
position: absolute;
overflow-y: auto;
}
We also remove the list style from our carousel so the VoiceOver
doesn’t tell us we have an empty bullet for each slide 👍
.carousel {
height: 300px;
width: 400px;
overflow: hidden;
text-align: center;
position: relative;
padding: 0;
list-style: none;
}
For our markup, let’s make the slider version not use a list for the container. The track element inside it can become a list. Remove the padding and margin from the track;
.carousel__track {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0;
margin: 0;
transition: transform 0.5s;
}
That’s it! permalink
We’ve explored creating pure CSS
carousels. Taking advantage of element behavior and CSS
features, we can do some pretty neat things. Which style of CSS
only “carousel” do you prefer? Or should we banish carousels altogether? What else could we come up with? Be sure to check out the demo.