Vanilla JavaScript: How To Create a Draggable Slider With Auto-Play and Navigation.
Tags: css, javascript, animation, codepen
Published: 2023-10-04 18:24:18

Sliders, also known as carousels, are a popular tool for modern websites. They enable efficient transmission of information within a limited space.

Developers often rely on frameworks to build sliders. Although productive, this method does not contribute much to your growth as a developer.

Therefore, in this tutorial, we’ll adopt a more traditional approach to this project by using plain HTML, CSS, and JavaScript.

Codepen here:

{% codepen https://codepen.io/Lynxdev/pen/JjwOWwK %}

## Warming up

Before proceeding, I want to introduce two fundamental concepts that this project relies on.

Firstly, when you link a `label` element to an `input` element, like this:

```html
<input type="radio" id="radio1"/>
<label for="radio1">label for radio1</label>
```

Clicking that label on the webpage passes the focus to the linked input element, as shown below:


![a linked label](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfu40q0g47wx13nl9vdv.gif)

This behavior is consistent even when you reposition either of the elements using CSS:

```css
label{
   position: fixed;
   top: 10rem;
   right: 5rem;
}
```

The output:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wepnwd77swz9f4cc4vf7.gif)

Furthermore, you can apply this concept to other input types, such as checkboxes, numbers, and text fields. This linking expands the clickable area of the input element, and, as you will discover later, it allows for some neat tricks.

Secondly, using CSS pseudo-classes and combinators, you can dynamically change the styling of an element based on the current state of another element.

Pseudo-classes allow you to style specific states of elements. For example, `:hover` targets an element’s state when you hover over it with your mouse.

```css
p{
  color: black;
}

p:hover{
  color: red;
}
```

> Note that all pseudo-classes begin with a single colon (:).
>

Several pseudo-classes in CSS allow you to target various states of elements, but I’ll draw your attention to the `:checked` pseudo-class in this tutorial. 

`:checked` is used to target the state when you toggle on a radio input or checkbox. For example, let’s consider our previous input element:

```css
input:checked{
  outline: 2px solid red;
  outline-offset: 10px;
}
```

This output:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tzynoabs7j7cgftnef85.gif)

You can see that the browser applied the styles only after you toggled the radio on.

Alternatively, combinators establish the connection between selectors. There are four types of combinators in CSS:

- General sibling combinator (~)
- Adjacent sibling combinator (+)
- Child combinator (>)
- Descendant combinator (space)

Here, we’ll focus on the general sibling combinator. The general sibling combinator allows you to style sibling elements in your HTML. 

For example, consider the following CSS rule:

```css
.child1 ~ .child2{
  /* some declarations */
}
```

This selector initially identifies any element in our HTML with a class of `.child1`. It then applies the specified styles to any of its younger siblings with a class of `.child2`, as demonstrated below:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49966bimo6f9c3zafcbt.jpeg)

You should note the following about any two elements connected in this way:

- They need to be sibling elements, which means they must share a common parent element, as shown above.
- They do not need to be immediate siblings. This combinator disregards any other siblings placed between them.
- Finally, you cannot apply combinator styles upwards in the HTML tree, only downwards. Thus, `.child2` must come after `.child1`, making `.child2` a younger sibling.

Putting it all together, and still on our input and label elements from before, we have:

```css
input:checked ~ label {
  outline: 2px solid red;
  outline-offset: 10px;
}
```

The output:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/od36vkk6o7ggq4bsntxt.gif)

As you can see, we dynamically changed the styling of a sibling element(`label`) based on the `:checked` state of the `input` element.

With all that out of the way, we can continue with our program.

## The HTML

Our first task is to create a  `section` of class `slider` within the `body` element.

```html
<body>
  <section class="slider"></section>
</body>
```

Within this `section`, we’ll add:

1. Four radio inputs, one for each slide. Each radio input should have a unique `id`.
2. An unordered list (`ul`) with class `slides-flex`. It should contain four list elements (`li`), each assigned a class `slide`. These `li` will serve as our individual slides, containing whatever we wish to to display.
3. A `div` with class `navigation`. This should contain four label elements, each linked to one of the four radio inputs previously mentioned. Each label element should also have a unique `class` and an `id` between 1 and 4, corresponding to its position in the parent element.

```html
<body>
    <section class="slider">
      <!-- radio buttons start -->
      <input type="radio" name="radio-btn" id="radio1" />
      <input type="radio" name="radio-btn" id="radio2" />
      <input type="radio" name="radio-btn" id="radio3" />
      <input type="radio" name="radio-btn" id="radio4" />
      <!-- radio buttons end -->
      <!-- slides start -->
      <ul class="slides-flex">
        <li class="slide">
          <h2>1</h2>
        </li>
        <li class="slide">
          <h2>2</h2>
        </li>
        <li class="slide">
          <h2>3</h2>
        </li>
        <li class="slide">
          <h2>4</h2>
        </li>
      </ul>
      <!-- slides end -->
      <!-- navigation start -->
      <div class="navigation">
        <label for="radio1" class="btn1" id="1"></label>
        <label for="radio2" class="btn2" id="2"></label>
        <label for="radio3" class="btn3" id="3"></label>
        <label for="radio4" class="btn4" id="4"></label>
      </div>
      <!-- navigation end -->
    </section>
  </body>
```

