By Tim Evko

You Don’t Need JavaScript for That!

By Tim Evko

Web development today can be a whirlwind of various technologies, and even the simplest of widgets can often be complex under the hood.

With that in mind, I’d like to focus on a variety of things you can do with just HTML and CSS, no JavaScript required. Why, you ask? While some of these solutions may not be practical for every use case, they do inspire outside-the-box thinking that promotes lowered complexity, and a wider array of browser support. Here are a few cool things you can do without having to write a single line of JavaScript.

Building a Tabs Widget

We’ve all seen how the checkbox hack can be used for interactivity without JavaScript, but unfortunately, this comes at a cost to accessibility. So instead of another checkbox tutorial, I’d like to show how you can create an accessible tabs widget using the :focus pseudo-class in CSS.

Using :focus

The :focus pseudo-class is used to target an element that has received focus by the user (either by using the keyboard or the mouse). It is supported in every browser including IE8+. Additionally, you can apply a :focus state to any HTML element as long as you give it a tabindex attribute.

Using the tabindex Attribute

HTML’s tabindex attribute indicates if an element can receive focus. It can take several values, including a negative value, zero, or a positive value. Each of these values determines what order an element should be focused on.

Demo for the Tabs Widget

See the Pen Pure CSS Tabs Widget by SitePoint (@SitePoint) on CodePen.

In the HTML, each tab is a button with the content for each tab inside of a paragraph element. Each paragraph element is hidden, then set to position: absolute so that the content will display in the same area for each tab. The first button element has the autofocus attribute, so that the first tab will be visible on the initial page load. Each tab is wrapped in a div, and each div is given a tabindex value, which allows the <div> to be focusable.

As for the CSS, each button is set to display: inline-block, allowing for the tabs to appear side by side. We apply the :focuspsuedo-class to the button and use the adjacent sibling selector to show the related paragraph element when the button is focused. I’ve also added focus styles to the tab’s container div, which means that the tab will keep its focused state until the user clicks outside the entire widget.

Caveats for the Tabs Widget

Because this widget depends on the :focus psuedo-class, the tabbed content will be visible only when the tabs have a focused state. This means that the user will always have to click on a tab to see content. The autofocus attribute does allow one of the tabs to be visible on initial page load, but as soon as the user interacts with the page, the focus will be lost.

Browser Support for the Tabs Widget

I’ve tested this in IE8, and everything works as expected, except for the autofocus attribute, being that it’s an HTML5 feature. Chrome, Firefox, and Opera also work as expected, but the demo does not work in Safari unless the tab key is being used to apply focus. In the following demo the :focus pseudo-class doesn’t work in any of the versions of Safari that I’ve tested, which I suspect is due to a WebKit bug. I haven’t found a way around this issue, but as long as the WebKit bug is addressed, this technique should work just fine when the user clicks on the tabs.

Overall, the :focus pseudo-class can be a great way to add functionality to HTML and CSS, without having to invoke the use of JavaScript. If you enjoyed this demo, be sure to leave a comment, and more importantly, file a bug with Safari!

Building an Image Slider

If you need a simple static site with an image slider, a CSS only approach is a great way to keep your website fast an light. Let’s look at a quick example.

HTML and CSS for the Slider

We start with an outer container using a section element with a class of slideshow. Inside is a slideshow container where the images are placed. This slideshow also works with content other than images. To highlight that, we have a div with a class of text-container that is used to hold our non-image content.

The outermost container of the demo has a fixed width, and its overflow property is set to “hidden”. The container inside of this, which holds our slide content, has a much larger width, so that the images and text content can be placed next to each other without wrapping to a new line. It’s important to note that this container has to be exactly as wide as our slide content, or else the slides won’t transition properly.

Using CSS Animation

The slide class is where we apply the animation. The animation uses the translateX property to drag the long row of content across the outer container, which forms a mask, simulating the transition of slides.

The animation timing is one of the most important factors here. To get an even number of seconds between slide transitions, we need to multiply our desired transition time by the number of slides in our slider. Here we have four slides, and our desired transition is six seconds per slide – so our animation time needs to be 24 seconds long before it repeats. The infinite repetition is controlled by the “infinite” value on the animation-iteration-count property.

Setting up the Keyframes for this animation can be tedious. The goal is to get the animation to look like it pauses for each image, even though it never really does. To do this we need to define at least two empty keyframes per slide. This creates the pause effect, because the keyframes define key intervals in which the translateX property does not update. You can see this in the demo on line 66 of the CSS.

