← Back to CSS Course | Chapter 8: Animations & Effects | Lesson 13 of 22

CSS Image Modal

A modal that pops a thumbnail up into a full-screen lightbox is usually thought of as a JavaScript feature, but CSS alone — using either the :target pseudo-class or a hidden checkbox — can open and close one with nothing but link clicks.
Syntax
css
.modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: integer;
}
.modal img {
  max-width: 100%;
}

The :target Technique

Linking a thumbnail's <a href="#photo1"> to an element with id="photo1" lets CSS style that element differently when it's the page's current URL fragment, using the :target pseudo-class — this is enough to show a hidden full-size overlay purely from a click, no JavaScript required.

Note:
  • Accepted values:
  • :target — matches the element whose id is in the URL hash
  • display: none — default hidden state
  • display: block | flex — shown when targeted
  • position: fixed — covers the viewport

Example: The :target Technique

css
<style>
#photo1 {
  display: none;
  background: #333;
  color: white;
  padding: 20px;
}
#photo1:target {
  display: block;
}
</style>
<a href="#photo1">Open photo</a>
<div id="photo1">Full-size image</div>

Building the Modal Overlay

The modal itself is a fixed-position, full-viewport element (position: fixed; inset: 0) that starts hidden with display: none or opacity: 0, and only becomes visible when its :target rule matches, centering the enlarged image with flexbox.

Note:
  • Accepted values:
  • position: fixed — covers the viewport
  • inset: 0 — stretches to all edges
  • background: rgba(0,0,0,0.8) — dim backdrop
  • z-index: <integer> — stacks above the page

Example: Building the Modal Overlay

css
<style>
.modal {
  display: none;
  position: fixed;
  inset: 0;
}
.modal:target {
  display: flex;
}
</style>
<div id="photo1" class="modal">
  <img src="https://placehold.co/400x300" alt="Enlarged photo">
</div>

Closing the Modal

A close control is just another link, either back to the page's plain # (or no fragment at all) or to a dead anchor — clicking it changes the URL fragment away from the modal's id, so the :target rule no longer matches and the modal disappears.

Note:
  • Accepted values:
  • <a href="#"> — clears the hash so :target stops matching
  • position: absolute — places the close button
  • cursor: pointer — shows it is clickable
  • z-index — keeps it above the image

Example: Closing the Modal

css
<style>
.modal {
  display: none;
  background: rgba(0,0,0,0.7);
  color: white;
  padding: 20px;
}
.modal:target {
  display: block;
}
</style>
<div id="photo1" class="modal">
  <a href="#" style="color: white;">Close</a>
</div>

The Checkbox Hack Alternative

An alternative technique hides a real <input type="checkbox"> off-screen and pairs it with a <label> that toggles it; CSS then uses the :checked pseudo-class on a sibling selector to show or hide the modal based on the checkbox's state instead of the URL.

Note:
  • Accepted values:
  • input:checked ~ .modal — sibling selector shows the modal
  • display: none — hides the checkbox
  • <label for="id"> — toggles it
  • ~ — general sibling combinator, siblings after the checkbox only

Example: The Checkbox Hack Alternative

css
<style>
.modal {
  display: none;
  background: #333;
  color: white;
  padding: 20px;
}
#toggle:checked ~ .modal {
  display: block;
}
</style>
<input type="checkbox" id="toggle" hidden>
<label for="toggle">Open</label>
<div class="modal">Shown via :checked, no URL change</div>

:target vs Checkbox Hack

The :target approach changes the actual URL (useful for direct-linking to a specific opened image, but means the back button also closes it), while the checkbox hack leaves the URL untouched but requires careful sibling-selector structuring since :checked can only affect elements that come after the checkbox in the DOM.

Example: :target vs Checkbox Hack

css
<style>
#a:target { background: yellow; }
</style>
<a href="#a">Target link changes the URL, back button closes it</a>
<div id="a">Target section</div>
Live Example
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting the matching id on the target element, so :target never triggers.
  2. Using :target and jumping the page scroll to the element.
  3. Using the checkbox hack without connecting the label and the checkbox with for and id.
Browser Support

Supported in all modern browsers: Chrome, Firefox, Safari, Edge and Opera.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.