Artifacts Gallery Guilds Search Wiki Login Register

Welcome, Guest. Please login or register. - Thinking of joining?
a Summer day - @628.90 (what is this?)
Activity rating: Four Stars Posts & Arts: 76/1k.beats Random | Recent Posts | Guild Recents
News: :wizard: You can make anything on the web! :wizard: Guild Events: Summerween Watch-a-thon

+  MelonLand Forum
|-+  Life & The Web
| |-+  ✁ ∙ Web Crafting
| | |-+  Tutorial: a details/summary powered image gallery (HTML+CSS, no JS!)


« previous next »
Pages: [1] Print Embed
Author Topic: Tutorial: a details/summary powered image gallery (HTML+CSS, no JS!)  (Read 340 times)
Dan Q
Hero Member ⚓︎
*****
View Profile WWWArt


I have no idea what I am doing
⛺︎ My Room
RSS: RSS

Guild Memberships:
Artifacts:
Dan Q Cruisin'I DIDN'T meet Dan Q on Melonland!
« on: a Summer day » Embed

Inspired by a conversation with @FinleyIsHere over in the Wednesday Website Club, here's how I'd personally go about making a:

  • single page gallery (i.e. not using separate pages for the full versions of images; you can still paginate if you want!)
  • with thumbnails that you click to see the full images
  • and the full images have descriptions
  • with HTML and CSS only (no JavaScript dependency)

Here's what we're trying to make (I've gone for minimal/no "design"; that bit's up to you!):


If you're using a Static Site Generator (or CMS!) then you might want a different approach.

Rather than just give you a copy-pasteable example (although you can get that from a Codepen at the end!), I've tried to talk you through what I'm thinking and what I'm trying to achieve at each step along the way. My aim isn't to tell you what to do... it's to show how I, personally, approach this kind of problem in a way that you can follow-along with if you like.

Okay, let's go!

Step 1: image preparation

You'll need "thumbnail-sized" versions of your images. While it's technically-possible to use the full-sized version of the file "as" the thumbnail, this will mean that every visitor downloads the full version of every image... which will make the page load slower (boo!). Plus, deliberately making thumbnail-sized ones means that you can, if appropriate, crop them: e.g. the (delightful) Bird and Moon webcomic uses square thumbnails so that all the thumbnails look the same, even though individual comics are a mixture of square, portrait, and landscape. You can also make the thumbnails different, if you want, e.g. by focussing them on a particular part of the image, or by overlaying them with an age rating panda, or something. They're your thumbnails; do what you want!

It's up to you how you resize your thumbnails, but I'd suggest making them all the same height so that they line up nicely in a grid-shaped gallery.

Here's one of my demo images and its thumbnail (they're not very exciting!):


https://placehold.co/1200x800/darkblue/pink?text=Full%20Image%202 https://placehold.co/120x80/darkblue/pink?text=Thumbnail%202

Step 2: HTML

I'm a huge believer in the value of semantic HTML, so I ask myself: what does the thing I'm making represent, really? To me, an image gallery is a list of images, where each thumbnail is a summary version of the details in the full version. This tells me that I'm probably going to be using a <ul> (unordered list) for my gallery, with <li>s (list items) for each image, and a <details>/<summary> pair to represent the image and its thumbnail.

So here's the HTML I wrote, with my first three images in it:


Code
<!--
  it's a "list" of image (even though) we don't lay it out like a list;
  I'm giving it the class "gallery" so it's easy for me to restyle it
  and its contents using CSS later:
-->
<ul class="gallery">
  
  <!-- each image goes in its own list item <li>: -->
  <li>
    
    <!--
      each item is wrapped in a <details> block which contains:
      1. a <summary> version (the thumbnail), and
      2. the full version (the image and its description)
    -->
    <details>
      
      <!-- the "summary" is the thumbnail image -->
      <summary>
        <img src="thumbnail-1.png">
      </summary>
      
      <!--
        the full image follows, along with its description;
        note use of loading="lazy" so browsers don't download
        the image until they need to!
      -->
      <img loading="lazy" src="full-image-1.png">
      <p>Description of first image</p>
      
    </details>
  </li>

  <!-- I repeat the pattern above for each subsequent image: -->
  <li>
    <details>
      <summary>
        <img src="thumbnail-2.png">
      </summary>
      <img loading="lazy" src="full-image-2.png">
      <p>Description of second image</p>
    </details>
  </li>

  <li>
    <details>
      <summary>
        <img src="thumbnail-3.png">
      </summary>
      <img loading="lazy" src="full-image-3.png">
      <p>Description of third image</p>
    </details>
  </li>

