Journal
insights

10 Modern CSS Features That Can Replace JavaScript

For years, JavaScript was the go-to solution for adding even the simplest interactions to a website. Whether it was centering elements, creating accordions, detecting parent elements, or implementing smooth scrolling, developers often reached for JavaScript by default.

Modern CSS has changed that dramatically!

With the introduction of powerful new features, CSS is no longer limited to styling alone. It can now handle many interactions, layouts, and UI behaviors that once required additional JavaScript. The result is cleaner code, better performance, easier maintenance, and a more accessible user experience.

Why Modern CSS Is More Powerful Than Ever

For many years, JavaScript was considered the default solution for adding interactivity to a website. Developers relied on it for everything from smooth scrolling and sticky elements to responsive layouts, accordions, sliders, and even selecting parent elements. While JavaScript remains an essential technology for building complex web applications, modern CSS has evolved into a far more capable language than it was a decade ago.

Today’s CSS includes features that solve many of these common problems natively, allowing developers to write cleaner, more maintainable code with fewer dependencies. By reducing the amount of JavaScript needed for everyday UI interactions, websites become faster to load, easier to debug, and often more accessible to users. The best developers today don’t replace JavaScript entirely—they simply recognize when CSS is the better tool for the job. Understanding these modern CSS features not only improves performance but also helps you build interfaces that are simpler, more reliable, and easier to scale over time.

:has() : Style Parent Elements Without JavaScript

One of the biggest limitations of CSS for years was its inability to select a parent element based on its children. Developers frequently wrote JavaScript just to add or remove classes from parent containers whenever an input became checked, a form gained focus, or a specific child element appeared. The introduction of the :has() pseudo-class changed that completely. If you haven’t explored it yet, you’re about to discover one of the most powerful additions to modern CSS.

What is :has()?

The :has() pseudo-class is often described as CSS’s long-awaited “parent selector.” Instead of styling only child elements, it allows you to style an element based on what’s happening inside it. This opens the door to many interactions that previously required JavaScript, such as highlighting a form when one of its inputs is focused, changing the appearance of cards containing specific elements, or styling navigation items based on their active links.

For example, imagine you want to highlight an entire form whenever any input inside it receives focus. In the past, this would usually require JavaScript. Today, CSS handles it effortlessly.

form:has(input:focus) {
    border-color: #4f46e5;
    box-shadow: 0 0 0 4px rgba(79, 70, 229, .15);
}

A Practical Example

One of the most practical use cases for :has() is creating interactive cards. Suppose each pricing card contains a button, and you want the entire card to stand out whenever the user hovers over that button. Without :has(), you’d likely reach for JavaScript or restructure your HTML. With modern CSS, the solution is both cleaner and easier to maintain.

.pricing-card:has(.btn:hover) {
    transform: translateY(-8px);
    border-color: #4f46e5;
    box-shadow: 0 20px 40px rgba(0,0,0,.12);
}

Another common example is styling labels when their corresponding checkbox is checked.

label:has(input:checked) {
    background: #eef2ff;
    border-color: #4f46e5;
    color: #4f46e5;
}

These patterns eliminate unnecessary JavaScript while making your styles more expressive and closely tied to your HTML structure.

Browser Support

When :has() was first introduced, browser support was one of the biggest concerns. Fortunately, that’s no longer the case. Today, all major modern browsers—including Chrome, Edge, Safari, and Firefox—support the :has() pseudo-class, making it a practical choice for production websites targeting modern audiences.

If your project still needs to support significantly outdated browsers, it’s worth testing your use cases and providing graceful fallbacks where necessary. However, for the vast majority of modern websites, :has() is now stable enough to replace many JavaScript-based solutions while keeping your codebase cleaner and easier to maintain.

scroll-behavior: smooth : Smooth Scrolling Without JavaScript

Smooth scrolling has become a standard expectation in modern web design. Whether you’re navigating a landing page, jumping between documentation sections, or clicking items in a table of contents, abrupt scrolling feels outdated. For years, developers depended on JavaScript libraries or custom scripts to create this effect. Today, modern CSS can accomplish the same behavior with a single property, making implementation dramatically simpler while improving maintainability.

The Old JavaScript Approach

Before browsers supported native smooth scrolling, developers typically intercepted anchor link clicks with JavaScript and manually animated the page position. Although this approach worked, it introduced additional code, event listeners, and potential compatibility issues that had to be maintained over time.

