Header

5 things you don't need Javascript for

Javascript can do a lot, but it's really over-used. HTML and CSS are surprisingly powerful on their own, so let's have a look at some of the things you can achieve without Javascript (or a backend) - from animated diagrams to dark mode.

Originally posted on the Lexoral blog

This post has been updated. Expand for more details.

This post was updated 2022-03-04 based on discussions on Hacker News.

  • Warned about potential performance issues of complicated svg-based animations
  • If your accessibility settings indicate you prefer less motion on the page, the first example is now hidden by default with an explanation, rather than simply not animating
  • Sticky positioning example fixed to work on Safari - overflow must be set to a value other than auto
  • Indicated that checkbox-based dark mode is not a replacement for the CSS media query, and should be used in tandem
  • Added more discussion around progressive enhancement, and reinforced that these examples are not production-ready

You can also see the full commit history for this post.


Every day, I see people use Javascript to do things that are supported by default in good old HTML & CSS. That's usually a bad idea - it's much slower, can cause content to jump around the page after loading, and breaks your site for people with crappy browsers. I was determined to do better, and built this landing page and blog without any client-side Javascript.

Along the way, I discovered just how much CSS has to offer, and I'd love to share some of that with you today. I know a lot of people are a bit scared of CSS, and see it as black magic, so I won't be showing you anything too complicated. Instead, I'm just going to focus on simple techniques and overlooked features - things you could easily incorporate into your own sites.

1. Animating SVGs

You probably know that you can animate HTML elements with CSS. However, did you know that you can also animate SVGs, in exactly the same way? One of my favourite techniques is setting the stroke-dasharray property to a high value, then animating stroke-dashoffset. That results in the path being 'drawn' over time, or a line moving along a path, like in this animation of some fireworks:

Your accessibility settings indicate you are sensitive to animations and motion on the page, so this example is hidden by default.

Show anyway
Show the code

Animation.svelte

<svg viewBox="0 0 100 100"> <path class="flight" d="M 0 100 C 35 92 49 76 50 50" /> <path class="trail" d="M 50 50 C 41 23 26 23 1 41" style="stroke: yellowgreen" /> <path class="trail" d="M 50 50 C 30 43 14 51 0 100" style="stroke: turquoise" /> <path class="trail" d="M 50 50 C 84 46 96 63 100 85" style="stroke: goldenrod" /> <path class="trail" d="M 50 50 C 71 31 95 43 100 63" style="stroke: mediumorchid" /> <path class="trail" d="M 50 50 C 61 -6 76 3 73 100" style="stroke: firebrick" /> <circle class="explosion" cx="50" cy="50" r="20" /> </svg> <style> svg { height: 20em; width: 100%; background-color: darkslategrey; border-radius: 2em; } svg :global(*) { animation-iteration-count: infinite; animation-timing-function: linear; animation-duration: 10s; fill: transparent; stroke-linecap: round; stroke-linejoin: round; } .flight { stroke: gold; stroke-width: 2; stroke-dasharray: 10 100; stroke-dashoffset: 10; animation-name: flight; animation-timing-function: ease-in; } @keyframes flight { from { stroke-dashoffset: 10; } 21%, to { stroke-dashoffset: -80; } } .explosion { fill: orangered; opacity: 0.8; filter: blur(1px); transform-origin: center; animation-name: explosion; } @keyframes explosion { from, 19% { transform: scale(0); } 20% { transform: scale(1.5); } 22% { transform: scale(0.5); } 23% { transform: scale(0.8); } 25% { transform: scale(0.2); } 26% { transform: scale(0.4); } 35%, to { transform: scale(0); } } .trail { stroke-width: 2; stroke-dasharray: 1 10 5 10 10 5 30 150; animation-name: trail; animation-timing-function: ease-out; } @keyframes trail { from, 20% { stroke-width: 3; stroke-dashoffset: 80; } 100%, to { stroke-width: 0.5; stroke-dashoffset: -150; } } </style>