</ul><!-- don't forget to end the list! indent your code to help! -->

To check that I'm on the right track, I look at my page without writing any CSS. It'll be ugly, but what I'm interesting in seeing is are the bones right: do I see a list of thumbnail images and, when I click one, I see the full version. Sure enough, that works:


Step 3: CSS

The goals with my CSS are:

1. Make the "list" look less like a list and lay my thumbnails out in a "grid-like" pattern.
2. Suppress the <details>/<summary> default little "tick", and make the cursor look "like a link" when hovering a thumbnail.
3. Hide the thumbnail when the full image is shown.
4. Show the full image and its description "full screen" (well, full page), and close it when it's clicked anywhere.

Let's take those one at a time:

3a. Laying out the list like a grid

The CSS for this isn't too complicated, but I'll talk you through it anyway:

- for the thing with class="gallery" (.gallery),
- remove the bullet points (list-style: none;)
- stop indenting it "like a list" (margin: 0; padding: 0;)
- instead, let its list items "flow" alongside each other (display: flex;),
- wrapping when they reach the edge of the page (flex-wrap: wrap;),
- with a small gap between each of them (gap: 15px;) -


Code
/* Lay the gallery out like a grid: */
.gallery {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

Important: did you know that you can nest CSS selectors. Doing this lets you write much-more concise CSS, and makes it easier to ensure that the CSS you mean to write for your gallery doesn't "escape" and affect other parts of your page (or site, if you have a single CSS file that you use throughout it).

I'll be doing this extensively. If I wanted to say "all <img>s in my gallery get a 2px red border", I might end up writing something like this:



Code
.gallery {
  /* ... everything I already wrote, above ... */

  img {
    /* this is just an example of CSS nesting; not part of the tutorial!!! */
    border: 2px solid red;
  }
}

That CSS targets only images inside something of class="gallery", which means my other images don't get the border. I love nesting CSS like this; anyway - I just wanted to make sure you saw it before I move on to using it:

3b. Stopping the <details>/<summary> from looking stoopid

This one's a bit arcane because not all browser manufacturers agreed on how it should be done. But here's what I wrote:

- for <summary> items inside galleries (summary, inside . gallery { ... }),
- turn the cursor into a pointer, like it was a link (cursor: pointer;), and
- don't show the "tick" mark (everything else; ugh what nasty code!)


Code
.gallery {
  /* ... everything I already wrote, above ... */
  
  summary {
    cursor: pointer;
    list-style: none;

    &::marker, &::-webkit-details-marker {
      display: none;
    }
  }
}

3c. Hiding the thumbnail when showing the full version

When the <details> element is "opened" (by clicking on it), it gains the attribute "open". You can think of that as meaning that it has the HTML <details open>: in fact, if you use Inspect Element, that's probably what your browser will say it is! We can use this to target the <details> element when it's open and do things with it.

In this case, I'll:

- find any <img> in the <summary> (i.e. a thumbnail image) inside a <details> that is open (note that I'm using nesting for this: I could absolutely write it out as a long selector, but I know that I'm going to need to do more things in here later!)
- hide it! (display: none;)


Code
.gallery {
  /* ... everything I already wrote, above ... */
  
  details[open] {
    summary {
      img {
        display: none;
      }
    }
  }
}

Now I can click on a thumbnail and see the full version... but there are still two problems:

1. I want to make them fill the screen (on to that in a moment), and
2. There's no way to turn them back to the thumbnail version! (we'll solve that afterwards)

3d. Displaying the full image "larger"

Here's my approach to displaying the full version "large":

