DEV Community

Cover image for Creating Modal Windows with Pure CSS: No JavaScript Required
Max Prilutskiy
Max Prilutskiy

Posted on

54 10 12 10 8

Creating Modal Windows with Pure CSS: No JavaScript Required

Wait, you can build modal windows without JavaScript? Yep, that's CSS secretly laughing at JavaScript behind its back. At Lingo.dev, we love diving into weird engineering rabbit holes that make other devs do a double-take.

Most developers wouldn't even think twice before reaching for a JavaScript library when they need a modal. But what if I told you there's this bizarre, oddball alternative hiding in plain sight? Pure CSS can pull off some surprisingly sneaky tricks that'll make you question everything you thought you knew about frontend development.

In this tutorial, we'll explore how to create fully functional modal windows using nothing but HTML and CSS. You can use it for fun, or in production. We'll be pushing CSS to do things it probably wasn't designed for, and that's exactly what makes this approach so deliciously unusual.

So, let's dive in!

Understanding the Modal Window Challenge

Modal windows are those overlay elements that pop up on top of your main content, usually demanding some kind of interaction before you can get back to what you were doing. You've seen them everywhere – login forms, image galleries, notifications, those annoying "SUBSCRIBE TO OUR NEWSLETTER" popups (though let's build less annoying ones, shall we?).

Traditionally, modals need JavaScript for three main things:

  1. Showing the modal when triggered
  2. Hiding the modal when dismissed
  3. Managing focus and keyboard interactions

So here's our challenge: how do we handle these state changes without JavaScript?

The answer comes from two clever CSS techniques that leverage built-in browser behaviors to create interactivity: the :target pseudo-class and what's affectionately known as the "checkbox hack."

The CSS Building Blocks

Before we start coding, let's get our heads around the core CSS concepts that make JavaScript-free modals possible.

The :target Pseudo-class

The :target pseudo-class is triggered when an element's ID matches the URL's hash fragment. In plain English? If your URL ends with #modal, any element with id="modal" will match the :target selector.

This gives us a way to change styles based on navigation state, which is perfect for showing and hiding modals. When someone clicks a link pointing to #modal, the browser navigates to that fragment, and we can use CSS to make the modal visible.

/* Hidden by default */
.modal {
  opacity: 0;
  pointer-events: none;
}

/* Visible when targeted */
.modal:target {
  opacity: 1;
  pointer-events: auto;
}
Enter fullscreen mode Exit fullscreen mode

The Checkbox Hack

The checkbox hack uses a hidden checkbox input and the :checked pseudo-class to toggle states. By connecting a label to the checkbox, we can create clickable elements that toggle the checkbox's state without JavaScript.

/* Hidden by default */
.modal {
  opacity: 0;
  pointer-events: none;
}

/* Visible when checkbox is checked */
.modal-checkbox:checked ~ .modal {
  opacity: 1;
  pointer-events: auto;
}
Enter fullscreen mode Exit fullscreen mode

Essential CSS Properties

Both techniques rely on several key CSS properties:

  1. Position: fixed - This takes the modal out of the document flow and positions it relative to the viewport, allowing it to overlay the entire page.

  2. Opacity & pointer-events - These control visibility and interaction. Setting pointer-events: none prevents the hidden modal from blocking clicks on elements beneath it.

  3. z-index - Ensures the modal appears above other content.

  4. Transitions - Adds smooth animations when showing and hiding the modal.

Now that we understand the building blocks, let's implement our first modal.

Building a Basic CSS Modal with :target

The :target approach is the simplest way to create a CSS-only modal. It requires minimal HTML and CSS, making it perfect for quick implementations.

Here's how it works:

  1. We create a link that points to the modal's ID
  2. When clicked, the browser navigates to that fragment
  3. The :target selector applies styles to make the modal visible
  4. Another link with an empty fragment (href="#") closes the modal

Let's build it step by step:

HTML Structure

