DEV Community

Lexi
Lexi

Posted on

Yes, CSS can do AJAX. Here's how.

Okay, I lied a bit in the title. AJAX, short for Asynchronous JavaScript and XML is not possible at all in CSS, because duh, JavaScript isn't CSS. And I'm not using XML to transport data. But the thing that's commonly meant by "AJAX" is loading, sending and processing data on a website without having to reload the entire page to render stuff on the server. And that is possible in CSS with a bit of help with HTML. Without reloading. Here's how.

Storage and Logic in HTML/CSS

You probably already used the DOM to store information without even knowing. I've seen a great hack on how to implement tabs with CSS/HTML without a single line of JS: Using <label>s, <input>s and the :checked pseudoselector.

It essentially goes like this: You make a radio button for every tab, hide them, and use a selector like .radio:not(checked)+.tab to hide unchecked tabs. You can use this to store and check booleans or enums, like this:

<button><label for=myBoolean>Click to toggle</label></button>
<input type=checkbox class=hidden id=myBoolean>
The box is currently <span id=display />

<style>
#myBoolean:checked+#display::before {
  content: "checked"
}
#myBoolean:not(:checked)+#display::before {
  content: "unchecked"
}
.hidden {
  display: none
}
</style>
Enter fullscreen mode Exit fullscreen mode

You have binary variables, and buttons to change those variables. We have logic! It will get very messy if you do more than just tabs though.

Sending data to a server

This is a bit more tricky, because neither HTML nor CSS have a method that's intended for sending stuff to a server without reloading the page when a certain thing happens. But of course there's a hack that allows us to do just that: Images.

If you include an image in your HTML file, it gets preloaded, so every request for images is done when the page loads, so we can't use that asynchronously. CSS on the other hand can do exactly that: Load images when a certain condition, or rather selector, is true. It's super simple:

:root {
  --request0: url(/ajax/request0);
  --request1: url(/ajax/request1);
  --request2: url(/ajax/request2);
}

button0:checked {
  background: var(--request0)
}
button1:checked {
  background: var(--request1)
}
button2:checked {
  background: var(--request2)
}
Enter fullscreen mode Exit fullscreen mode

Due to the browser not knowing in advance whether the image gets used at some point or not, because CSS can do things based on conditions, it doesn't preload images. So we can use that to do requests!

Getting data from a server, the easy way

Doing that is super straight-forward. You can render an image on the server with a text on it, and embed that into the HTML. Problem is: You cannot work with that in CSS. The results are visible to our user, but not the CSS.

Getting data from a server, the <audio> way

I found a neat pseudoselector while researching: :playing. It does exactly what it says: Select playing elements. So you can send an audio file as true, and no audio file as false to transmit one bit. There are two caveats though, one bad, and one very bad. The user has to click on the audio player, which is bad. The other thing is, the :playing pseudoselector is currently not even implemented in any browser yet. So that's impossible.

Getting data from a server, the incredibly cursed way

There is another way though. And it even works with Internet Explorer 8. Yup.

The content attribute in CSS can effectively transform a <div> into a <img>. You use it like this:

div#myImage {
  content: url("https://path.to/image");
}
Enter fullscreen mode Exit fullscreen mode

The big difference though is that the image only gets loaded when the selector is active, so we can use it asynchronously. But that only allows users to see the data, right? Wrong.

Here's the cursed way to get one bit data into an HTML element: You load two images from the server. One 1x1 pixel image (we'll call that false), and one either 1x1 or 1x0 pixel image (we'll call that true), depending on what you want to send. Now you load them into your HTML like this:

<input type=checkbox id=true >
<input type=checkbox id=false>

<button><label for=load>Do AJAX request</label></button>
<input type=checkbox id=load>
<div class=relative>
  <label for=false><div id=false></div></label>
  <label for=true ><div id=true ></div></label>
</div>
Click on the square above!

<style>
.relative { position: relative; height: 20px }
.relative > label { position: absolute }
.relative > label > div { width: 20px }
:checked + div #true  { content: url("/true");  z-index: 2 }
:checked + div #false { content: url("/false"); z-index: 1 }
</style>
Enter fullscreen mode Exit fullscreen mode

Now prompt the user to click the black square, and depending on whether the true image, and therefore the element, is 20px or 0px high, the user will click the label for the correct checkbox. Now we can read the checkbox with more CSS selectors, and the best thing is: This is even compatible with IE8. In practice, you cannot use a 0x0 image, so I used a 1000x1px image for that, but that essentially does the same.

And this also has one cool side effect: You can make the request wait as long as you want. If you want to do something on the server that takes a bit, you can send the true image only when the task is done.

And there we go! We have AJAX now!

Writing an example app with CSS AJAX

So let's actually used our cursed creation now. I wrote a super simple online store that checks whether enough items are available with CSS AJAX. I've tweeted the demo (for some reason, the video isn't working in the Glitch embed, sorry :( ), or if you want to try it yourself and look at the code, you can remix this Glitch.

Thanks for reading!

Latest comments (2)

Collapse
 
pcd444 profile image
pcd444

Cool. Does caching ever get in the way?

Collapse
 
lexi profile image
Lexi

I've tested it only on Firefox and Chrome for Android but it seems like the images get loaded again every time if the server doesn't say so, so AFAIK no :)