For another, more advanced example of how you can create animated diagrams with CSS, check out our landing page! The How it Works section contains a detailed animation. If you want to copy from it, Lexoral is open-source on GitHub.

Creating an animation like that is certainly more complicated than a simple video, and very detailed animations can cause hard-to-debug performance issues. However, there's plenty of reasons to prefer an animated SVG:

  • Negligible bandwidth use
  • Better device compatibility
  • Perfect quality at any resolution
  • Responsive & Scalable
  • Can be checked in to VCS

If you want to learn more about CSS animations, I can recommend starting with the MDN guide. To create the SVGs, either use a visual editor like Inkscape, or a more dev-oriented tool like this one. Often it's easier to just write the SVG markup by hand, in which case MDN has a great reference page.

2. Sidebars

You want a side navigation bar that hides when it's not in use. Put away that script file, we've got CSS for this. We'll use transform to move it off-screen by default, then override that when the user hovers over the element. Importantly, we should also show the menu when you use the Tab key to select one of the links in the menu - an important step towards keeping our site accessible:

← Hover

(or use tab)

Show the code

Sidebar.svelte

<div class="container"> <nav> <a href=".">Option 1</a> <a href=".">Option 2</a> <a href=".">Option 3</a> <a href=".">Option 4</a> </nav> <p>← Hover</p> <p>(or use tab)</p> </div> <style> .container { overflow: hidden; position: relative; height: 15em; max-width: 25em; margin: auto; border: 0.2em solid black; } nav { display: flex; flex-direction: column; position: absolute; right: 100%; padding: 1em; background-color: skyblue; transform: translateX(1em); transition: 0.2s transform; } nav:hover, nav:focus-within { transform: translateX(100%); } a { white-space: pre; color: black; } p { font-size: 2em; text-align: center; } </style>

This example is just clever use of pseudo-classes in CSS. You probably skipped that link, but go back and click it. Even if you think you know everything about CSS, click it and read down that list pseudo-classes. Look how many there are. Think about how you could use them.

It's too easy to see :hover and assume it's just for styling hyperlinks. We've just seen it used to create a sidebar, but here's 9 more ways you could use it:

  1. Dropdown menus
  2. Tooltips
  3. Showing Video Thumbnails
  4. Scaling up an image preview
  5. Showing a preview of the link destination like on Wikipedia
  6. Highlighting the important parts of a complex interface
  7. Styling the hovered row in a table
  8. Previewing what will happen if you click a button
  9. Making the 'Reject Cookies' button move out of the way when people try to click it

That last one was a joke. Please don't do that. I'm serious. I refuse to take responsibility if you do that. Please.

A few of those ideas would need a delay before the hover effect activated - it would be quite hard to use Wikipedia if a popup obscured half the screen every time you moused over a link. You might have to fall back to using Javascript in that case, but try the transition-delay property first.

3. Sticky Positioning

PSA: position: sticky; exists now. Please stop reimplementing it with Javascript. Some of you are confused why I'm mentioning such a basic feature. If that's you, skip to the next section.

Some of you have no idea what I'm talking about, and are feeling pretty self-concious. When you want an HTML element to move down the page as you scroll, you can do that with plain CSS! Not only that, it actually works way better than a Javascript version:

Scroll down the page

And watch the blue square 🡖

The blue square follows you

As you scroll past it

And if you scroll back up

It goes back where it was

Show the code

StickyPositioning.svelte

<div class="scroller"> <p>Scroll down the page</p> <p>And watch the blue square 🡖</p> <div class="square" /> <p>The blue square follows you</p> <p>As you scroll past it</p> <p>And if you scroll back up</p> <p>It goes back where it was</p> </div> <style> .scroller { max-width: 25em; height: 10em; margin: auto; padding: 1em; border: 0.2em solid black; overflow-x: hidden; overflow-y: scroll; } p { height: 5em; } .square { height: 5em; width: 5em; background-color: lightskyblue; margin-left: auto; position: sticky; top: 2em; } </style>

