Skip to main content Go to the homepage
State of the Browser

Stop using JS for that:
Moving features to CSS and HTML

If you've been building websites for a while you "know" that some things require JS and that's just the way it is. Turns out, spec writers and browser makers both are working hard to find common JS patterns and implementing them in CSS and HTML. Things like accordions, auto-suggest, smooth scrolling, dialogs are all available without JS. Looking a little ahead, things like parallax scrolling, styleable selects and component-dependent styling are expected to make their CSS-debut as well.

In this talk I walk through a few common patterns, explain how they can be implemented in CSS and HTML, how that's better and what accessibility implications they have, both for features available today and feature available soon™️.


[MUSIC PLAYING] There we go.

Hi, everyone.

Except now I lost my speaker notes here.

But maybe we can do-- there we go.

Now I'm really here.

Hi, everyone.

I'm here to tell you to stop using JavaScript.

Yes, so usually I give this talk to a full stack audience, so I have this little caveat here that says "hooray!

" Of course that's not needed here.

It's "hooray!

" So who am I?

I'm Kilian.

I spent the past 20 years building websites in all manner of technologies.

I am part of the Electron governance team.

If you're unfamiliar with Electron, it's a framework to make desktop applications using web technologies.

So it basically takes Chromium and Node and meshes them together.

It's great.

I built Polypane in it, which is a browser for developers.

And this is what Polypane looks like.

I'm not going to discuss it anymore.

I just want to mention that because working on Polypane means that I write bucket loads of JavaScript all day, every day, like obscene amounts of JavaScript.

So you could actually say I love JavaScript.

But I also really like CSS, and I even like HTML a lot.

Usually here I switch this out because it's content editable, but you know everyone here I'm not going to do that.

So the reason I love all three of these technologies is something called the rule of least power.

It's one of the core principles of web development and it means that you should choose the least powerful language for a given purpose.

So on the web this means preferring HTML over CSS and CSS over JavaScript.

Your JavaScript can break, it can fail to load, it takes extra resources to download and run, and it can exclude keyboard users and people using assistive technologies if you don't do it right.

The reason is that JavaScript is a programming language whereas CSS and HTML are declarative.

With CSS and HTML you tell the browser what to do instead of how to do it.

The browser will figure out for you how to do it.

Of course your CSS can also fail to load, but all of you write amazing HTML so your site will still be perfectly accessible.

HTML really is the foundation.

Do you have any.

maybe it's.

So the good news is that router makers are listening.

They and specification writers are rapidly implementing stuff in CSS and HTML that up until a couple of years needed JavaScript.

So that's what we'll be discussing today, but the larger point I want you to take away from this talk is that it's very tricky to learn something because once you've learned something you don't have to learn it again, you already know it.

So if in 2005 you learned how to implement a carousel using JavaScript, then that knowledge stays with you forever, becomes part of your toolbox and every time you need to implement a carousel, you can just keep using the same thing, because this is the web, and everything that you build for the web is going to work forever.

Caveats apply, but in general, all the crappy code you wrote a decade ago still runs in all the browsers.

So there's never really a reason to learn something again, because the old stuff still works for a given definition of "works".

So the techniques that I'll be teaching today and showing today are cool, but what I want you to take away from this is that just because you know something requires JavaScript, that knowledge may no longer be true.

Just because you know something needs a specific browser hack, that may not be true anymore.

You can make better websites if you check those assumptions every now and then.

So let's get started.

It's going to be a bit of a grab bag of features, new and old, some of them you might know, some of them you might not know.

There's going to be a bunch of live demos on a device that's not my own, so expect stuff to break and if you can please join me in laughter.

So let's get started with something small, custom toggles.

So when implementing custom toggles we often reach for a JavaScript solution that handles the click events and keeps track of all the states of your toggle.

But of course we can do the same with an ordinary checkbox and the checked pseudo class.

That means you can have fully interactive toggles.

Is this getting worse?



We'll see how it goes.

So we can have fully interactive tools using CSS and native browser APIs and that means that accessibility is done for you.