Our HTML section is now complete. You can copy this into your editor. 

As you can see, our webpage is starting to take shape:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zaoiu7q4e644bvn9ueuq.jpeg)

## The CSS

You can adapt most of the styling of this project to your specific use case. However, in this section, I’ll explain the fundamental style decisions that make this project work.

### Global Styling

Firstly, let’s remove the default margins, paddings, and box-sizing present on each element using the global selector (`*`). This improves control over element styling and reduces variation across browsers.

```css
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
```

Next, the `:root` selector allows you to create custom variables that can reused throughout your style sheet. We’ll use  `color: #333;` numerous times in our style sheet, and creating a custom variable is essential to avoid repetition.

```css
:root{
 --primaryColor: #333;
 }
```

> *Note that we can later access this custom variable using* `color: var(--primaryColor);`.
> 

Now, for our `body` element:

```css
body {
  display: grid;
  color: var(--primaryColor);
  height: 100vh;
  place-items: center;
  background-color: rgb(208, 230, 249);
  font-family: sans-serif;
}

ul {
  list-style: none;
}
```

Here, we set the following properties:

- `background-color`: To provide a consistent visual backdrop for our webpage.
- `color` and `font-family`: We apply these styles globally to all child elements.
- `display: grid;` and `place-items: center;`: When used together, they center all child elements. It’s important to note that the parent element requires a specified `height` property for this method to work. Our height  in this case, spans the entire viewport (100vh).
- `list-style: none;`: To remove the bullets from all list elements on our webpage.

Our webpage should look like this:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5k9boudn11o3756n0y8.jpeg)

### Class  `.slider`

This will serve as a container to showcase our slides.

```css
.slider {
  position: relative;
  width: 80%;
  aspect-ratio: 1;
  max-width: 50rem;
  max-height: 31.25rem;
  border-radius: 0.7rem;
  /*overflow: hidden;*/
  cursor: grab;
  border: 2px solid green;
}

.slider input {
  display: none;
}
```

- `position: relative;`: This accommodates the later placement of absolutely positioned elements.
- `aspect-ratio: 1;`: Ensures the element remains square with equal sides, eliminating the need to set the height property for improved responsiveness.
- `max-height` and `max-width`: Set limits on how large the element can become on larger screen sizes.
- `overflow: hidden;`: We will discuss more on this later.
- `cursor: grab;`: Indicates that the element is draggable.
- `display: none;`: Hides the element from the viewport. Note that we can still access the radio inputs programmatically even though they are invisible.

The output:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3pm7xe2voaey24f0xsm.jpeg)

> *Note that the borders are only present for visualization purposes, and we’ll remove them later*.
> 

### Class  `.slides-flex`

In this section, we’ll arrange the slides horizontally using Flexbox.

Moreover, this section serves as the dynamic part of our project. To achieve this, it needs to have absolute positioning so we can adjust it relative to its relatively positioned parent(`.slider`).

```css
.slides-flex {
  display: flex;
  width: 400%;
  height: 100%;
  position: absolute;
  left: 0;
  transition: 0.8s;
}
```

- `width: 400%;`: This sets the width to four times that of its parent.
- `height: 100%;`: Stretches the element to fill the entire height of its parent.
- `position: absolute;`: Enables us to use the `left: 0;` declaration relative to its parent, placing it at the left edge of .slider.
- `transition: 0.8s;`: Used to create a smooth motion and introduce a sliding effect.

### Class  `.slide`

In this section, we’ll style each of our slides. We’ll start with the general layout by styling all elements with a class of `.slide`. We’ll then target each slide using the `:nth-child` pseudo-class.

```css
.slide {
  width: 25%;
  display: grid;
  place-items: center;
}

.slide:nth-child(1) {
  background-color: #fed050;
  border-radius: 0.7rem 0 0 0.7rem;
}

.slide:nth-child(2) {
  background-color: #7fdbee;
}

.slide:nth-child(3) {
  background-color: #4cf89c;
}

.slide:nth-child(4) {
  background-color: #f79191;
  border-radius: 0 0.7rem 0.7rem 0;
}

.slide h2 {
  user-select: none;
  height: 5rem;
  width: 5rem;
  font-size: 3rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  text-align: center;
  outline: 3px solid;
}
```

