If you’ve ever read the WeAreDevelopers Magazine before, you’ll know we love showing developers how to take advantage of native platform features and avouid over-engineering UI’s with frameworks or pre-built components.
So, when we came across Théodore Lefèvre’s CSS-related educational content on socials, we knew our audience would want to know more.
After appearing on our weekly LIVE show and sharing some fantastic tips and tricks, we thought we’d take a closer look at some of the features highlighted by Theo, so you can start using them today.
Modals without JavaScript with <dialog>
The next time you find a modal in the wild, take a look at the source code and the chances are you’ll see a complicated mess of <div>'s with all kinds of hacky approaches used to handle focus trapping, keyboard navigation, backdrop handling, scroll-locking, and, of course, toggling the modal to open and close.
The native <dialog> element solves those problems automatically.
<button id="open-dialog">Open Dialog</button>
<dialog id="my-dialog">
<h1>Hello I'm a Dialog</h1>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
Opening it requires exactly one line, making use of the showModal() and close() methods:
dialog.showModal();
Once open, the browser blocks interaction with the page behind it, traps focus, and adds a backdrop, which you can style with the ::backdrop pseudo-class:
dialog::backdrop {
background-color: #5c5c5c94;
}
Dialogs also expose their state via the open attribute, which means you can animate them cleanly with CSS:
dialog[open] {
opacity: 1;
}
Of course, you could use JavaScript to add or remove the [open] attribute, but alternatively you could submit a form with method="dialog" to automatically dismiss it.
Finally, to prevent page scrolling while the dialog is open, simply look for an [open] dialog within the body:
body:has(dialog[open]) {
overflow: hidden;
}
It’s that simple! Check out Theo’s video walkthrough below:
Accordions with <details> and <summary>
Accordions are another UI pattern people routinely over‑engineer with JavaScript, handling toggling, open-and-close animations, and ensuring only one is open at any one time.
All of this can be handled with the native <details> and <summary> elements, like so:
<details>
<summary>Accordion title</summary>
<p>Hidden content goes here</p>
</details>
Immediately, it’s keyboard accessible, screen‑reader friendly and still functional with JavaScript disabled. It even opens automatically when the browser finds hidden text via search.
If you want an accordion open by default, just add open:
<details open>
<summary>Accordion title</summary>
</details>
You can also link multiple accordions together so only one stays open at a time by sharing a name attribute:
<details name="group">
<summary>Accordion 1</summary>
</details>
<details name="group">
<summary>Accordion 2</summary>
</details>
Styling and transitions are driven entirely by CSS using the open attribute:
.accordion[open] {
background: #eee;
}
For smooth height animations, the ::details-content pseudo‑element lets you animate without JavaScript:
.accordion::details-content {
block-size: 0;
}
.accordion[open]::details-content {
block-size: auto;
}
Even the default disclosure arrow can be replaced with your own icon using CSS, so as well as working well, it’ll look great, too.
See it in action below:
A Two-way Table of Contents
Finally, we’re going to use scroll-target-group and :target-current to create a two-way table of contents, where users can click a link to scroll to a particular section on the page, with the navigation staying in sync with scroll position so that the section the user is currently viewing will be higlighted in the navigation menu.
Normally, this would mean using IntersectionObserver with offsets to check which section the user is on, and use JavaScript to amend elements when they’re in view.
Now it’s two lines of CSS.
<nav class="toc">
<a href="#build-time">Build time</a>
<a href="#layering">Layering</a>
<a href="#runtime">Runtime</a>
</nav>
First, group the links, and of course we could do this with the nav element, but for specificity we’ll use the .toc class:
.toc {
scroll-target-group: auto;
}
Then style the currently active section:
.toc a:target-current {
font-weight: bold;
}
That’s it.
As you scroll, the browser automatically highlights the correct link. Clicking links still works. Scrolling still works. The connection is fully two‑way.
Support isn’t universal yet, but this is ideal progressive enhancement, and a serious replacement for a lot of JavaScript.
We hope you found this article useful, and be sure to get in touch if you’d like to share some tips and tricks with our audience.
Theodore’s book, You Don’t Need JavaScript is available now so be sure to check it out for lots more amazing tips and tricks, and visit his website at theosoti.com to stay up to date with his latest posts.
