Recently, I've been working on a site that uses a CMS that's a bit limiting. I can add my own HTML and CSS to the site, but JavaScript is off-limits.

The designs I'm working from included a carousel. I had some ideas of how I could make that work using CSS animations and the transform property, but that would leave me with a carousel that scrolled automatically and didn't allow for user input which wasn't really what I was looking for. After some thinking, I eneded up with a solution that uses absolute positioning and the :target pseudo-selector to change the z-index and opacity of our carousel items to cycle through them. It looks something like this:

This is the first item Idque Caesaris facere voluntate liceret: sese habere. Qui ipsorum lingua Celtae, nostra Galli appellantur. Inmensae subtilitatis, obscuris et malesuada fames. This is the second item Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Idque Caesaris facere voluntate liceret: sese habere. Cum sociis natoque penatibus et magnis dis parturient. Nihilne te nocturnum praesidium Palati, nihil urbis vigiliae. And finally, the third Quis aute iure reprehenderit in voluptate velit esse. Cum ceteris in veneratione tui montes, nascetur mus. Prima luce, cum quibus mons aliud consensu ab eo. Quam temere in vitiis, legem sancimus haerentia. Sed haec quis possit intrepidus aestimare tellus.

Let's build one!

The structure

The structure of our carousel goes something like this: We have a main div.carousel-wrapper that gives our carousel its size. Inside our wrapper, we have span.hidden-target elements with unique IDs that act as targets for our carousel items controls, and div.carousel-item elements that hold the content of each of our carousel items.

Each of our div.carousel-item elements have some content within them, and two links, a.arrow-prev and a.arrow-next, which we use to cycle between the carousel items.

Because our individual carousel items will be position: absolute (so we can stack them on top of eachother), we have to set the div.carousel-wrapper's height manually, and it makes sense to do this inline. We're going to try to offload as much of our CSS to our external stylesheet, but some of the items we'll have to write inline in order to make our carousel reusable and scalable.

I'm also using inline CSS to set the background image of two of our div.carousel-item elements to make them a little more vibrant, but we'll leave that out below so that our markup is more readable.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!--Here's our main wrapper. Since our carousel items get their size from their parent, we have to specify its height.--> < div class = " carousel-wrapper " style =" height : 400 px ; " > <!--The carousel uses regular links to cycle through each item. The links actually target these display: none; spans so our page doesn't jump like it normally would when using jump links.--> < span id = " target-item-1 " > </ span > < span id = " target-item-2 " > </ span > < span id = " target-item-3 " > </ span > <!--Here are our carousel items. Each has a 'carousel-item' class, which we use for shared styling and an item-# class, which we use to control its opacity depending on which target-item-# is currently targeted--> < div class = " carousel-item item-1 " > <!--We can add any content in here, just make sure that your .carousel-wrapper is big enough to hold all the content.--> < h2 > Item 1 </ h2 > < p > Content goes here. </ p > <!--Here are the links that control the carousel! Make sure the href of each one is pointing to the right target-item-# so that the carousel cycles in sequence.--> < a class = " arrow arrow-prev " href = " #target-item-3 " > </ a > < a class = " arrow arrow-next " href = " #target-item-2 " > </ a > </ div > <!--And here are a couple more carousel items so that we have some content to scroll to. Notice the 'light' class? Royal blue is a pretty dark background color, so we'll add a CSS rule to make the text white if a carousel item has this class--> < div class = " carousel-item item-2 light " style =" background-color : royalblue ; " > < h2 > Item 2 </ h2 > < p > Content goes here. </ p > < a class = " arrow arrow-prev " href = " #target-item-1 " > </ a > < a class = " arrow arrow-next " href = " #target-item-3 " > </ a > </ div > < div class = " carousel-item item-3 " > < h2 > Item 3 </ h2 > < p > Content goes here. </ p > < a class = " arrow arrow-prev " href = " #target-item-2 " > </ a > < a class = " arrow arrow-next " href = " #target-item-1 " > </ a > </ div > </ div >

That's it for our HTML. It's surprisingly light. The CSS (SCSS, in this case) is where the magic happens.

The styles

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 /_ Here's where our carousel begins, with the main wrapper being relatively positioned, so that our absolutely positioned items are in the right place. _/ .carousel-wrapper { position : relative ; /_ Our absolutely positioned carousel items span the width and height of its parent . We're making them transparent by default so that they fade in when we cycle through them using the arrow links. _/ .carousel-item { position : absolute ; top : 0 ; bottom : 0 ; left : 0 ; right : 0 ; padding : 25 px 50 px ; opacity : 0 ; transition : all 0.5 s ease-in-out ; /* Did you notice the 50px left, right padding up above? It's so we can position our arrow links! Each one will be 50px wide. Also, I'm using empty links with a background image so that the links look like arrows. Make sure you swap out that URL with an actual URL so that your arrow links aren't just transparent rectangles. */ .arrow { position : absolute ; top : 0 ; display : block ; width : 50 px ; height : 100 % ; -webkit-tap-highlight-color : rgba ( 0 , 0 , 0 , 0 ) ; background : url ( "/carousel-arrow-dark.png" ) 50 % 50 % / 20 px no-repeat ; /* Let's put our arrow to go back on the left. */ & .arrow-prev { left : 0 ; } /* And our arrow to go forward on the right. Since I'm using the same arrow image for both my arrows, I'm rotating this one by 180 degrees so that it points in the right direction */ & .arrow-next { right : 0 ; -webkit-transform : rotate ( 180 deg ) ; transform : rotate ( 180 deg ) ; } } /* I really like how these carousel items look on a dark image background, so if a .carousel-item div has the class 'light', we'll make its text color white, and use a white arrow instad of a dark gray one. Again, make sure this arrow image exists somewhere */ & .light { color : white ; .arrow { background : url ( "/carousel-arrow-light.png" ) 50 % 50 % / 20 px no-repeat ; } } /* Let's use using some media queries to resize the arrows on smaller devices.*/ @media ( max-width : 480 px ) { .arrow, & .light .arrow { background-size : 10 px ; background-position : 10 px 50 % ; } } } /_ Let 's set our jump link targets display: none; so that we' re not making the browser jump to the top of the carousel whenever a user clicks on one of our arrow links. This attribute selector will target any element whose id starts with 'target-item'. _/ [id^="target-item"] { display : none ; } /_ So, up above we made all our carousel items transparent, which means that on page-load, we'd have a big empty box where our carousel should be. Let's set our first item's opacity to 1 so that it displays instead. Also, we're setting its z-index to 2, so that it's positioned on top of the other carousel items. _/ .item-1 { z-index : 2 ; opacity : 1 ; } /_ But we don't want the first item to ALAWYS be opacity : 1 ; otherwise it would peek through when cycling between items two and above. _/ \*:target ~ .item-1 { opacity : 0 ; } /_ ... but if #target-item -1 is targeted , well we do want the first item to show up , so we're selecting it with the ~ sibling selector and setting its opacity to 1 again : - ) _/ #target-item-1:target ~ .item-1 { opacity : 1 ; } /_ If any other target-item-# is targeted, let's select it using the sibling selector, make it fade in, and place it on top of the pile using z-index: 3. Here's where you'd add more target items if your carousel has more than three items. It might be worth adding like 10 items right off the bat. _/ #target-item-2:target ~ .item-2, #target-item-3:target ~ .item-3 { z-index : 3 ; opacity : 1 ; } }

And that's it! You have a carousel that's fully functional and is 100% HTML and CSS! We only made a carousel with three items, but you can keep adding items, just make sure you add more target items, and you link up your arrow links correctly.