Build a Color Contrast Checker with Eleventy Serverless

I spoke at Jamstack Toronto's 2021 edition of the 11ties — a series of 11-minute lightning talks about the Eleventy static site generator. In my demo, I showed how you can use Eleventy's new Eleventy Serverless feature to build a color contrast checker application that requires zero client-side JavaScript.





[00:00:13] Henri: I may discuss the Jamstack, I will talk about web performance, but I'm also a fan of accessibility, especially anything that has to do with the human visual system. Did you know that color contrast is the number one WCAG failure? Well, you do now. This is why I thought the next presentation would be very interesting. Here to build, or talk about building a color contrast checker using Eleventy Serverless is our good friend Ben Myers. Enjoy!

[00:00:44] Ben: You should be seeing a whole lot of purple. Fair warning, today's going to have a whole lot of purple!

[00:00:50] Alright. Howdy, howdy! It is an honor to kick off today's 11ties. I'm super excited to be here. Today I'm going to be demonstrating how to build a color contrast checker with Eleventy's hot new feature, Eleventy Serverless. I'm super excited to be going through this.

[00:01:05] Now, because this is a lightning talk, I know that this is going to go fairly quickly. There's going to be a lot of code that I have sped up the recordings of. And so, you're probably going to wanna see the source code, you're gonna wanna see the slides, you're gonna wanna see the actual demo. All of that is available for you at The slides will be forthcoming, but they will be at that destination as well. So if ever you need anything, that's where to go, is

[00:01:32] A bit about myself, just real quickly. Hi, I'm Ben. You can find me on Twitter at @BenDMyers. And the long story short is I blog and stream about accessibility and core web technologies and sometimes about Eleventy. So that is the world's quickest summary. My cat is just as famous as I am. His name is Tuna. He is delightful, and he's everyone's favorite coworker!

[00:01:56] Yeah! So what are we going to be doing today? We're going to be building a color contrast checker, an experience that looks an awful lot like this. It, you know, takes two hex code colors and it spits out their contrast ratio. Now we can imagine, if we were going to be doing this with Eleventy before Eleventy Serverless, we were going to have to generate a page for every possible combination, every pair of colors. So any two hex code colors.

[00:02:24] Assuming six digits for each hex code color, the math on that is astronomical. That would assume we would have about 2.75 × 10^14 pages that we would have to render. And we would pay that cost every time we rebuilt our dev server, as well as every time we redeployed! And that is hugely wasteful. Like, I just… I wouldn't want to make any changes. I wouldn't want to have to go in and, like, update the footer or anything like that anytime I needed to update the design because this is an exorbitant number of pages. So prior to Eleventy Serverless, we had no good way to handle anything of that scale with Eleventy, right? I'm sure that there were ways, there were workarounds, but now we have Eleventy Serverless, which is a first-class solution to this.

[00:03:13] So we're going to take this experience. I've actually already built out the template for this. It's got values hardcoded in it. We're going to take this experience and we're going to make it a serverless application.

[00:03:23] So, step one is we're going to be installing Serverless. You are going to need to have a version of Eleventy that is the 1.0 canary 39 or above or beta 1 through 4. And once you've got that installed, you can add the Serverless plugin to your Eleventy config like this. I'm actually on a canary, so we're going to see that I'm adding an input directory that is no longer necessary. I am behind the times here.

[00:03:51] But long and short, what we're saying is, hey, we're adding the Serverless plugin here. We're going to call this plugin 'onrequest'. You can call it whatever you want, but I'm just giving this this name because that's what makes sense to me, is like, oh, every time I request one of these pages, I'm going to return something. So I'm calling it 'onrequest'. And then I'm saying that my serverless functions will live in the netlify/functions directory here. So that's, real quickly, all you need to do to add this to your Eleventy config.

[00:04:19] And then the next time you build your project, you're going to get a whole bunch of stuff like this. These are these serverless functions that are generated for you, all of that code. We actually don't want to deploy all of this. The only thing we really need to deploy is our index.js. So we're going to go into our .gitignore and exclude everything else. That way, Netlify can generate those other things for us during build time. So to do that, we ignore netlify/functions — any file inside netlify/functions except for that index.js.

[00:04:54] Now to call out, we could make changes to that index.js if we wanted, specifically if we were interested in using Netlify's on-demand builders for caching our color contrast pages. However, as we're going to see later, I'm going to take advantage of query parameters in our routes, and that is not supported by Netlify's on-demand builders, so I'm going to use all the default serverless function configuration here. I'm not making any changes. But if you need, the Eleventy docs do tell you how you can use on-demand builders and what changes you would need to make to these files. So, that is itself a bit of a caveat.