Notice how when you scroll down, the blue square is always in exactly the same position. It's easy to tell when someone has tried to DIY their sticky positioning, because the element always lags one frame behind when scrolling. The faster you scroll down, the higher up the screen it would be. Then, when you stop scrolling, it jumps to the correct position.

Sticky positioning is a combination of relative and fixed positioning. It's mostly used for navigation menus that should follow you as you scroll down the page. It's used on this page too, if you are viewing it on a large landscape screen. The panel to the right with my face on it uses sticky positioning, meaning I'm always staring at you as you read my blogs.

To use sticky positioning, first you need to set position: sticky and provide distances for some (usually only one) of top, right, bottom, and left. When the element is scrolled past that position on the screen, it swaps to fixed positioning, and starts following you. If you scroll back, it returns to its original position and stays there. They don't follow you forever though - a sticky element will never move outside of its containing element.

It's a little difficult to wrap your head around, but I'd recommend reading through the MDN article on the position attribute. I use position all the time, so I think it's worth having a good understanding of all the possible values and what they do.

4. Accordion Menus

Accordion Menus are commonly used for FAQs, where you have a list of headers and each one can be expanded to show the content inside. This is another case of me telling you about a fairly basic feature of HTML+CSS, but <details> exists:

FAQ

Why is it called an accordion menu?

Because each part of it can expand and contract, like in an accordion. If you don't know what an accordion is, just imagine a cute fluffy cat. You still won't know what it is, but at least you'll feel better about not knowing.

Huh?

Huh.

If I use an accordion menu will it make me cool?

No, not unless you're designing a MySpace profile. The <details> element is cool though, and you can use that for a lot of things. I'm using it on this page right below here, to show the code for each example!

Show the code

Accordion.svelte

<div class="container"> <h3>FAQ</h3> <details> <summary>Why is it called an accordion menu?</summary> <hr /> <p> Because each part of it can expand and contract, like in an accordion. If you don't know what an accordion is, just imagine a cute fluffy cat. You still won't know what it is, but at least you'll feel better about not knowing. </p> </details> <details> <summary>Huh?</summary> <hr /> <p>Huh.</p> </details> <details> <summary>If I use an accordion menu will it make me cool?</summary> <hr /> <p> No, not unless you're designing a MySpace profile. The <code >{"<details>"}</code > element is cool though, and you can use that for a lot of things. I'm using it on this page right below here, to show the code for each example! </p> </details> </div> <style> .container { padding: 1em 2em; border: 0.2em solid black; border-radius: 2em; } details { border: 0.1em solid black; padding: 1em; margin-bottom: 1em; border-radius: 1em; } summary { font-size: 1.2em; cursor: pointer; } </style>

It's not always a good idea to use accordion menus due to them having a lot of usability issues. However, the <details> element is super useful when you want to hide some content from the user, especially if it's very long and most users won't want to read it. For example, I used one just above here to hide the source code for the example.

If you're not sure whether to use a details element, try reading the NHS service manual for it. They've done way more usability testing than you ever will, and have some decent guidelines for when it's appropriate to use.

One downside of a pure-CSS approach here is that details is tricky to animate without Javascript. It's possible to animate the box opening using transition, as long as you know the height of the box when open. Currently, browsers don't natively support animating the element being closed, so you'll have to restort to Javascript or a custom in/out animation.

5. Dark Mode

You can have an entirely functional dark mode on your website without any Javascript, cookies, or separate URLs - all in the browser. There's 2 components to this. First, you should be using the prefers-color-scheme media query. This allows you to automatically display a light-mode or dark-mode version of the site, based on the user's preferences.

However, the best way to make your website accessible is to give the user choice. Maybe they like dark mode usually, but on your website they would prefer light mode. We can make use of :checked - a CSS selector that only matches checked checkboxes. You might be able to see where this is going:

My Website

This is some text