As an added bonus, hovering over the slider will pause the animation. This is done by setting the animation-play-state property to “paused” when targeting the slideshow-container element’s :hover state.

The Image Slider Demo

See the Pen Pure CSS Image Slider by SitePoint (@SitePoint) on CodePen.

Browser Support for the Image Slider

Works as expected in all browsers including IE10 and up!

Creating Icons with File Type Indicators

A while ago I was working on a WordPress site when I came across a potentially challenging design. In this case, different icons were needed to represent links based on their file types. This was a great time to present a JavaScript-free solution on a JavaScript-heavy website. The following demo shows how to get information from a block of HTML and display it with the CSS content property.

HTML and CSS for the File Type Icons

In this demo, I’m using data attributes to display the type of file that the user will be redirected to when they click the link.

The CSS content property takes as a value an attr() function that allows us to get the value of our HTML data attribute. Using the ::after pseudo-element, we create a small black bar that holds the tet in the content property, and we position it to display on the left side of our icon.

The CSS attr() function represents an exciting part of the specification, but currently does not have any browser support for use outside of the content property.

File Type Icons Demo

See the Pen File Type Icons with Data Attributes by SitePoint (@SitePoint) on CodePen.

Browser Support for the File Type Icons

I was surprised to find that this demo worked in every browser I tested it in, except for IE8 of course!


