I spend a lot of time writing about CSS features that replace small pieces of JavaScript. The examples are usually easy to understand in isolation. Finding the same opportunity inside a real website is much harder.
A dropdown may be buried in a minified bundle. A modal may be a fixed <div> with a home-made focus trap. A reveal animation may only exist after you scroll halfway down the page.
I wanted to paste in a URL and get a useful answer to one question:
Which parts of this page could use native HTML or modern CSS instead?
So I built CSS Radar.
A class name is not evidence
My first concern was false positives.
Finding .modal in a stylesheet proves very little. It might be an image, a legacy rule that no longer matches anything, or a non-modal panel with an unfortunate name. The same problem applies to classes like .dropdown, .accordion, and .reveal.
The scanner renders one public page in Chromium. It scrolls through the whole page, hovers controls, and clicks interactions that it can test safely. Form submissions, navigation, and downloads are blocked.
It also collects the relevant HTML, CSS, and JavaScript around each candidate. A finding is only published when the captured evidence matches a replacement the scanner knows how to verify.
That makes it conservative. I prefer a missed opportunity to a report full of guesses.
A custom modal can become <dialog>
Here is a familiar modal setup:
<div class="modal" role="dialog" aria-modal="true" hidden>
<button class="modal__close">Close</button>
<p>Modal content</p>
</div>
Enter fullscreen mode Exit fullscreen mode
trigger.addEventListener('click', () => {
modal.hidden = false;
trapFocus(modal);
});
Enter fullscreen mode Exit fullscreen mode
The code is not necessarily bad. It just rebuilds behavior the browser now provides.
<button command="show-modal" commandfor="settings-dialog">
Open settings
</button>
<dialog id="settings-dialog">
<p>Modal content</p>
<button command="close" commandfor="settings-dialog">
Close
</button>
</dialog>
Enter fullscreen mode Exit fullscreen mode
<dialog> gives you modal semantics, the top layer, focus handling, Escape dismissal, and ::backdrop. The newer command and commandfor attributes can even connect the buttons without a click listener in supporting browsers.
This is the kind of finding CSS Radar can verify from several pieces of evidence: aria-modal="true", the fixed overlay, its hidden state, and the code that opens it.
A class-toggled menu can become a popover
This pattern is everywhere:
<div class="language-selector">
<div class="selector-button">Language</div>
<div class="language-menu">
<a href="/en">English</a>
<a href="/fr">French</a>
</div>
</div>
Enter fullscreen mode Exit fullscreen mode
selector.addEventListener('click', () => {
selector.classList.toggle('open');
});
Enter fullscreen mode Exit fullscreen mode
.language-menu {
display: none;
position: absolute;
}
.language-selector.open .language-menu {
display: block;
}
Enter fullscreen mode Exit fullscreen mode
A modern version can use a real button and the Popover API:
<button popovertarget="language-menu">Language</button>
<nav id="language-menu" popover>
<a href="/en">English</a>
<a href="/fr">French</a>
</nav>
Enter fullscreen mode Exit fullscreen mode
Popover handles opening, Escape, light dismissal, and the top layer. CSS Anchor Positioning can attach the menu to its button when you need more control over placement.
The awkward part for a scanner is that real sites do not always use buttons. Sometimes the trigger is a <div> with a click listener. Clicking every <div> on a page would be reckless, so CSS Radar only tests a non-semantic trigger when the surrounding structure or behavior gives it a concrete reason.
That rule came from debugging a missed language selector. The scanner saw the open class and initially treated it like an accordion. Watching the menu become an out-of-flow panel made the correct replacement much clearer.
Some scroll reveals no longer need an observer
Another common pattern uses IntersectionObserver to add a class when an element enters the viewport:
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
}
}
});
document.querySelectorAll('.reveal').forEach((element) => {
observer.observe(element);
});
Enter fullscreen mode Exit fullscreen mode
For a decorative entry animation, a view timeline can often do the job in CSS:
.reveal {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes reveal {
from {
opacity: 0;
translate: 0 2rem;
}
}
@media (prefers-reduced-motion: reduce) {
.reveal {
animation: none;
}
}
Enter fullscreen mode Exit fullscreen mode
This recommendation only makes sense when the observer controls presentation. If the callback loads data, changes application state, or records a business event, CSS is not a replacement.
That difference is why the scanner looks for both the viewport observation and a matching visual mutation on the same element.
What the report contains
The report does not rewrite the website. I do not think an automated tool should silently replace interaction code after spotting one pattern.
Instead, each finding shows:
- the element or selector that matched
- the captured code that drives it
- a possible native replacement
- current browser support
- what to verify before shipping the change
When I have a matching example from my book, I also link to the CodePen. That gives you something working to inspect before touching production code.
The scanner also catches smaller CSS opportunities such as paired physical spacing declarations that can become logical properties. Repeated occurrences are grouped into one finding because six cards that all say padding-top plus padding-bottom should become padding-block are not six different ideas.
What it cannot tell you
CSS Radar scans one public page. It cannot enter an account or reach a component hidden behind an application-specific workflow. A component may also appear on another route and never exist on the page you submit.
It is not a security, performance, or accessibility audit. Native HTML often removes custom code and gives the browser more responsibility, but that does not make every replacement automatically correct.
You still need to test keyboard behavior, older browsers, analytics hooks, and any state tied to the current implementation. The report is a review list, not a merge button.
If you have an old page with a suspicious amount of UI glue, you can run a free audit on CSS Radar. I am especially interested in the patterns it misses. Those failures have been more useful than the easy successes.
답글 남기기