[00:05:33] So now that we've got Eleventy Serverless all installed in our project, we need to make sure that our contrast ratio template is serverless. And so to do that, we're going to first go into our template. Here I've got my contrast.html template. And I'm going to add a permalink! But unlike the permalinks that we're used to which are strings, this one is an object. It has a key of onrequest, which matches what we called it in our .eleventy.js, and the destination is '/contrast/'. So anytime someone goes to the /contrast/ route, it will be generated for them by the serverless function instead of using anything that was built during build time. So currently, there's no change to the experience here, just a change to how the page is actually created and delivered.

[00:06:20] So, we have now specified that this template is a serverless template and we can start to use some nifty things! Like, for instance, we can start to use query parameters that were applied whenever someone went to our page. So here, I'm just going to update the display so that we're no longer using our hardcoded hex codes, but rather we're using hex codes from the URL.

[00:06:42] So here we go! We're accessing the eleventy.serverless.query.foreground and .background properties, so if you use the foreground and background query parameters, those are now injected into the page. And we can actually see that here. I've got the hex codes #4c1074 and #ee4433 in my query parameters. That "%23," that's the URL-encoded hash. So we're now displaying the hex codes that were provided through the URL.

[00:07:12] So we're already starting to get some dynamic behavior depending on the query parameters that you provided when you hit this page, but the next thing is we need to make sure that the ratio actually works. Currently, the ratio is hardcoded to 21, and that would be kind of useless as a color contrast tool. So let's go ahead and generate our ratios.

[00:07:34] To do this, I'm actually going to use some computed data. So I'm creating a contrast.11tydata.js data file. This is a data file that corresponds to the contrast.html template. And in there, I'm creating some computed data. Specifically, a property called ratio, which uses that foreground and background that was supplied by Eleventy Serverless, and it returns the ratio here. And then I'm actually creating a second computed data property called formattedRatio, which does a bit of handy formatting because after, you know, two degrees of precision, you really don't need any more specificity there. So, this injects two new data properties into our data cascade: one called ratio, which is the fully qualified ratio number, and then one called formattedRatio, which is the truncated version of that. And it's specifically using those two query parameters.

[00:08:30] So now, at this point, we want to actually use this ratio in our templates. So let's go ahead and override our hardcoded ratio here. There we go! We provide our neatly formatted ratio. And now, whenever you get to this page, you can see that it says, like, oh, this specific pairing has a ratio of 3.37:1, or 3.86:1.

[00:08:55] And at this point, we have a minimum viable product in not that many lines of code. It totally works. It pulls dynamic information from the URL and it spits out the correct response. But I actually think we could go a little further. This isn't very usable. You have to, like, just kind of memorize the URL you need. So let's add some navigation, and we're going to do so completely zero client-side JavaScript. So let's go ahead and do that!

[00:09:24] I'm going to take advantage of the <form> element, which does not need any JavaScript to work. This is actually why we used query parameters in the first place, is because our form fields will get translated into query parameters when we navigate, so this foreground field gets translated into the foreground query parameter. There we go. We've just added a handy form there.

[00:09:46] And now we can see it in action! Let's go ahead and, you know, go back to our page here. We're going to specify two colors here and we're going to navigate to another page, and we get a new ratio! And we can see that this works, you know, one more time, too!

[00:10:01] So this, at this point, now has all the full navigation we need. It's really starting to come together as an application, right? Now people don't even have to memorize the format of the URL. But I think we could go a little further. We can use Serverless to add some of the little things, the nice little details. Specifically, adding some nicer styles! Like, for instance, we can use our query parameters in our CSS here and create a neat little background that uses both of the supplied colors. Or we could go into our form and we could add some default values so that way, as you navigate between pages, it persists the value of your query parameters so it doesn't feel like it's resetting every time.

[00:10:43] And so here's what that's gonna look like for us. We now have a fully functioning experience where you can go to different pages. You can get the ratio. It persists your changes. This really feels like an app to me. It's a small app, a minimalistic app, but it is a fully functioning app that delivers zero client-side JavaScript, and yet it manages to have a cohesive feel and provide all the information about the ratio you need.

[00:11:10] So this is a quick little demo. I know it's been incredibly whirlwind and I do apologize for that. However, again, you can go to for all the resources. And then if you want to specifically play around with this, which I highly recommend — it's a lot of fun — go to You'll be able to play with that very experience yourself. It's totally deployed. I'd love to know what you think. I'd love to be able to add more, like maybe guidance about what WCAG thresholds you're meeting and so forth.

[00:11:39] But yeah! That is, in just a few minutes' time, how you could create a fully functioning color contrast application with Eleventy Serverless and zero client-side JavaScript. Thank you all so much for your time!