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:
- Showing the modal when triggered
- Hiding the modal when dismissed
- 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;
}
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;
}
Essential CSS Properties
Both techniques rely on several key CSS properties:
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.
Opacity & pointer-events - These control visibility and interaction. Setting
pointer-events: none
prevents the hidden modal from blocking clicks on elements beneath it.z-index - Ensures the modal appears above other content.
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:
- We create a link that points to the modal's ID
- When clicked, the browser navigates to that fragment
- The
:target
selector applies styles to make the modal visible - 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>
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 */
}
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:
- A hidden checkbox controls the modal's visibility
- A label linked to the checkbox serves as the open button
- Another label linked to the same checkbox serves as the close button
- 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>
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;
}
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:
- No URL changes or browser history entries
- More control over the modal's behavior
- 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);
}
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);
}
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);
}
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;
}
}
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:
ARIA attributes - Add
role="dialog"
andaria-modal="true"
to the modal container.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.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>
Real-world Applications
CSS-only modals are unusual, however, they are perfect for several use cases:
- Image galleries - Click a thumbnail to show a larger version in a modal.
- Simple forms - Newsletter signups, contact forms, or login dialogs.
- Notifications and alerts - Display important messages to users.
- 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:
-
Mobile browsers might handle the
:target
selector differently, especially regarding the back button behavior. - 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:
Top comments (22)
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.
Zero JS code is required if you use the Popover API with dialog element.
Thank you for sharing that, I didn't know it existed. From MDN I see it is an experimental feature.
You're welcome. It's only experimental if you use hint popovers:
developer.mozilla.org/en-US/docs/W...
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.
This is just brilliant! Thank you for sharing with us! ✨
Thanks Anita :)
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!
nice! 🔥
Great article!
Thanks Anthony!
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?
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. :)
Pretty cool seeing CSS pushed like this. Makes me want to mess around and see what else I can pull off without JS.
Excellent article and well explained 😁👍🏻
From now on I won't make a modal any other way.
⚡⚡
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!
CSS is becoming a programming language 😅
Cool CSS trick!
Yes, indeed!
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?