Here on my next slide, oh there we go, you can see the HTML that we're going to use.

a label, inside the label is an input, it's of the type checkbox and I describe what it is, it totals my awesome feature.

Now this, using this instead of some JavaScript solution, is already giving us benefits, because.


I can click anywhere inside the label and the browser has already realized that this This entire label is linked to this input, because I've put the input inside the label.

That means the entire thing is clickable without an onclick handler that I have to manage.

This is something the browsers already give us for free.

Of course I could say we're done here, because it's clearly a toggle.

That's nice if you're your own boss, but most of you will work with a designer.

If you show this to your designer and you say "I'm done", they say no.

So we have to do a little bit more work.

So we're going to add a ton of CSS and now it's a pretty toggle.

What all this CSS does isn't super important except for that line on top which says "appearance none".

Appearance none is a way to tell the browser to please leave this element alone.

You see form elements and images are something called replaced content.

That means that as your browser renders the HTML, they don't actually render something in the place of your phone control.

They leave a little space.

They leave a little space for the image, they leave a little space for the input.

Don't worry, I'll put it in.

It leaves a little space for the browser to replace it with native form controls with a thing that displays your image.

Now this is great because the native form controls are provided by the operating system, they behave the same throughout it, etc.

But they're also very hard to style because they're not "of the web" in a sense.

With Appearance None you can tell the browser "that's great, but please don't touch my inputs, please just let me handle everything.

And that gives us additional powers, because replaced content comes with a bunch of restrictions.

One of them is that you can't really style everything about them, but also things like pseudo elements don't work in replaced content.

While they work, you can put a before and an after element on replaced content, but But because before and after elements are part of the elements and the entire element gets replaced, so does your after and before elements.

They're gone.

Now with appearance none, they come back.

That means that we have extra attributes or extra bits that we can style.

So what I've done here is that the input is the background and the before element is the a little nib in the toggle.

Because we've used the same HTML, all of this is still perfectly interactive.

So I can now click my thing, and as you can see it's now checked.

I mean, trust me, it's checked.

We need to do a little bit more work, and this is where the checked pseudo-element comes in.

To make that change visible, we can use the checked pseudo element, and this will match whenever the checkbox is checked.

So now as I click anywhere in the label, that checked pseudo element starts to match, and my background changes before the element gets transformed.

So there's one last thing to do, because we have a very pretty custom toggle now that makes our designer very happy.

We still have all the accessibility, but we need to do a little bit more.

We need to add an outline to the toggle when it's selected by the keyboard.

But we prefer not to do that when we click it.

You see the focus outline, and I'm sure there's people that are going to disagree with me on that is there to help people know where they click or where the focus is.

But if you already have a mouse and you click the thing, then you know the thing you clicked.

So for that reason a lot of people hide the outline.

Please don't do that.

Please keep the outline but only when people interact with it with your keyboard, with their keyboard.

Just a moment.

There we go.

As I click it with my mouse, it's clear that I clicked it, but now if I switch to the spacebar, I get this really nice focus outline.

If you've been designing for more than, or building websites for more than five years, you probably know that the outline is this really ugly dotted black box around your elements that you can't style, you can't do anything about.

You can, because outline now follows the border radius.

looks great by default.

You can add an outline offset that can go outside of the elements but also into the elements so you can have a much nicer outline that even your designer will be happy with.

Now one last last thing is that even though we're all used to writing outline none, I want you to start writing outline color transparent instead.

The result will be the same, because with outline none the outline isn't there because it's not there, and with outline color the outline isn't there because it's invisible.

But the difference is that when a user uses high contrast mode in Windows or forced colors, That outline is then visible again for people that want higher contrast.

I don't have time to go into forced colors and what that actually is, so if you want to learn more about that I wrote a very long blog post about it on polypane.


With this we've done everything you want to do for a custom toggle without touching any JavaScript.

While we're working with forms, it's time for a quick one.


Datalist is the browser's built-in way to show a list of match suggestions as a user types into an input.

It's an autosuggest, but it's done by the browser instead of by a 400-line JavaScript library.