<!-- Modal trigger button -->
<a href="#modal-example" class="modal-trigger">Open Modal</a>

<!-- Modal container -->
<div id="modal-example" class="modal">
    <!-- Backdrop for closing the modal when clicking outside -->
    <a href="#" class="modal-backdrop"></a>

    <!-- Modal content -->
    <div class="modal-content">
        <h2>Welcome to the Modal!</h2>
        <p>This modal window is created using only HTML and CSS!</p>
        <a href="#" class="modal-close">Close Modal</a>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

CSS Implementation

/* Modal container - hidden by default */
.modal {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;

    /* Center the modal content */
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
}

/* When the modal is targeted via URL hash, make it visible */
.modal:target {
    opacity: 1;
    pointer-events: auto;
}

/* Modal content box */
.modal-content {
    background-color: white;
    padding: 2rem;
    border-radius: 6px;
    width: 90%;
    max-width: 500px;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 5px 30px rgba(0, 0, 0, 0.2);
}

/* Backdrop close link - covers the entire screen */
.modal-backdrop {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    cursor: default;
    z-index: -1; /* Place behind modal content */
}
Enter fullscreen mode Exit fullscreen mode

Complete Working Example

For a fully functional implementation of this approach, check out our first example. This example includes:

  • A clean, minimal design
  • Smooth fade-in/out transitions
  • The ability to close the modal by clicking outside it
  • Responsive behavior for all screen sizes

This implementation creates a modal that:

  • Appears when you click the "Open Modal" link
  • Can be closed by clicking the close button or anywhere outside the modal
  • Animates smoothly with a fade effect
  • Is centered on the screen with a semi-transparent backdrop

The beauty of this approach is its simplicity. With just a few lines of HTML and CSS, we've created a functional modal without a single line of JavaScript.

However, the :target method does have some limitations. The most notable is that it changes the URL by adding a hash fragment, which affects browser history. Each time you open and close the modal, a new entry is added to the browser's history, meaning users might need to click the back button multiple times to navigate away from the page.

For applications where this behavior is problematic, the checkbox hack offers an alternative.

The Checkbox Hack Alternative

The checkbox hack avoids URL changes by using a hidden checkbox input to track the modal's state. Here's how it works:

  1. A hidden checkbox controls the modal's visibility
  2. A label linked to the checkbox serves as the open button
  3. Another label linked to the same checkbox serves as the close button
  4. CSS selectors use the checkbox's :checked state to show or hide the modal

Let's implement this approach:

HTML Structure

<!-- Hidden checkbox for controlling modal state -->
<input type="checkbox" id="modal-toggle" class="modal-checkbox">

<!-- Modal trigger button (label for the checkbox) -->
<label for="modal-toggle" class="modal-trigger">Open Modal</label>

<!-- Modal container -->
<div class="modal">
    <!-- Modal content -->
    <div class="modal-content">
        <h2>Welcome to the Modal!</h2>
        <p>This modal window uses the checkbox hack!</p>

        <!-- Close button (another label for the same checkbox) -->
        <label for="modal-toggle" class="modal-close">Close Modal</label>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

CSS Implementation

/* Hide the checkbox input */
.modal-checkbox {
    display: none;
}

/* Modal container - hidden by default */
.modal {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;

    /* Center the modal content */
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
}

/* When the checkbox is checked, make the modal visible */
.modal-checkbox:checked ~ .modal {
    opacity: 1;
    pointer-events: auto;
}
Enter fullscreen mode Exit fullscreen mode

Complete Working Example

For a fully functional implementation of the checkbox hack, check out our second example. This example includes:

  • A clean, modern design with a different color scheme
  • Scale animation for the modal content
  • No URL changes when opening/closing the modal
  • Responsive behavior for all screen sizes

The checkbox hack offers several advantages over the :target method:

  1. No URL changes or browser history entries
  2. More control over the modal's behavior
  3. Ability to create more complex interactions