A simplified example looked like this:

document.querySelectorAll('a[href^="#"]').forEach(link => {
    link.addEventListener("click", function (e) {
        e.preventDefault();

        document.querySelector(this.getAttribute("href"))
            .scrollIntoView({
                behavior: "smooth"
            });
    });
});

While this isn’t particularly complicated, it’s still JavaScript that many websites simply don’t need anymore.

The CSS Solution

Modern browsers support smooth scrolling natively through the scroll-behavior property. Instead of writing custom scripts, you only need a single CSS declaration to enable smooth navigation across your entire website.

html {
    scroll-behavior: smooth;
}

That’s it!

Every internal anchor link will now scroll smoothly without additional JavaScript, making your codebase smaller, cleaner, and easier to maintain. It’s a perfect example of how modern CSS can replace functionality that once required scripting.

When to Use It

scroll-behavior: smooth is ideal for websites where users frequently navigate between sections on the same page. Landing pages, portfolios, product pages, documentation websites, FAQs, and long-form blog posts all benefit from smoother transitions because they help users maintain context as they move through content.

That said, smooth scrolling should enhance the experience rather than become a distraction. If your website relies on highly customized scrolling effects, complex animation timelines, or advanced scroll-triggered interactions, JavaScript libraries may still be the better choice. For simple in-page navigation, however, native CSS is faster, lighter, and easier to implement.

position: sticky : Sticky Headers Without Extra Code

Keeping important interface elements visible while users scroll has become a standard practice in modern web design. Navigation bars remain accessible, sidebars continue displaying helpful information, and tables of contents follow readers through long articles. In the past, achieving these behaviors often meant writing JavaScript to detect scroll positions and dynamically update element styles. Today, CSS offers a much simpler solution through position: sticky. With just a few lines of code, you can create interfaces that feel polished, improve usability, and require virtually no maintenance. Before reaching for JavaScript, it’s worth understanding how powerful this single CSS property can be.

Sticky Navigation

One of the most common uses of position: sticky is creating navigation bars that remain visible as users scroll down a page. This improves usability by keeping important links and actions within easy reach, especially on long landing pages or documentation websites.

Implementing a sticky header is surprisingly simple:

header {
    position: sticky;
    top: 0;
    z-index: 1000;
    background: white;
}

Unlike position: fixed, a sticky element behaves like a normal element until it reaches the specified offset. Once that point is reached, it stays fixed within its parent container. This creates a much more natural scrolling experience while requiring almost no additional code.

Sticky Sidebars

Sticky sidebars are another excellent use case, particularly for blog posts, documentation, pricing pages, or dashboards. They allow users to keep important content—such as a table of contents, call-to-action, or navigation menu—visible while reading long pages.

Here’s a simple example:

.sidebar {
    position: sticky;
    top: 2rem;
}

This tells the browser to keep the sidebar fixed once it’s 2rem away from the top of the viewport. The result feels modern, improves navigation, and eliminates the need for scroll event listeners or JavaScript calculations.

Common Mistakes

Although position: sticky is easy to use, developers often assume it’s broken when it doesn’t work as expected. In reality, the issue is usually caused by the surrounding layout rather than the property itself.

For example, the sticky element must have a positioning offset like top, left, right, or bottom. Without one, the browser has no point at which to “stick” the element.

.sidebar {
    position: sticky;
    top: 20px;
}

Another common issue occurs when a parent container uses overflow: hidden, overflow: auto, or overflow: scroll. In many cases, this prevents the sticky behavior from working correctly because the sticky element becomes constrained by that container instead of the viewport. Checking parent elements is often the first step when debugging sticky layouts.

<details> & <summary> : Build Accordions Without JavaScript

Accordions are one of the most common UI components on the web. They’re used in FAQs, documentation, product descriptions, settings panels, and countless other interfaces. For years, developers built them using JavaScript or relied on third-party libraries, even though browsers now provide a native solution. The <details> and <summary> elements allow you to create fully functional, accessible accordions with almost no effort. Combined with CSS, they offer a lightweight alternative that’s easier to maintain and performs exceptionally well.

Why They’re Better Than Custom Accordions

The biggest advantage of native accordions is simplicity. Instead of writing JavaScript to toggle classes, manage state, and handle keyboard interactions, browsers take care of everything automatically. Users can expand and collapse content without any scripting at all.

A basic accordion looks like this:

