Amy’s Kapers

HTML and CSS Tricks

So recently I shared a HTML and CSS only testimonial carousel, and was accused of witchcraft 🤣

Despite the fact that this absolutely made my day (and resulted in me having to go home that night and re-watch The Holy Grail), I decided to compile a few of my little JS-free tricks.

Now some of these can be a little janky, and not quite as smooth as when using JS or an external plugin or library, but they’re also much better for performance and don’t rely on loading heavy external resources.

I had to put together a testimonial carousel for a project, normally I would only have a single testimonial but for this one I thought I’d see how to do this in HTML and CSS only.

For this I’m using radio buttons and the next sibling selector (element + sibling {}) to test if the button is selected. I’m also using a different CSS hack to make nice looking radio buttons.

For the HTML, each testimonial has an input, label and blockquote element, although you could omit the label part and use vanilla browser-supplied radio button styling.

<input type="radio" id="quote-1" name="testimonials" />
<label for="quote-1">Testimonial 1</label>
  CSS and HTML are awesome, you can do so many amazing things with them.

The id on each input allows you to relate the label to the input using the for attribute, this allows you to click on the label to check the input rather than only on the input. This should be different for each testimonial.

The name for the inputs should be the same across all, this creates a group of them so only one input can be checked at any one time.

The blockquote is then the testimonial we’re displaying. You can put anything you like in the blockquote, including ps, imgs or cite.

Most of the CSS is to make things look pretty, but this is the bit for the functionality:

input[type="radio"]:checked + label + blockquote {
  display: block;

blockquote {
  display: none;

The first part of the code is overly specific, but we need to make sure we’re only getting the blockquote associated with each testimonial. You could use classes or another attribute to link the two together, but for ease of maintainability I chose to use siblings instead.

That’s all there is to it!

Flexbox then allowed me to re-order it visually to keep the radio buttons at the bottom.

body {
	display: flex;
	flex-wrap: wrap;
	justify-content: center

blockquote {
	width: 100%;

label {
	order: 2;

Then I made the radio buttons nice and pretty.

Setting the font size and colour to make the label invisible makes the label still accessible to screen readers but hides it visually.

Then I set the label to be a circle with a border, and the :after pseudo element to be the middle of the selected radio button.

input {
  display: none;

label {
  border: 1px solid #5b5b5b;
  border-radius: 50%;
  color: transparent;
  font-size: 0px;
  height: 20px;
  width: 20px;
  position: relative;

  &:after {
    background: #16a6b1;
    border-radius: 50%;
    position: absolute;
    top: 2px;
    right: 2px;
    bottom: 2px;
    left: 2px;

input[type="radio"]:checked + label:after {
  content: "";

You can then style the blockquote however you like.

It’s a little janky, and if the testimonials are different heights things will move around when switching between the testimonials, I’m still working on a solution to fix this.