It’s becoming more common for developers to build things without JavaScript. These might not be great solutions for all use cases, but they’re options to consider and learning the concepts involved can deepen our understanding of CSS.

  • Efren Castillo

    HTML rocks! Thanks for sharing.

  • This is a really cool CSS example, but it’s actually no better for accessibility than the checkbox hack since you have to keep focus on a tab to have the related content be viewable.

    Other than that, it’s really darn cool though.

    • Tim

      Thanks for the input Scott! Be sure to check out the other demos in the article too!

      • Heya Tim,

        I did read through the entire article and I’m sorry that I didn’t comment on the rest of it. They’re all very clever examples. I particularly liked the image slider one.

        I think the first demo just stuck out to me because it is the lead in, and the wording makes it sound like it’s an accessible solution, when it really isn’t. It does allow for some keyboard controls, but that doesn’t actually equate to being accessible.

        With that said, it doesn’t detract from the creative use of CSS in that demo / the rest of the demos here.

    • JJJ

      Hi Guys,

      I made one depends on Scott’s post, with a hidden input group, I think it’s stable than focus behavior.

      • ket

        This is flawed. The divs must have the same height and width to hide the other divs behind.

  • Alex Sofronie

    Well, I am on Safari (Mac) and the tab example works exactly as expected.

    • Tim

      That’s great to hear! I think this works correctly because focusing on the container div OR the button will show the hidden content. I’m pretty sure that relying on the button focus to show the hidden content would break the demo in safari. when I first wrote this article, the tabs demo was implemented a bit differently, which I had since revised, and forgot to re-check for the safari bug. Thanks for pointing this out!

  • Dean Hamack

    I would argue that this tabs menu example is *less* accessible than a traditional tabs menu with three anchor links in a list, followed by three divs. If I’m using a screenreader, and I want to just read the content in the third section, using this example I would have to read through the first two sections first. Whereas if I have three links right at the beginning of the page that tell me what’s in those three sections, I can jump right to the one I want using the anchor link menu.

    • Tim

      I don’t necessarily see a downside to this. Each div is tab-able, meaning you can tab to each section without having to read through it first

      • Dean Hamack

        I also have an issue with buttons that do nothing except change the style of an element. It’s not semantic, and likely to be confusing to the screen reader user as well. Replace the buttons with H2’s however, and then we’re talking; accessible, semantic, better for SEO, and no unnecessary/confusing markup.

  • This article is a great reminder to us Front End Developers that we don’t need to overload our JavaScript with what CSS can do (it’s great for performance too). I’m learning a lot here. Thank you.

  • Tim

    I invariably choose CSS-only solutions wherever possible. My philosophy is to start from the most organic and internal solution until you can’t go any further. (Which is why even though nowadays I’m building a lot of WordPress sites, I still avoid it when I can. It’s unnecessary overhead, complexity and vulnerability for a lot of uses.) So I handcraft CSS-only dropdown menus, and on a site I’m currently working on, yes, I do confess to abusing the checkbox. :)

  • Chu Quang Tú

    I’m very excited with your example with tabs. I’m gonna try it today. Thank you for this very good work

  • Craig Buckler

    As an alternative, you can also use :target instead of :focus…

  • Dennis Lembrée

    JavaScript is required to make a tab widget accessible to screen readers (via ARIA) and to follow the keyboard pattern recommended by the W3C (use arrow not tab to tabs). Also, here’s an article on making an accessible toggle switch with only HTML and CSS:

  • Hey Tim, great post. It turns out that I just found it after writing my own post on pure CSS tabs yesterday (I’ll link to it below; hope that’s okay). This leads me to a question about the pure CSS tabs that you’ve created and IE8 compatibility.

    When I wrote my own post about this, I actually tried the exact technique that you explain here, using the :focus pseudo-class and sibling selectors. However, I found that this doesn’t work in IE8 (at least for me!); in fact, I just tested your version and the tabs don’t work in that scenario (the tabs are clickable, but no tab box content shows up). You say you’ve tested this in IE8 and that it works for you, though, so I’m surprised/confused! Can you say more about how you tested it? (I’m using the IE8/WinXP virtual machine provided by Microsoft for web developers.)

    I actually also found a discussion on Stack Overflow suggesting that IE8 doesn’t work right with :focus and sibling selectors (I linked to it in my own post on this topic, if you want to check out the Stack Overflow discussion). I’d be happy to hear that this isn’t correct, though!

    Thanks again! And here’s the link to my post:

    • Tim

      Hey Jason, thanks for this comment. Some of the css for this demo was changed after I wrote the article, so that the demo would look more like a tabs widget. That may have contributed to the demo not working so well in IE8. I just tested on browser stack and it is working for me, but you have to click the body of the demo first (outside of the tabs widget). See here –

  • Jim Mortenson

    Excellent! I like that slider–although when I run it locally as a standalone file it seems to hang, trying to get off the first image in (guess who?) IE. Specifically, IE11 running on x64 Windows 7. Any ideas?

  • Thanks for the reply, Tim! You’re right that a tab box will show up if I click the body of the page first, then click a tab (sorry, I forgot to try that with your demo; I should have, as I had initially encountered a similar issue when I tried it on my own). But even then, it turns out (at least in my version of IE8) that I also have to click on the body of the page in between *every* click on a tab in order to see that tab’s content. In other words, if I click on the page, then click a tab, and thereby get that tab’s content to open, I can’t just click on another tab to switch between their contents. Instead, after the first tab’s content shows, if I want to see another tab’s content, I then have to once again click elsewhere on the page first, *then* click the tab I want. That situation won’t be a great experience for most users. :( (It’s IE’s fault, though, not yours!)

    Is it working differently for you after you click on the page the first time? (I couldn’t view the gif you posted, unfortunately, to see what was happening for you.) If it’s not, and you do have to click on the page in between every click on a tab, then I think that’s essentially the same bug I was encountering when I tried using both :focus and sibling selectors in IE8. I mentioned it briefly in my post that I linked to in my previous comment (under the “Option 2” section); apparently IE8 has a bug of some sort that requires you to unfocus elements before focusing on another element if you want the sibling selector styles to take effect, or something along those lines.

    BTW, I also had to change the positioning of the content in IE8 in order to get it to display properly; it was otherwise covering the tabs whenever I got it to display.

  • Al Rios

    Your slider is not working in IE11

  • Tim

    Thanks for the notification Al! That’s actually do to a bug, and it’s fixed by adjusting the css keyframes, particularly the last 100% value, from 0 to 0.1%. See on line 91

  • I love it! Keep it all CSS!

  • Any particular reason you are using the data attributes for the “File Type Icons Demo”? I’ve done something similar plenty of times, but instead using the CSS attribute selector combined with the “ends with” designation


    • Tim

      Hey Oscar, that way will definitely work, but you would have to write css for every possible file type. If the file is a pdf for example, and you didn’t add support for that in your css, then the icon won’t work. Using data attributes, you can support all file types with minimal css

      • Gotcha. Looking through the code, your method is much more versatile and efficient, pulling the visual label text from the file type itself. I also like the ability of setting a default label of “file”. Good stuff.

  • Surendra Vikram Singh

    Thanks for sharing! It is always a pleasure reading solutions using CSS.

  • David Maxwell

    Question for you. I tried to modify your slider example so I could use different size images, but I’m missing something obvious. I added a container around the images and set the css for those containers instead of the images, but it’s not lining up.

    I put it into this codepen to show what I’m getting:

    Any ideas why it would be off?

  • Adam Allard

    Is there a way to trigger an event when a tab is selected? For example, I click Tab 2, can that also call a function and pass the Tab index?

Get the latest in Front-end, once a week, for free.