<details>
    <summary>What is Modern CSS?</summary>

    <p>
        Modern CSS includes powerful features that reduce the need for JavaScript in many common UI scenarios.
    </p>
</details>

Beyond requiring less code, native accordions are also more accessible by default. Keyboard navigation, semantic structure, and assistive technology support are already built into the browser, saving developers both time and effort.

Styling Native Accordions

Although the default appearance is functional, it doesn’t mean you’re stuck with the browser’s styling. Using CSS, you can transform native accordions into components that perfectly match your design system.

details {
    border: 1px solid #ddd;
    border-radius: 12px;
    padding: 1rem;
    margin-bottom: 1rem;
}

summary {
    cursor: pointer;
    font-weight: 600;
}

You can also target the open state to create richer interactions.

details[open] {
    background: #f8f9ff;
    border-color: #4f46e5;
}

This approach combines the convenience of native HTML with the flexibility of modern CSS, giving you professional-looking accordions without introducing unnecessary JavaScript.

Browser Compatibility

Browser support for <details> and <summary> is now excellent across all modern browsers, making them suitable for most production websites. Unless your project specifically targets legacy browsers, these elements can generally be used without concern.

Because they are part of the HTML specification rather than a JavaScript library, they also reduce dependency on external code. Fewer scripts mean smaller bundles, faster loading times, and fewer opportunities for bugs—an advantage that’s particularly valuable on performance-focused websites.

aspect-ratio : Responsive Elements Without Calculations

Maintaining consistent proportions has traditionally been one of the more frustrating parts of responsive web design. Developers often relied on complicated padding hacks, JavaScript calculations, or fixed dimensions to preserve the shape of images, videos, and cards. Modern CSS solves this problem elegantly with the aspect-ratio property. Instead of calculating heights manually or writing extra markup, you simply define the desired ratio and let the browser handle the rest. It’s one of those small additions to CSS that dramatically simplifies everyday development while producing cleaner, more predictable layouts.

Images

Images frequently have different dimensions, which can create inconsistent layouts if their proportions aren’t controlled. By defining an aspect ratio, you ensure that every image occupies the same amount of visual space, even before it’s fully loaded.

img {
    aspect-ratio: 16 / 9;
    width: 100%;
    object-fit: cover;
}

This technique creates cleaner galleries, prevents layout shifts, and delivers a more polished user experience across different screen sizes.

Videos

Responsive videos used to require wrapper elements, percentage-based padding hacks, and carefully calculated positioning. With aspect-ratio, embedding videos becomes significantly simpler.

iframe {
    width: 100%;
    aspect-ratio: 16 / 9;
}

The browser automatically maintains the correct proportions while allowing the video to scale naturally with the layout. The result is cleaner code that’s much easier to understand and maintain.

Cards & Components

One of the most practical uses of aspect-ratio is maintaining consistency across reusable UI components. Product cards, portfolio items, blog thumbnails, and gallery previews all benefit from having predictable dimensions regardless of the content they contain.

.card-image {
    aspect-ratio: 4 / 3;
    overflow: hidden;
}

.card-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

Using aspect-ratio within a component-based design system helps ensure visual consistency across your entire website. Every card aligns perfectly, layouts remain balanced, and developers no longer need to rely on fragile workarounds or JavaScript calculations to achieve responsive designs.

scroll-snap : Touch-Friendly Sliders Without Libraries

Modern websites frequently include horizontally scrollable sections, from product galleries and testimonial sliders to image carousels and portfolio showcases. Traditionally, developers relied on JavaScript libraries like Slick Slider or Swiper to create these experiences. While those libraries are still useful for advanced functionality, many simple sliders can now be built using nothing more than CSS. The scroll-snap property allows the browser to automatically “snap” scrolling to predefined positions, creating a smooth and intuitive experience—especially on touch devices. If you’re building lightweight, performance-focused websites, this feature deserves a place in your toolkit.

Horizontal Galleries

Horizontal image galleries are one of the easiest places to use scroll-snap. Instead of manually aligning images with JavaScript, you can let the browser handle the scrolling behavior naturally.

.gallery {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    gap: 1rem;
}

.gallery img {
    scroll-snap-align: start;
    flex: 0 0 300px;
}

As users swipe across the gallery, each image automatically snaps into position, making navigation feel polished and responsive without any additional scripting.

Product Carousels

