CSS only components
Tags: css
Published: 2019-12-14 18:32:46

---
title: CSS only components
published: true
date: 2019-12-13 16:51:49 UTC
tags: css
canonical_url: https://blog.logrocket.com/css-only-components/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/vyb2rhpl0ip90y64bkut.png
---
**Written by [Ananya Neogi](https://blog.logrocket.com/author/ananya-neogi/)**✏️

We know CSS is used to style a UI component, but did you know that you can add interactivity with just CSS as well?

In this post we are going to find out how.

JavaScript is great — the purpose of this post is not to make the case that you don’t need JavaScript at all.  However, it’s good to be aware that you can build many UI components functionality without the additional dependency of JavaScript.

### How to add interactivity with CSS

Let’s take a look at some patterns that will help us to add interactivity to our UI components with just CSS.

### When two separate elements need to work together

In HTML, we can establish relationships between a link and an element with `id` attribute of the element along with `href` in an `<a>` tag.

Here’s an example:

```jsx
<a href="#p1">Jump to the paragraph</a>
    
<p id="p1">Hello! I'm a paragraph.</p>
```

If we click on the link, the page will automatically jump to the paragraph with `id="p1"` because the `href` of `<a>` matches with it. We now have a relationship between these two elements.

Now, if we want to modify styles or add additional styles when `<p id="p1">` is in focus, we can use the `:target` pseudo-class. `:target` helps us in targeting the element with an `id` matching the URL’s fragment i.e the `href`.

```jsx
p:target {
  background-color: thistle;
}
```

Now when the page jumps to the paragraph after the link is clicked, the paragraph will also have a background color as mentioned.

Let’s see some examples of UI components using this pattern.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

### CSS-only Modal/Dialog

{% codepen https://codepen.io/ananyaneogi/pen/LYEPXEQ %}

Here’s the simplified versions of the HTML and CSS:

```jsx
<!-- #dialog creates the relationship between the button link and dialog -->
    <a class="button" href="#dialog">Click To Open Modal</a>
    
    <!-- wrap dialog body in "overlay" -->
    <!-- role="dialog" will let the screen reader know the purpose of this section -->
    <div id="dialog" class="overlay" role="dialog">
      <div class="dialog-body">
    
          <!-- href="#" resets the target, hence the dialog closes  -->
          <a class="close" href="#" aria-label="Close dialog">&times;</a>
    
            <!-- content -->
        </div>
    </div>


    .overlay {
    /* Positioning and styling of the overlay */
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;        
      right: 0;
      background: rgba(0, 0, 0, 0.8);
      transition: opacity 250ms;
    
    /* Dialog is hidden until it matches an anchor target */
      visibility: hidden;
      opacity: 0;
    
      /* Dialog fades in when it matches an anchor target */
      &:target {
        visibility: visible;
        opacity: 1;
      }
    }
    
    .dialog-body {
      position: relative;
      margin: 50px auto; /* center the dialog body */
      width: 300px;
      background: #fff;
    
    /* positioning the close button inside the dialog body */
      .close {
        position: absolute;
        width: 20px;
        height: 20px;
        top: 20px;
        right: 20px;
      }
    }
```

Caveats

- Cannot close the dialog with `esc` key.
- Cannot trap focus inside the dialog.

### Lightbox style image viewer

{% codepen https://codepen.io/ananyaneogi/pen/vYENrYq %}

Here’s a simplified version of the code:

```jsx
<!-- thumbnail image -->
    <a href="#img1">
      <img src="https://picsum.photos/id/999/300/300" alt="description of image">
    </a>
    
    <!-- lightbox image (visually hidden) -->
    <div class="lightbox" id="img1">
      <a href="#" class="close" aria-label="close image">&times;</a>
      <img src="https://picsum.photos/id/999/800/400" alt="description of image">
    </div>


    .lightbox {
      /* Hidden lightbox image */
      display: none;
      position: fixed;
      z-index: 999;
      width: 100%;
      height: 100%;
      text-align: center;
      top: 0;
      left: 0;
      background: rgba(0, 0, 0, 0.5);
    
      img {
          /* Add some padding to the image when enlarged */
        max-width: 90%;
        max-height: 80%;
        margin-top: 2%;
      }
    
      &:target {
        /* Show the lightbox */
        outline: none;
        display: block;
      }
    }
    .close {
      position: absolute;
      top: 20px;
      right: 20px;
      font-size: 3em;
      color: #fff;
      text-decoration: none;
    }
```

Caveats

- Both images are loading at all times. To resolve this additionally with JavaScript, we can load the larger size of image only when the thumbnail is clicked.

* * *

### Radio button controlled UI components

The thing about radio buttons is that in a group of radio buttons that has the same `name` attribute, only one of them can be `checked`.

We can use this to our advantage to create UI components that work on this principle, i.e, only one section can be selected at a given instance (such as Tabs and Accordions).

Since these components are radio buttons, you can readily navigate them with arrow keys. No extra setup is needed.

### CSS-only tabs

> #### [CSS only tabs](https://codepen.io/ananyaneogi/pen/oNgNVeq)
> CSS-Only Tabs Ilvermorny If you fancy living a Hogwarts-style life across the pond. The founder of Ilvermorny, Isolt Sayre, always wished she could go to Hogwarts, and the school definitely seems to embody some of its traditions. Hogwarts Because you love it.

<script async src="//cdn.embedly.com/widgets/platform.js" charset="UTF-8"></script>

Simplified version of the code:

```jsx
<div class="tabs" role="tablist">
    
    <!-- all the radio buttons has name i.e radioTab so at a time only one can be checked --> 
    <div class="tab" role="tab">
        <input type="radio" name="radioTab" id="tab-1" checked>
        <label for="tab-1">Label 1</label>
          <div class="content">
              Content for label 1
          </div>
      </div>
    
      <div class="tab" role="tab">
        <input type="radio" name="radioTab" id="tab-2">
        <label for="tab-2">Label 2</label>
          <div class="content">
              Content for label 2
          </div>
      </div>
    
      <div class="tab" role="tab">
        <input type="radio" name="radioTab" id="tab-3">
        <label for="tab-3">Label 3</label>
          <div class="content">
              Content for label 3
          </div>
      </div>
    </div>


    .tabs {
      position: relative;
      display: flex;
      align-items: flex-start;
      min-height: 200px; /* give a min height, can be anything you want */
    }
    
    .tab {
      display: flex;
      flex-direction: column;
    
      label {
        background-color: #0094a7;
      }
    
      /* hide the radio buttons visually*/
      [type="radio"] {
        position: absolute;
        height: 0;
        width: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
    
        /* change color of active tab */
        &:checked ~ label {
          background: #007584;
        }
    
        /* makes the active tab's content visible */
        &:checked ~ label ~ .content {
          opacity: 1;
          z-index: 1; /* increase the z-index so the content is in focus*/
        }
      }
    }
    
    .content {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      top: 3em;
      width: 100%;
      height: 100%;
      z-index: -1;
      opacity: 0; /* hides the tab content by default */
    }
```

### CSS-only accordion

{% codepen https://codepen.io/ananyaneogi/pen/vYEYqez %}

Simplified version of the code:

```jsx
<div class="accordion">
    <!-- all the radio buttons has name i.e radioPanel so at a time only one can be checked --> 
      <div>
        <input type="radio" name="radioPanel" id="panel-1" checked>
        <label for="panel-1">Panel 1</label>
        <div class="content">
          Content for Panel 1
        </div>
      </div>
      <div>
        <input type="radio" name="radioPanel" id="panel-2">
        <label for="panel-2">Panel 2</label>
        <div class="content">
          Content for Panel 2
        </div>
      </div>
      <div>
        <input type="radio" name="radioPanel" id="panel-3">
        <label for="panel-3">Panel 3</label>
        <div class="content">
          Content for Panel 3
        </div>
      </div>
    </div>


    .accordion {
      /* Visually hide the radio input */
      input[type="radio"] {
        position: absolute;
        height: 0;
        width: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
      
        &:checked ~ .content {
          max-height: 20em; /* give a max height, can be anything */
        }
    
        &:checked + label {
          background-color: #3aa13a;
        }
      }
      
      label {
        position: relative;
        display: block;
        background-color: darkseagreen;
        color: white;
        transition: all 300ms ease-in-out;
        cursor: pointer;
      }
      
      .content {
        /* so that the content is scrollable when it exceeds the mentioned max height*/
        overflow: auto; 
    
        max-height: 0em;
        position: relative;
        transition: all 300ms ease-in-out;
      }
    }
```

* * *

### Dynamic CSS-only tooltip

With the `attr()` CSS function, we can define content in our html markup using any custom property and then fetch the value.

This currently only works with the `content` property. Using a combination of these, we can create tooltips that have dynamic content in them.

{% codepen https://codepen.io/ananyaneogi/pen/GRgojdz %}

```jsx
<p>
      <span data-title="Hello in french">Bonjour!</span> How are you?
    </p>

    span {
      position: relative;
      color: blue;
      cursor: pointer;
      border-bottom: 2px dotted currentcolor;
    
      &:before {
        /* content inside will come from the data-title attribute as defined in HTML */
        content: attr(data-title); 
    
        opacity: 0;
        position: absolute;
        top: 30px;
        right: -90px;
        font-size: 14px;
        width: 100px;
        padding: 10px;
        color: #fff;
        background-color: #555;
        border-radius: 3px;
        pointer-events: none;
      }
    
      &:hover:before {
        opacity: 1;
      }
    }
```

In the codepen example, I have used `aria-label` to access the content.

In the example above, I used `data-title`, this is just to show that `attr()` can access the content from any custom property.

* * *

### Conclusion

These are just few examples that show how interactivity can be achieved with just CSS.

The next time you’re building a component, consider how much of it can be achieved just with CSS without bloating our JavaScript files.

Here are a few other awesome examples from around the web:

- [Mine Game](https://codepen.io/jcoulterdesign/pen/NOMeEb) – A game built purely with CSS [🤯](https://s.w.org/images/core/emoji/12.0.0-1/72x72/1f92f.png)
- [Page scroll indicator](https://codepen.io/MadeByMike/pen/ZOrEmr)
- [CSS only Countdown Clock](https://codepen.io/kindofone/pen/DkhAz)– made with HAML & CSS  
–[Examples of CSS-only Image Sliders](https://corpocrat.com/2015/12/09/20-cool-pure-css-sliders-without-jqueryjavascript/)

### References

- [Radio-Controlled Web Design](https://alistapart.com/article/radio-controlled-web-design/)  
- MDN reference on [`:target`](https://developer.mozilla.org/en-US/docs/Web/CSS/:target)

* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/css-only-components/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [CSS only components](https://blog.logrocket.com/css-only-components/) appeared first on [LogRocket Blog](https://blog.logrocket.com).