- when a <details> is open (details[open]),
- display it in a fixed location, covering everything else on the page (position: fixed; top: 0; left: 0; width: 100%; height: 100%;)
- give it a background color so we can't see the original page contents "behind" it (background: white;)
- display its contents (the full image and its description) in a column, with everything centered (display: flex; flex-direction: column; align-items: center; justify-content: center;)
- and with the full size image (img - while this also targets the thumbnail image, we've already hidden that so it doesn't really matter):
- let it be no wider than the browser window (max-width: 100%;)
- and no taller than 90% of the browser window's Vertical Height (max-height: 90vh;)


Code
.gallery {
  /* ... everything I already wrote, above ... */
  
  details[open] {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: white;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    
    img {
      max-width: 100%;
      max-height: 90vh;
    }

    /* Here's the stuff I already wrote to hide thumbnails: */
    summary {
      img {
        display: none;
      }
    }
  }
}

That looks pretty good: I can click a thumbnail and I see the full version and its description paragraph "full screen". Now I just need to make sure it can be closed by clicking again:

3e. Making it "closeable"

I'm really grateful to my past self for setting up such clean nested CSS, because now I'm looking to:

- find the <summary> (the bit that can be clicked to open/close the <details>), inside the open <details>, in the gallery (.gallery { details[open] { summary )

- create a new "fake" element after it, using & (which means "the previous thing in the nested list", i.e. the summary) and :after (to create the new fake element)
- (I also need to use content: ''; to make the fake element actually appear: it has to have 'content', even if that content is an empty string!)
- make that new fake element fill the screen, just like we did with the open details (position: fixed; top: 0; left: 0; width: 100%; height: 100%;)

What this does is makes the <summary> - which was otherwise going to be hidden because the only thing "in" it (the thumbnail image) was hidden - fill the page, over the top of the full-page full-size image. Which means that clicking anywhere is is a click on the <summary>, which then toggles the <details> back to closed again:

Code
.gallery {
  /* ... everything I already wrote, above ... */
  
  details[open] {
    /* ... everything I wrote to make full-size images fill the screen ... */
    
    summary {
      /* Here's the stuff I already wrote to hide thumbnails: */
      img {
        display: none;
      }
      
      /* Here's the new stuff I'm adding: */
      &:after {
        content: '';
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
    }
  }
}

If you prefer, you can use the summary:after (&:after) to make a "close" button and position it in the top right or something.

3f. Putting it all together

I've shown you the CSS a bit at a time, but for your convenience, here's a commented copy of the entire CSS:


Code
/* Lay the gallery out like a grid: */
.gallery {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  
  /* Hide the details/summary 'marker', and use a 'pointer' cursor: */
  summary {
    list-style: none;
    cursor: pointer;

    &::marker, &::-webkit-details-marker {
      display: none;
    }
  }
  
  /* When clicking a thumbnail... */
  details[open] {
    /* display the full image full-screen and centered */
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    background: white;
    align-items: center;
    justify-content: center;
    
    img {
      max-width: 100%;
      max-height: 90vh;
    }
    
    summary {
      /* hide the thumbnail entirely: */
      img {
        display: none;
      }
      
      /* and add an 'overlay' so clicking anywhere closes the full version: */
      &:after {
        content: '';
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
    }
  }
}

The finished product

Here's how that looks when it's finished:



I've put the whole thing into a Codepen so you can try it and make tweaks there, if you want.

I hope that helps somebody!
Logged

https://danq.me/_q26t/badges/dan-q-88x31-lighter.gif https://danq.me/_q26t/badges/dan-q-88x31-peekaboo-scroller.gif https://beige-buttons.danq.dev/beige-buttons-88x31.gif https://embed-html.danq.dev/embed-html-88x31.gif

Artifact Swap: I met Dan Q on Melonland!Polyamorousradio polyDoctor RedactedJoined 2025!
MrsMoe
Hero Member ⚓︎
*****
View Profile WWWArt


Your friendly neighborhood living dead girl
⛺︎ My Room

Guild Memberships:
Artifacts:
Deviantart Llama!Great Posts PacmanFirst 1000 Members!Joined 2022!
« Reply #1 on: a Summer day » Embed

As someone who wants to make a proper gallery for their website, thank you so much for this!  :transport:
Logged

https://blinkies.cafe/b/display/0113-autism.gifhttps://blinkies.cafe/b/display/0021-vampirefangs.gifhttps://blinkies.cafe/b/display/0182-halloweencathouse.gifhttps://i.postimg.cc/02L6smjc/ezgif-7-ebef155de1.gifhttps://64.media.tumblr.com/e53076104c8d22ed3d272d62cc8fccfa/88280e3f54890c11-c1/s250x400/aa5c1239ea59888acaa71ad26271790a794ccc5d.gifvhttps://blinkies.cafe/b/display/0138-greenglow.gif
https://i.ibb.co/Lzg2SZj/tumblr-5afbadec7c65089061ba8cfa48b57bc8-469c61aa-100.webphttps://tinyurl.com/yn2v726zhttps://tinyurl.com/39tjbzfmhttps://tinyurl.com/32x3t38jhttps://tinyurl.com/2ybkhmh5https://tinyurl.com/2ue7d945https://tinyurl.com/493hsx5phttps://tinyurl.com/ye28hzrphttps://64.media.tumblr.com/92ec86321325be8fac199fecef8fd7b0/tumblr_ptpl0x7U4o1y8ua8do1_100.gifv
Limette
Jr. Member ⚓︎
**
View Profile WWWArt


⛺︎ My Room

Guild Memberships:
Artifacts:
I got robbed by Dan Q on Melonland!Joined 2025!
« Reply #2 on: a Summer night » Embed

:ozwomp: Instant bookmark! :ozwomp: Making a proper gallery for my art is something I knew I wanted on my site from the start, but I kept pushing it off because it intimidated me. This tutorial is PERFECT.

Of course, it came right as I was already head deep into reworking other parts of my site... so the task of making a gallery will have to be pushed off once more. BUT! I'll know where to look for help when I finally get there. Thank you!! I like semantic HTML too (tho I am no good at it yet), and this does help me understand how to utilize it.
Logged

https://limette.neocities.org/assets/limette.gifhttps://file.garden/aJDdYDydqnG0q1NR/blinkies/exinc.gif
Dan Q
Hero Member ⚓︎
*****
View Profile WWWArt


I have no idea what I am doing
⛺︎ My Room
RSS: RSS

Guild Memberships:
Artifacts:
Dan Q Cruisin'I DIDN'T meet Dan Q on Melonland!
« Reply #3 on: a Summer night » Embed

thank you so much for this!

Instant bookmark!

Quick question: would an "interactive" session on this kind of topic be the kind of thing that would be useful to anybody?

I've got this half-an-idea dream of running say a weekly share-my-screen (camera-optional) Zoom session to which anybody can drop in, where I try to solve some kind of interesting problem using HTML + CSS (and as little JS as I can get away with), with a focus on semantics and standards and self-growth.

But I fear that I'd organise such a thing and nobody would come, because it's not the right medium or something. I don't know. I just feel like there's a "missing" real-time/sync Melonlandia meeting. Homebrew Website Club do sync, but they're not so strong on sharing learnings. Melonland is good on sharing learnings, but not so much with the sync.

Dunno; just a thought I have sometimes.
Logged

https://danq.me/_q26t/badges/dan-q-88x31-lighter.gif https://danq.me/_q26t/badges/dan-q-88x31-peekaboo-scroller.gif https://beige-buttons.danq.dev/beige-buttons-88x31.gif https://embed-html.danq.dev/embed-html-88x31.gif

Artifact Swap: I met Dan Q on Melonland!Polyamorousradio polyDoctor RedactedJoined 2025!
Limette
Jr. Member ⚓︎
**
View Profile WWWArt


⛺︎ My Room

Guild Memberships:
Artifacts:
I got robbed by Dan Q on Melonland!Joined 2025!
« Reply #4 on: a Summer night » Embed

Quick question: would an "interactive" session on this kind of topic be the kind of thing that would be useful to anybody?

I would actually love this! I always learn best when there's someone working on a similar problem as me in a live environment, but that kind of thing isn't easily accessible when you're self teaching yourself and only have access to free resources on the internet. Usually you'd need to pay to get "coaching" of some sort, but that also isn't the same as just... coding with someone else. Not necessarily being taught, or collaborating on a project, but instead just watching someone problem solve. Maybe even trying to solve the same task in parallel.
Logged

https://limette.neocities.org/assets/limette.gifhttps://file.garden/aJDdYDydqnG0q1NR/blinkies/exinc.gif
Limette
Jr. Member ⚓︎
**
View Profile WWWArt


⛺︎ My Room

Guild Memberships:
Artifacts:
I got robbed by Dan Q on Melonland!Joined 2025!
« Reply #5 on: a Summer night » Embed

Sat down today to try my first shot at making my own gallery based on this code. Everything works smoothly for the most part, however I noticed that if an image has a long description, it just gets cut off. I tried to fix this by making the text and the image scrollable, except you can't scroll or otherwise interact with the <details> content, due to the "overlay":

Code
.gallery {
  /* ... everything I already wrote, above ... */
  
  details[open] {
    /* ... everything I wrote to make full-size images fill the screen ... */
    
    summary {
      /* Here's the stuff I already wrote to hide thumbnails: */
      img {
        display: none;
      }
      
      /* Here's the new stuff I'm adding: */
      &:after {
        content: '';
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
    }
  }
}

Dan Q did propose an alternative solution of adding an "X" button to close the image instead, which would solve the overlay problem. But I'm not sure how to position said button to always be at the correct spot relative to the image (for example, the top right corner), and frankly I prefer having the click anywhere option.

So, next I wondered if there is a way to make the overlay be "behind" the image and description, making it so they only close if you click somewhere outside of them. You can layer elements with the z-index if they are positioned, so I tried that out, but I couldn't get that to work either. Whether that's because it's not possible or because I did it wrong is anyone's guess.

In the end I realized that putting the overlay "behind" the content would make it pretty much impossible to close on a mobile screen where it takes up the whole screen anyway, so the X button would have to come into play no matter what.

Guess I'm back at square one!

@Dan Q do you have a solution for long text descriptions?
Logged

https://limette.neocities.org/assets/limette.gifhttps://file.garden/aJDdYDydqnG0q1NR/blinkies/exinc.gif
clownvomitz
Casual Poster ⚓︎
*
View Profile WWW


they/he/it
⛺︎ My Room
StatusCafe: clownvomitz
iMood: clownvomitz
RSS: RSS

Guild Memberships:
Artifacts:
Joined 2026!
« Reply #6 on: a Summer night » Embed

TYSM for this!!! I will definitely be looking in to incorporating something similar to what you've displayed here for my gallery!  :chef:

Quick question: would an "interactive" session on this kind of topic be the kind of thing that would be useful to anybody?


I think this would be really neat! Sometimes asking a person rather than helplessly Googling a niche question you have just makes things a lot easier to figure out and actually understand. The way you explained your process here was really helpful!
Logged

https://file.garden/ZpyoPBriBHjSP-h5/nb.gifvhttps://file.garden/ZpyoPBriBHjSP-h5/hb.gifv
https://adriansblinkiecollection.neocities.org/e65.gifhttps://adriansblinkiecollection.neocities.org/e48.gif
"sunrise, parabellum"

Artifact Swap: SMK LuigiSpongebob
Dan Q
Hero Member ⚓︎
*****
View Profile WWWArt


I have no idea what I am doing
⛺︎ My Room
RSS: RSS

Guild Memberships:
Artifacts:
Dan Q Cruisin'I DIDN'T meet Dan Q on Melonland!
« Reply #7 on: a Summer day » Embed

@Limette: if you're dropping the overlay effect, you might also want to not hide the originals when opening the large version? Otherwise it can look a bit odd.

But anyway: positioning an X button. As I see it, there are at least two (non-JS) options:

1. position the :after 'fixed' as now, but tether it to one corner (e.g. with top: 15px; right: 15px;), give it a suitable width and height, and put e.g content: 'x';. Then it'll be fixed to the top right corner. The effect is similar to what I do on my blog - click on a picture in this post, for example - although note that I'm using a different approach (<dialog>:target approach) which I plan to replace with one more-like the one I suggested to you. Anyway: this will position the 'X' in a fixed position in the top right of the screen.

2. I've not tried it, but you can probably do something like wrapping each <details><summary>...</summary>...</details> in something (like an <li>) and have some CSS that says "if the <li> :has an open <details>, then have IT fill the screen... so that the contents can be 'centered', so that the close button can be positioned relative to them? Or something. I'm just thinking out loud here.

A further option, I suppose, would be to have the scrollable text 'punch through' to the foreground, as you say. This ought to be possible: you might need to set position: absolute on the descriptions and position: relative on their parent to allow z-index to work?

If you've got a copy of your page up in almost-working state, I can help you inspect it?
Logged

https://danq.me/_q26t/badges/dan-q-88x31-lighter.gif https://danq.me/_q26t/badges/dan-q-88x31-peekaboo-scroller.gif https://beige-buttons.danq.dev/beige-buttons-88x31.gif https://embed-html.danq.dev/embed-html-88x31.gif

Artifact Swap: I met Dan Q on Melonland!Polyamorousradio polyDoctor RedactedJoined 2025!
chilblands
Jr. Member ⚓︎
**
View Profile WWW


make it happen or regret
⛺︎ My Room

Guild Memberships:
Artifacts:
I got robbed by Dan Q on Melonland!Joined 2026!
« Reply #8 on: a Summer day » Embed

Thank you for this! This is prob outside of intended purpose but I used this in combination of a carousel: https://chilblands.neocities.org/hobbies/junk-journal

This is something I am still working on and I only wanted a "bigger look" of the journal spread.

Edit: hmmm after using it, I am considering putting the carousel INSIDE the detail[open]. So that all of my journal books are in one place and people can click and look through. Maybe this is the best with css modal and then with my existing content inside? Choices, I have too many choices...

I've got this half-an-idea dream of running say a weekly share-my-screen (camera-optional) Zoom session to which anybody can drop in, where I try to solve some kind of interesting problem using HTML + CSS (and as little JS as I can get away with), with a focus on semantics and standards and self-growth.
I think weekly is too frequent... maybe once a month/ fortnight. I am interested (and most likely will just lurk)
« Last Edit: a Summer day by chilblands » Logged

chilblands it's getting cold in here...

Artifact Swap: Cursor!The poop hooliganHUH?Shocked SharaDevil HeartSafe from the Rain!Hungry MouseI met Dan Q on Melonland!Batman SpiffoPurby
fairyrune
Full Member ⚓︎
***
View Profile WWWArt


we live to make the impossible possible!
⛺︎ My Room
iMood: fairyrune
Matrix: Chat!

Guild Memberships:
Artifacts:
Dino FossilJoined 2025!
« Reply #9 on: a Summer day » Embed

this is great, dan!! thank you for posting! i have an aversion to using js for most things where possible and i wasn't sure how to make a gallery, but this works perfectly. (EDIT: i published my own gallery page using this post here!)

this is just a preference, but i made the background of the larger image slightly transparent, so the page can still be seen beneath it. i also removed the display: none from the thumbnail part when <detail> is "open". unfortunately, this shunts the thumbnail above the larger image when clicked! is there a way around that with this method, or would i need to employ a different one?

thank you for taking it step by step as well, and explaining everything as you go. it's great for learning.
:4u:
« Last Edit: a Summer night by fairyrune » Logged

☾ "you want to run - i'll run with you" ☼
https://files.catbox.moe/ivdca8.gifhttps://files.catbox.moe/qlgdtd.gifhttps://files.catbox.moe/ji1nvt.gifhttps://files.catbox.moe/9ypmrn.gifhttps://files.catbox.moe/3im96c.gif
Pages: [1] Print Embed 
« previous next »
 

Melonking.Net © Always and ever was! SMF 2.0.19 | SMF © 2021 | Privacy Notice | Send Feedback | Supporters ♥ Forum Guide | Rules | RSS | WAP | Mobile


MelonLand Badges and Other Melon Sites!

MelonLand Project! Visit the MelonLand Forum! Support the Forum
Visit Melonking.Net! Visit the Gif Gallery! Pixel Sea TamaNOTchi
MelonLand @000

Minecraft: Online
Join: craft.melonking.net