E-commerce websites often display featured products in horizontal carousels. While advanced stores may still require JavaScript for looping or autoplay, many product sliders only need smooth horizontal scrolling.

.products {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x proximity;
}

.product-card {
    flex: 0 0 320px;
    scroll-snap-align: center;
}

This approach reduces dependencies, improves loading performance, and gives users complete control over scrolling while maintaining a premium browsing experience.

Mobile Experiences

Touch devices benefit the most from scroll-snap. Mobile users naturally swipe through content, and snapping creates a more controlled interaction that feels similar to native mobile applications.

Whether you’re building a portfolio, pricing section, testimonial slider, or image gallery, scroll-snap helps create fluid experiences without the overhead of large JavaScript libraries. Sometimes the browser already provides exactly what you need—you simply have to use it.

:focus-within : Interactive Forms Without JavaScript

Creating responsive, interactive interfaces often means reacting when users focus on an input field, navigate a menu, or interact with search components. In the past, these interactions frequently relied on JavaScript to add or remove CSS classes. The :focus-within pseudo-class removes that requirement by allowing parent elements to respond whenever one of their descendants receives focus. The result is cleaner code, better accessibility, and interactions that feel more polished without writing a single line of JavaScript.

Better Form UX

Highlighting an entire form or input group helps users understand exactly where they’re interacting. With :focus-within, the parent container automatically updates whenever one of its child inputs gains focus.

.form-group:focus-within {
    border-color: #4f46e5;
    box-shadow: 0 0 0 4px rgba(79,70,229,.12);
}

This subtle enhancement improves usability while keeping your CSS expressive and easy to maintain.

Navigation Menus

Dropdown menus and navigation components can also benefit from :focus-within. Keyboard users can move through navigation links while the parent menu remains open, improving accessibility without requiring JavaScript.

.nav-item:focus-within .dropdown {
    opacity: 1;
    visibility: visible;
}

This technique creates interfaces that work naturally for both mouse and keyboard users.

Search Components

Search bars often contain multiple interactive elements, including an input field, search button, and icons. Rather than styling each element individually, you can react to focus at the container level.

.search-box:focus-within {
    border-color: #4f46e5;
    transform: scale(1.02);
}

The result is a more cohesive interaction that feels refined while requiring only a few lines of CSS.

accent-color : Customize Form Controls in One Line

Native form controls have traditionally been difficult to style consistently across browsers. Developers often replaced checkboxes, radio buttons, and sliders with custom components built using JavaScript and additional HTML, increasing both complexity and maintenance costs. Modern CSS introduces the accent-color property, allowing you to apply your brand color to many native controls with a single declaration. It’s one of the simplest features in CSS, yet it can save significant development time while preserving accessibility and browser-native behavior.

Checkboxes

Changing the appearance of checkboxes used to require hiding the native element and rebuilding it from scratch. With accent-color, the browser does the work for you.

input[type="checkbox"] {
    accent-color: #4f46e5;
}

This approach keeps the control fully accessible while instantly matching your website’s visual identity.

Radio Buttons

Radio buttons can be customized just as easily. Instead of relying on custom SVGs or complex CSS, you only need a single property.

input[type="radio"] {
    accent-color: #4f46e5;
}

This creates a more consistent interface while dramatically reducing unnecessary styling code.

Range Sliders

Range sliders are another control that benefits from accent-color. While highly customized sliders may still require vendor-specific styling, many projects simply need a slider that matches the site’s branding.

input[type="range"] {
    accent-color: #4f46e5;
}

For many websites, this small property eliminates the need for lengthy browser-specific CSS and keeps native controls looking clean and modern.

text-wrap: balance : Better Headlines Automatically

Headlines are often the first thing visitors notice, yet they’re surprisingly difficult to optimize across different screen sizes. Long titles may wrap awkwardly, leaving one lonely word on the final line or creating uneven text blocks that feel visually unbalanced. Designers have traditionally solved this problem manually by inserting line breaks or adjusting copy for different layouts. Modern CSS introduces text-wrap: balance, allowing browsers to intelligently distribute text across multiple lines for a cleaner, more harmonious appearance.

Hero Sections

Large hero headings benefit enormously from balanced text wrapping. Rather than ending with an awkward single word, the browser automatically creates more visually pleasing line breaks.

.hero-title {
    text-wrap: balance;
}

This simple property can dramatically improve the first impression of your landing page without requiring manual adjustments.

Blog Titles