- `border-radius`: This property can accept one to four values. When using four values, they are applied clockwise starting from the top-left corner. In our first slide (`0.7rem 0 0 0.7rem`), we aim to round the top-left and bottom-left corners. Conversely, for our fourth slide (`0 0.7rem 0.7rem 0`), we intend to round the top-right and bottom-right corners. You’ll see the importance of this in the JavaScript part.
- `user-select: none;`: Prevents unnecessary text highlighting while dragging each slide with the mouse.

The output:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5y8pnx5c0xky6ptmbgce.gif)

### Class `.navigation`

For our navigation, let’s style each `label` to look like radio inputs.

```css
navigation {
  position: absolute;
  display: flex;
  width: 100%;
  bottom: 2.5rem;
  gap: 2rem;
  justify-content: center;
}

.navigation label {
  border: 2px solid var(--primaryColor);
  padding: 0.3rem;
  border-radius: 50%;
  cursor: pointer;
  transition: 1s;
}
```

- `position: absolute;`: Enables the use of `bottom: 2.5rem;`, positioning it 2.5rem from the bottom of `.slider`.
- `cursor: pointer;`: Indicates to the user that the element is clickable.
- `border-radius: 50%;`: Transforms each label into a circle.

Here’s what the navigation should look like:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/svawf16btkg4713mgao9.gif)

### On `:checked`

As mentioned earlier, the `:checked` pseudo-class allows access to the checked state of radio inputs. And when used with the general sibling combinator (~), grants access to their younger siblings:

1. Class `.slides-flex`  , and
2. Class  `.navigation`.

Starting with `.slides-flex`:

```css
#radio1:checked ~ .slides-flex {
  left: 0;
}

#radio2:checked ~ .slides-flex{
  left: -100%;
}

#radio3:checked ~ .slides-flex {
  left: -200%;
}

#radio4:checked ~ .slides-flex {
  left: -300%;
}
```

In CSS, when you use a percentage value to position an element, that value is relative to the width of its parent element. So, when you set `left: -100%;`, it shifts the element to the left, outside its parent element, at a distance equal to 100% of its parent’s width, as demonstrated below:

![left: -100%;](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0w2miw1fe3fcezicezj.jpeg)

As a reminder, we previously defined the width of `.slides-flex` to be four times(400%) that of `.slider`. Therefore, setting `left: -100%;` simply shifts `.slides-flex` by one-quarter of its width to the left, bringing a new slide to focus.

Looking at the image below, clicking each button after the first subtracts an additional 100% from the position of`.slides-flex`, bringing the slide at that position into focus.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sb5r5oecql6y07wqmoj1.gif)

Now for  `.navigation`,  so we can know what slide we’re in:

```css
#radio1:checked ~ .navigation .btn1 {
  background-color: var(--primaryColor);
}

#radio2:checked ~ .navigation .btn2 {
  background-color: var(--primaryColor);
}

#radio3:checked ~ .navigation .btn3 {
  background-color: var(--primaryColor);
}

#radio4:checked ~ .navigation .btn4 {
  background-color: var(--primaryColor);
}
```

Notice how, first, we selected the parent element of the buttons, which is a sibling to the radio inputs. Then, we accessed each button using the descendant combinator (space).

Now, when we click any button, it changes its background color then brings a corresponding slide into focus, as shown below:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cko3q2qy0wlwhc7l18t9.gif)

Rounding up, to ensure that only the selected slide is visible, let’s uncomment the line `overflow: hidden;` in our `.slider` styling. This change hides content outside the boundaries of the parent element, which, in this case, includes all other slides.

We can also comment out the border styling while at it:

```css
.slider{
  ...
  overflow: hidden;
  ...
  /* border: 2px solid green; */
}
```

The output:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezz5eadlq9o0ikmz4i9j.gif)

Finally, if you look at the image above, you’ll see that upon reload, no label is checked, and users won’t know what slide they are currently on. To avoid this, let’s add the `checked` attribute to our `#radio1`. This checks the first radio input on every reload, indicating that we are currently on the first slide.

```html
<input type="radio" name="radio-btn" id="radio1" checked />
```

The output:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r39p6bhfdxepq237oh0y.gif)

That concludes the CSS part. So far, we’ve implemented the sliding animations and manual navigation. Regrettably, for the sake of brevity, we must pause here. I’ll release a sequel to this article covering the JavaScript part, allowing us to achieve the:

1.	Autoplay, and
2.	Dragging (mouse) or swiping (touchscreen) options.
Stay tuned!

## Conclusion

In this tutorial, we’ve learned that not all webpage functionality needs to rely solely on JavaScript. By utilizing CSS pseudo-classes and combinators, we dynamically modified our webpage elements—a task that might otherwise  have cost us several lines of JavaScript code.

Finally, if you’ve learned anything new, consider dropping a like and sharing so others can benefit as well. Until next time.

## Some Helpful Links

Find out more on:

- CSS pseudo-classes [here](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
- CSS combinators [here](https://www.w3schools.com/css/css_combinators.asp)