It looks like this, there's an input and you provide it with a list attribute and that list attribute refers to the id of a data list element.

You can put the data list element anywhere on the page, it's not rendered, it's display none so you don't have to worry about that.

Now as I start typing in the input it suggests to me all the items from the data list and And it automatically filters it down.

I can even get an overview of all of them.

And very important, I still have the option to create my own CSS framework.

So zero JavaScript, quite a lot of functionality.

If you haven't used this yet, try it out.

In the same vein, instead of shipping a huge JavaScript color picker with a nice canvas and a full UI, etc.

, you can let the browser handle it with an input type color.

The nice thing is that this actually gives your user more.

This shows the very nice canvas without you having to ship anything.

But because it's the browser, it can actually pick colors from anywhere on the screen.

Good luck doing that in JavaScript.

Right now the entire screen is the browser, but this specific API also lets you pick colors from outside of the browser.

Lets you pick colors from the entire display.

So that's pretty neat.

And you get it with literally a single line of HTML.

It's very hard to beat that.

One other thing I want to call out here is that this is an unstyled color input and we're used to those being ugly white boxes.

Whereas this is an ugly dark grey box.

That's because I've used a color scheme dark to tell the browser that actually this design is in dark mode.

So if you have replaced content, please provide me with dark mode input elements.

And the browser then does that for us.

Now if you have a website that has both a dark and a light mode, you can instead say color scheme dark space light and the browser will dynamically switch between their dark mode and light mode input elements for you.

So again, saves you a bunch of code.

small examples, let's go back to something a little bit more meatier.

Complex in-page transitions.

So when moving from one section of a page to another, browsers jump by default.

They instantly go to the other part of the page.

This can be jarring and disorienting because you no longer know exactly where you are on the page, how far down are you, what's above it, what's below it.

It's much nicer to scroll the user, like gently take him to the next part of the page so that they have a sense of where they are on the page.

In the past we used jQuery for this, with just seven lines of gorgeous JavaScript and of course hundreds and hundreds of lines of jQuery in the background doing the actual work.

I really like this, I spent many years writing this, but you can forget it because even these seven lines, which aren't a lot, can be replaced by a single line of CSS, scroll behavior smooth.

This tells the browser to always scroll smoothly when navigating to a fragment identifier, which is any ID on your page essentially.

And now every internal link is magically upgraded.

The nice thing here is that whereas with jQuery, jQuery needed to decide how fast it should go, it needed to wait on the browser to make the animations happen, etc.

Here because CSS is declarative, the browser will figure out everything for you.

It figures out how long the animation should take, it makes sure that there is a decent It can skip parts if it thinks that's good for the length of the animation.

And that means that if you use scroll behavior smooth, it's always snappy.

It doesn't hang because there's some random other JavaScript process going on.

Now don't worry, you can still use this in JavaScript as well.

if the scroll APIs have behavior as a potential option.

So, you know, scrollTo, scrollIntoView, etc.

And that still saves you a bunch of JavaScript.

Now there's one important consideration here and that's accessibility.

Well for most people it's the jumping from one part of the page to another part that can be generating.

People that have vestibular disorders, for them it's the scrolling that poses the problem, because it can make them nauseous and unwell and there's a lot of motion that they prefer not to see.

Now browsers have a way of catering to these users with prefers-reduced motion.

The way to implement this is to see smooth scrolling as an added behavior.

So by default we choose the accessible version which is no smooth scrolling.

And only when the user has said that they don't mind motion with prefers-reduce-motion no preference do we add the scroll behaviour.

So smooth.

We have native smooth scrolling for the people that want that.

We have no smooth scrolling for those that don't.

And we're not shipping any CSS.

Now that's a great start, but we can add a little bit more, because what if we want to scroll to an element but keep a little headroom?

For example to make sure that our lovely fixed header doesn't overlap the title we just scrolled to?

Well CSS has a solution for that too, called scroll margin.

It works the same as regular margin, but it's only applied when scrolling, and there's also scroll padding which works the same.

So now, if I go to my target, you can see that there's still space left above my target so that it doesn't fall under the header.