This is my website where I put my text. I hope you like it. I have now used up my entire text budget and I have none left, goodbye.

Show the code

DarkMode.svelte

<div class="container"> <input class="lightOverride" id="lightModeCheckbox" type="checkbox" /> <label class="lightOverride" for="lightModeCheckbox">Light Mode</label> <input class="darkOverride" id="darkModeCheckbox" type="checkbox" /> <label class="darkOverride" for="darkModeCheckbox">Dark Mode</label> <div class="body"> <h3 class="title">My Website</h3> <p>This is some text</p> <p> This is my website where I put my text. I hope you like it. I have now used up my entire text budget and I have none left, goodbye. </p> </div> </div> <style> .container { position: relative; border: 0.2em solid black; max-width: 25em; margin: auto; } .body { padding: 1em; } input, label { position: absolute; top: 0.5em; right: 0.5em; height: 1.8em; } label { padding-right: 1.2em; user-select: none; } .title { margin: 0; } .darkOverride { color: black; } #darkModeCheckbox:checked { color: whitesmoke; } #darkModeCheckbox:checked ~ label { color: whitesmoke; } .lightOverride { color: whitesmoke; } #lightModeCheckbox:checked { color: black; } #lightModeCheckbox:checked ~ label { color: black; } @media (prefers-color-scheme: light) { .lightOverride { display: none; } .body { background-color: white; color: black; } #darkModeCheckbox:checked ~ .body { background-color: darkslategrey; color: whitesmoke; } } @media (prefers-color-scheme: dark) { .darkOverride { display: none; } .body { background-color: darkslategrey; color: whitesmoke; } #lightModeCheckbox:checked ~ .body { background-color: white; color: black; } } </style>

This is actually a more general technique, which works any time you want to let the user toggle between two different versions of an element, or two different styles. For example, I used it in my last blog post to toggle between different versions of a code snippet.

The trick is to combine the :checked pseudo-class with the ~ sibling combinator. A CSS rule like p ~ a applies to all <a> elements that have a <p> element sibling before them in the HTML document.

The actual rule used is #checkboxId:checked ~ .body - which applies some styles to the body class only when the checkbox is checked. Since the affected element must be a subsequent sibling, we can't put the dark mode checkbox inside the body element. Instead, we put it before the body element, then use position: absolute to position it inside.

This isn't just a toy example either. Most browsers will automatically remember the checkbox state, meaning you can save the user's preference for free - try ticking the box and refreshing this page! It won't always work, so you should probably include a little bit of JavaScript to store the user's preference in local storage.

Conclusion

It's daunting to learn the ins-and-outs of HTML/CSS. It can be frustrating when you know exactly what you want it to do but can't figure out how to tell the computer. However, it's easier now than ever before to make websites look great. CSS has had 20 years worth of development, and is full of new features to make life easier. If you want some further reading, check out:

Some of the things I've mentioned are reasonably new, meaning that if you need to support Internet Explorer, these techniques probably aren't for you. For example, position: sticky and :focus-within aren't supported in any version of IE. Hopefully you can forget that Internet Explorer ever existed, in which case everything I've used here has close to 100% support in browsers!

Despite that, it's still best to embrace progressive enhancement. You should assume that your users will be using the most barebones browser imaginable, and make your site work as well as possible. Then, assume they're using a slightly better browser, and add things that make it work even better for those people.

It's like when we were talking about dark mode. I recommended that you should include a small piece of JavaScript code to maintain the state of the user's override checkbox. If they didn't enable JavaScript, the website would still work fine, but you've used that code to make it slightly better when JS is available. A purely JS solution would be completely broken for anyone without JavaScript. And that's a lot of people.

Anyway, I think I've shown how powerful of a tool CSS can be when you really stretch yourself. The point of this post was never for you to just copy my example code, though you should feel free to do so. Instead, it's to give you some pointers towards the more useful parts of the language, and encourage you to explore what the more basic parts of web dev have on offer.

Send me a message if you make something cool!