However, it does require more HTML elements and careful consideration for accessibility. Without proper ARIA attributes, screen readers might not understand the relationship between the checkbox and the modal.

Enhancing Your Modal with Animations

Now that we have the basic functionality working, let's add some visual polish with CSS animations. Animations make the modal feel more responsive and provide visual cues about what's happening.

Here's how to add a simple entrance animation to our checkbox modal:

/* Modal content box with animation */
.modal-content {
    background-color: white;
    padding: 2rem;
    border-radius: 6px;
    width: 90%;
    max-width: 500px;

    /* Initial state - scaled down */
    transform: scale(0.8);
    transition: transform 0.3s ease;
}

/* Animate to full size when visible */
.modal-checkbox:checked ~ .modal .modal-content {
    transform: scale(1);
}
Enter fullscreen mode Exit fullscreen mode

This creates a subtle "pop" effect as the modal appears. You can get creative with different animation styles:

Slide-in Animation

.modal-content {
    /* Initial state - off-screen */
    transform: translateY(-50px);
    transition: transform 0.3s ease;
}

.modal-checkbox:checked ~ .modal .modal-content {
    transform: translateY(0);
}
Enter fullscreen mode Exit fullscreen mode

Fade-and-scale Animation

.modal-content {
    /* Initial state - transparent and small */
    opacity: 0;
    transform: scale(0.9);
    transition: all 0.3s ease;
}

.modal-checkbox:checked ~ .modal .modal-content {
    opacity: 1;
    transform: scale(1);
}
Enter fullscreen mode Exit fullscreen mode

Making Your Modal Responsive

For a truly polished modal, we need to ensure it works well on all device sizes. Here are some responsive design considerations:

/* Base styles for the modal content */
.modal-content {
    width: 90%;
    max-width: 500px;
    padding: 2rem;
}

/* Adjustments for small screens */
@media (max-width: 600px) {
    .modal-content {
        width: 95%;
        padding: 1.5rem;
    }

    /* Smaller text on mobile */
    .modal-content h2 {
        font-size: 1.5rem;
    }
}
Enter fullscreen mode Exit fullscreen mode

These adjustments ensure the modal remains usable on mobile devices, where screen real estate is limited.

Accessibility Considerations

While CSS-only modals are impressive, they do have accessibility limitations compared to JavaScript implementations. Here are some ways to improve accessibility:

  1. ARIA attributes - Add role="dialog" and aria-modal="true" to the modal container.

  2. Focus management - This is challenging without JavaScript, but you can use the :focus-within pseudo-class to style the modal when it contains focused elements.

  3. Keyboard navigation - Ensure all interactive elements are tabbable and that the modal can be closed with keyboard actions.

<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
    <div class="modal-content">
        <h2 id="modal-title">Accessible Modal Title</h2>
        <!-- Modal content -->
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Real-world Applications

CSS-only modals are unusual, however, they are perfect for several use cases:

  1. Image galleries - Click a thumbnail to show a larger version in a modal.
  2. Simple forms - Newsletter signups, contact forms, or login dialogs.
  3. Notifications and alerts - Display important messages to users.
  4. Content previews - Show a preview of content before navigating to a full page.

For more complex interactions, such as multi-step forms or modals that need to communicate with a server, you'll likely need JavaScript. But for many common use cases, CSS-only modals provide a lightweight, performant solution.

Browser Compatibility

The techniques we've covered work in all modern browsers. The :target pseudo-class has been supported since IE9, and the checkbox hack works in all browsers that support CSS3 selectors.

However, there are some edge cases to be aware of:

  1. Mobile browsers might handle the :target selector differently, especially regarding the back button behavior.
  2. Older browsers might not support some CSS features like flexbox or transitions.

Always test your modals across different browsers and devices to ensure consistent behavior.

Conclusion

Creating modal windows with pure CSS demonstrates the surprising power of modern CSS. It's pretty cool and unusual, that we can leverage the :target pseudo-class or the checkbox hack, to create interactive components that traditionally required JavaScript.