Now if I scroll back to top, I don't have a scroll margin there.

And that means that while it's technically scrolled to the top, the top isn't actually readable because it's behind the header and I still have to scroll back.

Now this definitely beats manually subtracting the offset in JavaScript before its animation, right?



So as a finishing touch here, what if we want to highlight our target in some way to give it extra prominence?

Of course we could here use JavaScript to add a class to the target that then adds a transition but there's a CSS solution for that as well called the target pseudo class.

The target pseudo class matches whenever an element is the target which is the result of clicking a link to its fragment identifier.

So if I click to target here now we get this lovely outline and we know that we definitely need to look at my middle title.

So that means we've built a way to smoothly scroll to a specific section, highlight it and also keeping space for the rest of the UI.

We're doing all of that without any JavaScript.

Now in the previous couple of demos you saw a header that was fixed to the top of the screen with position fixed.

But position fixed is quite difficult to work with because by its very nature it's taken out of your regular document flow and you really only have the viewport to position it against, which isn't always what you want.

Now in CSS you can now use position sticky, which behaves as a regular element until it hits certain borders or edges like top, bottom, left and right, and then it becomes fixed like it becomes sticky.

So as I scroll these, you can see that this element, it scrolls along with the parent element until it reaches that top 50 percent, and then it's stuck.

This is the exact same CSS, but because the parent element started later, this one still is happily scrolling until it also gets stuck.

Now they're stuck together.

But as the parent element scrolls further along, you can see that it takes the child element along with it.

And the browser takes care of all of the logic here.

So definitely something you'll want to try instead of doing some random in-view JavaScript library.

Now, what about this other staple of JavaScript-scrolling features?

I'm talking about image carousels or sliders.

For that we have the scroll snap APIs.

So with scroll snap we can create sliders that snap to their parent elements in different ways while still accepting native scrolling.

So I have a scroll snapping slider here and as I scroll you can see that it just follows the mouse like any other scroller.

It's only when I release the mouse that you can see that it snaps to whatever is closest.

This is the way it works.

On the parent you set a scrollSnap type that takes two arguments, a direction which is X and Y.

It's not blocking inline because that would be consistent, it's X and Y.

Then you have to tell it how it should should snap, which is either mandatory or proximity.

So mandatory always snaps and proximity only when it's close enough to an edge.

Which edge?

Well, that's where a scroll snap align on the child element comes in.

That's where you tell it which side to snap on.

There's start and there's end for in English left and right or center.

And that start and end will switch if you use languages or pages in languages that use different directions.

Now by changing the scroll-snapper line to center, we get center aligning.

And I want you all to take a moment and think about how you would write the JavaScript to to centerline a randomly sized div in a scroller like this.

Because notice that they're all slightly different sizes, and the browser just takes care of it and we don't have to do any math at all.



[laughter] Now, there's a lot of stuff you can do with scroll snapping.

A whole range of very cool tricks.

If you want to learn more, check out this presentation by Adam.

It really goes into the nitty-gritty of all the things you can do with scroll snapping.

On to accordions and modals.

Two other elements that we very often solve with JavaScript that we can also just use HTML for, an accordion and a modal.

Having an accordion on your page can really help you keep your content organized, by showing the titles of sections and only expanding the full section when a user clicks on them.

The HTML for this is details and summary, and implements exactly this.

content inside a details element is hidden, except for the summary element, until you click on the summary element and then the rest of the content is made visible.

Now that's nice, but of course your designer designed your accordion such that the first one is open by default and you're out of luck here because it's closed by default.

worry, you can just add an open attribute to the details and now it's open.

And if you write any sort of React or JavaScript framework, you're going to look at this and think, okay, well now it's open forever because that's just how it works.

Not in the browser.

Because open is just the starting state, it's a dynamic attribute.

So I can still click it to close The browser gives us a ton of stuff for free.

Back to the designer.

He or she is going to hate that triangle.

Not for any particular reason, just they didn't design it.

So it's clearly the wrong triangle.

Don't worry.

CSS for that too, with the marker pseudo element.