Blog posts often have long, descriptive titles that wrap differently depending on screen width. Balanced wrapping helps maintain readability while creating a more polished layout.

.post-title {
    text-wrap: balance;
}

The improvement may appear subtle, but typography is built on subtle details that collectively create a premium experience.

Card Components

Cards frequently contain titles of varying lengths, which can create inconsistent layouts across a grid. Applying balanced wrapping helps maintain visual harmony throughout the interface.

.card-title {
    text-wrap: balance;
}

Small typographic refinements like this contribute to cleaner, more professional designs without adding unnecessary complexity.

clamp() : Responsive Values Without Media Queries

Responsive design has traditionally relied on multiple media queries to adjust typography, spacing, and layouts across different screen sizes. While this approach works, it often leads to repetitive CSS that’s difficult to maintain. The clamp() function provides a far more elegant solution by allowing values to scale fluidly between a defined minimum and maximum. Instead of writing several breakpoints, you can create responsive interfaces using a single line of CSS.

Responsive Typography

Fluid typography is one of the best use cases for clamp(). Font sizes automatically grow and shrink based on the viewport while staying within sensible limits.

h1 {
    font-size: clamp(2rem, 5vw, 4.5rem);
}

This ensures headings remain readable on small devices while taking advantage of larger screens without additional media queries.

Responsive Spacing

Spacing should scale just as naturally as typography. With clamp(), padding and margins adapt to different screen sizes while maintaining a balanced layout.

section {
    padding-block: clamp(3rem, 8vw, 8rem);
}

The result is a more consistent design that feels comfortable across every device.

Responsive Layouts

clamp() can also control the width of containers, cards, and reusable components, allowing layouts to remain flexible without becoming excessively wide or narrow.

.container {
    width: clamp(320px, 90vw, 1200px);
}

By reducing the number of media queries needed, clamp() makes responsive design simpler, cleaner, and much easier to maintain—one of the reasons it’s become a staple of modern CSS development.

When You Still Need JavaScript

Modern CSS has become incredibly powerful, but that doesn’t mean JavaScript is becoming obsolete. Instead, the relationship between the two technologies has evolved. CSS is now capable of handling many visual interactions and UI behaviors that previously required scripting, while JavaScript remains the right tool for dynamic logic, data processing, and application-level functionality. Knowing where CSS ends and JavaScript begins is one of the characteristics of an experienced front-end developer. Before you remove JavaScript from your next project, keep these situations in mind—they’re exactly where it still shines.

  • Dynamic Data: CSS can control how content looks, but it cannot generate or update content dynamically. If your application displays live notifications, dashboards, user-generated content, or real-time updates, JavaScript is responsible for fetching and rendering that information.
  • API Requests: Communicating with external services requires JavaScript. Whether you’re retrieving products from an e-commerce API, loading blog posts, displaying weather information, or authenticating users, CSS simply has no access to external data sources.
  • Form Validation: HTML provides basic validation, and CSS can style valid or invalid states, but complex validation rules still belong to JavaScript. Checking password strength, comparing multiple fields, validating custom formats, or submitting forms asynchronously all require scripting.
  • State Management: Modern web applications constantly change state based on user interactions. Opening shopping carts, switching themes, managing authentication, updating dashboards, and synchronizing UI components are all tasks that depend on JavaScript rather than CSS.
  • Complex UI Interactions: Advanced interfaces such as drag-and-drop systems, interactive maps, data visualizations, custom drawing tools, video editors, collaborative applications, and sophisticated animation timelines still require JavaScript. While CSS can handle many visual transitions, application logic remains JavaScript’s responsibility.

Modern CSS Makes Better Developers: Not Less JavaScript

Modern CSS has fundamentally changed the way we build websites. Features like :has(), clamp(), scroll-snap, position: sticky, and aspect-ratio allow developers to replace hundreds of lines of JavaScript with clean, native browser capabilities. The result is lighter websites, improved performance, simpler maintenance, and code that’s easier for future developers to understand.

That said, the goal isn’t to eliminate JavaScript—it’s to use the right tool for the right job. By understanding what modern CSS can accomplish on its own, you can reduce unnecessary dependencies, improve the user experience, and build websites that are both faster and more maintainable.

If you’re looking for a website built with this philosophy—prioritizing clean code, modern CSS, performance, and a scalable design system—I’d be happy to help turn those principles into a polished, production-ready website.