If you're open to using a bit of CSS, the benefits of this approach are clear:

  • Faster loading times (no JavaScript to download and parse)
  • Better reliability (works even when JavaScript fails)
  • Simpler maintenance (fewer moving parts)

While CSS-only modals do have limitations, particularly around complex interactions and accessibility, they're a valuable tool in any developer's toolkit. For many common use cases, they provide an elegant, lightweight solution that challenges our assumptions about what's possible with CSS alone.

Next time you need a simple modal, consider whether you really need JavaScript, or if CSS might be all you need. You might be surprised by how much you can accomplish with a few clever CSS tricks. ;)


Useful links:

Also:

ACI image

ACI.dev: The Only MCP Server Your AI Agents Need

ACI.dev’s open-source tool-use platform and Unified MCP Server turns 600+ functions into two simple MCP tools on one server—search and execute. Comes with multi-tenant auth and natural-language permission scopes. 100% open-source under Apache 2.0.

Star our GitHub!

Top comments (22)

Collapse
 
xwero profile image
david duymelinck

I find it a bit strange that nobody mentioned the dialog html element.
It is better than using a div and it has javascript events.

If you are feeling adventurous the CSS tricks from this post can be used. Personally I would stick to the small amount of javascript you need to add to make the dialog working.

Collapse
 
jesterly profile image
jesterly

Zero JS code is required if you use the Popover API with dialog element.

Collapse
 
xwero profile image
david duymelinck

Thank you for sharing that, I didn't know it existed. From MDN I see it is an experimental feature.

Thread Thread
 
jesterly profile image
jesterly

You're welcome. It's only experimental if you use hint popovers:

developer.mozilla.org/en-US/docs/W...

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

If you are feeling adventurous the CSS tricks from this post can be used

Haha, yes, exactly!

We personally do use these tricks internally, but most of the time, from what we see, it's a design system / component library implementation detail.

Collapse
 
anitaolsen profile image
Anita Olsen

This is just brilliant! Thank you for sharing with us! ✨

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

Thanks Anita :)

Collapse
 
jswhisperer profile image
Greg, The JavaScript Whisperer • Edited

Neat solution, I thought I'd fork your solution and demo a no js needed no css needed solution 🤯

I kid you not! I added CSS just to match your example, but it's not needed, and go for it disable js... still works github

Read more here: dev.to/jswhisperer/invoker-command...

it's also hella accessible!

Collapse
 
maxprilutskiy profile image
Max Prilutskiy • Edited

nice! 🔥

Collapse
 
anthonymax profile image
Anthony Max

Great article!

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

Thanks Anthony!

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

pretty cool you can ditch js for stuff like this - honestly makes me wonder if i’m over-complicating my own setups. you think pushing css like this actually makes sites easier to maintain in the long run or nah?

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

Great question Nathan!

I think it boils down to how well this type of CSS is encapsulated in the code in the end.

I personally think CSS modals are elegant, though it isn't the most usual thing to do. :)

Collapse
 
nevodavid profile image
Nevo David

Pretty cool seeing CSS pushed like this. Makes me want to mess around and see what else I can pull off without JS.

Collapse
 
ryanguitar profile image
Ryan Els

Excellent article and well explained 😁👍🏻

From now on I won't make a modal any other way.

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

⚡⚡

Collapse
 
lovit_js profile image
Lovit

Really loved how you explained both the :target method and the checkbox hack — super clear and practical. It’s amazing how much we can do with just CSS!

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

CSS is becoming a programming language 😅

Collapse
 
veronica_prilutskaya_c597 profile image
Veronica Prilutskaya

Cool CSS trick!

Collapse
 
maxprilutskiy profile image
Max Prilutskiy

Yes, indeed!

Collapse
 
nevodavid profile image
Nevo David

pretty cool seeing css pull this off - makes me want to rethink some of my usual stacks haha. you ever find yourself secretly hoping to kick more js out of your projects?