The marker pseudo element only takes a subset of CSS so you can't go wild with it, but you can still style the sizing and the content.

For example we can have these two emoji and then of course use that open attribute to show a different emoji when the dialogue or the details is actually open, without having to do any sort of listeners.

Now a gotcha here is that though a summary is clickable, like a link or a button, it doesn't have any indication that it's clickable, like a link or a button.

It doesn't get an underline, it doesn't have big chunky borders.

So I think we need to help users along here.

I don't want to get into the "only links should have pointer cursor" discussion, but you should at least do something to make sure that people know that this thing is something you can click on and then stuff happens.

Okay, on to dialogue.

For this one I'm going to cheat a little bit since it does need some JavaScript.

So the dialog element in HTML is like a better confirm or a better alert.

It implements a modal dialog for you, but what it doesn't do is lock the main thread.

So your page can still do stuff while you show a dialog.

It automatically helps keep focus inside the dialog, it prevents Z-index issues and it also handles closing the element for you.

So this is what the dialogue looks like, because by default it's hidden.

To create it, you create a dialogue element, then inside it you put a form with a method of dialogue.

This is like a new thing, because before we only really had methods post.

And I'll explain why we want this form in a bit.

And now to show it, we need to call a function on this dialogue, which is either show for non-modal dialogue or show modal for a modal dialogue.

The difference being that a non-modal dialogue opens in place and a modal dialogue opens across the entire page.

So that works like this.

You click the button, the modal shows.

But there's no close button here.

So how do we get out of it?

I also can't click next to it because there's no listeners here.

That's where the form comes in.

Whenever you click a button in the form that submits that form, the browser will see that as a close event.

That's how you can implement a confirmation dialog.

If you have any other form data in there, you can add a listener to the dialogue and listen to the close event to get the actual value of whichever button you clicked.

So now we have two buttons and one is correct and the other isn't.

Then on the closed event listener you can listen to dialogue.


If it happens again during a demo I'm just going to say trust me bro.

Lastly you can also style.

Trust me bro.

You can also style the backdrop which is the layer between the dialogue and the rest of the page.

Container queries are also a thing that you no longer need JavaScript for, and I would be remiss not to mention them, but they would fill up all the time.

I don't actually have left anymore, so I'm just going to say there are a bunch of really excellent resources to learn about them.

They haven't been around for very long, but they're in every browser now, unlike the next few bits which aren't yet in browsers but will be soon hopefully fingers crossed.

One of them is a masonry layout.

So instead of using Packery or masonry or some other huge JavaScript library to get the Pinterest layout, you can also use the great template rose masonry CSS to get that.

It's available in Firefox behind the feature flag, it's in Safari's technology preview, I hope Chrome is shipping it soon as well, and you can find more information on the link on the screen.

[laughter] [applause] (audience laughing) (audience applauding) (audience laughing) I can wiggle it, but-- - We'll share the slides later.

  • Yeah.

  • Yeah.

  • I'll just plug it out and plug it back in again.

That always works.

That always works.

Guess I'll just leave it at that.

So I had another bit about the select list element, which is extremely exciting because It lets you do a completely styleable select without screwing everyone that doesn't use a mouse.

And it's coming and it's great and it works wonderful.

The has pseudo function, CSS pseudo function, that lets you select any other element on the page and query that to determine the style of a random element.

It's already in Chrome and in Safari.

Just this week Firefox Nightly enabled it as well, so that's coming very very soon, ready for use.

Then I had this really cool demo by Brahmas about scroll-driven animations, which I was just going to show, as in look at this amazing thing that doesn't need any JavaScript.

And that's it.

Thanks everyone.

I hope you enjoyed this.

[applause] I hope you enjoyed this.

[applause] [APPLAUSE] [ Applause ]

About Kilian Valkhof

Kilian Valkhof

Kilian is a front-end developer with over 20 years of experience that switched from building websites to building apps to build websites with. He is interested in modern web development, desktop app development and new technologies, and regularly speaks about topics like responsive websites, design systems and Electron. Kilian is a frequent open source contributor.