This tutorial will teach you how to make an HTML album component, as shown above, for the gallery or artwork pages for your websites. This component is inspired by early smartphone and skeuomorphic design, such as Samsung's Gallery app on their old phones. Much like the inspiration, this component will have images that are stylized to have photo borders, are visibly stacked on each other, and complete with animations. Ready to replace your boring square image* link?
*I actually do this tool in mobile mode. Still keep accessibility in mind!
Before we begin, I'm going to assume that you know how to setup a new HTML file and that is what you will be working with in this tutorial. I'll also assume that you are using your own images for this tutorial. The ones I'll use are below:
HTML Breakdown
This component is intended to be a link. As such, all the elements
that we create from now on will be a child of a single a
element.
We'll then add a span
element to the anchor, which will contain
our images. We'll also give this span
the class name
imgs-holder
. We then add three <img>
tags as a
child to this span. Of course, set the src
attribute to the
thumbnails you'd like to use. The alt
attributes of the image
elements can just be left empty.
The next child of the anchor will be another span
element, which has the
content of our name. As such, give this element the class name album-name
.
<a href="#">
<span class="imgs-holder">
<img src="1.png" alt="" />
<img src="2.png" alt="" />
<img src="3.png" alt="" />
</span>
<span class="album-name">
Album Name
</span>
</a>
Without any styling, the page should look like the following below.
Clearly, styling is needed.
CSS Breakdown
Image Sizing and Stacking
Let's start killing the first bird by limiting the image sizes first.
img {
max-height: 12rem;
max-width: 12rem;
}
Moving on to the second bird, there's various ways to stack elements using
CSS, but the method I'll
use will be using CSS
grid. Let's
use a grid layout on imgs-holder
. It should only have one column and row, and
while we're at it, center the images too.
span.imgs-holder {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
align-items: center;
justify-items: center;
}
We also need to tell the images in which column and row they belong, otherwise they will be placed in newly created rows, which is what we don't want.
img {
grid-column: 1;
grid-row: 1;
...
}
Now, the images should be stacked... But don't blame yourself if you can't tell.
Image Order and Frames
After setting up image stacking, you may have noticed that the images are stacked in the wrong order. As such, we'll have to tell each image what order they should be in.
img:first-child {
order: 3;
}
img:nth-child(2) {
order: 2;
}
img:last-child {
order: 1;
}
In addition to making each image visible and differentiable from one another,
let's also add some image borders. From my personal experience, I found that
HTML elements that use the border property have weird effects when using
transitions and transformations. Instead of using borders on the images, let's
instead give the images some padding
with a background
. Let's also top it
off with a light drop-shadow
filter.
img {
...
padding: 0.25rem;
background: linear-gradient(rgb(240, 240, 240), rgb(220, 220, 220));
filter: drop-shadow(0 0 2px rgba(96, 96, 96, 0.75));
}
Now, the images looks nice, but we still got to adjust the rest of the component.
Size, Alignment, and Text
Currently, the component consumes the entire width of the page. Let's add a
width
and height
for the component to use.
a {
width: 16rem;
height: 16rem;
}
Once the size has been set, let's then make the elements in the align to the component's center. We can do this with flexbox. We need to also specify that the direction the component is going in a column. Let's also add some spacing between the label and images.
a {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5rem;
...
}
Finally, let's modify the text to be black, set the font, and remove the underline (at least when the component isn't focused or hovered).
a {
...
color: black;
font-family: Arial, sans-serif;
text-decoration: none;
}
Now, it's time for the component to react to our hovers and focuses.
Transforms and Animation
The component will do four things when hovered/focused:
- Have all images grow in size.
- Have the two images behind the first slightly move outward.
- Have the label move slightly downward to get out of the images' way.
- Animate all the transformations above.
Each image will scale with the same factor. So let's create a scale
variable
so we don't have to type in the same value each time and in case we would like
to easily adjust the scaling factor.
:root {
--scale: scale(1.075);
}
We can easily animate the transformations of img
and span.album-name
with
the transition
property.
img,
span.album-name {
transition: transform 0.1s;
}
Since this component is a link, we should probably indicate that by underlining the text on hover/focus.
a:focus,
a:hover {
text-decoration: underline;
}
And now, time it's time to add all the transformations we would like to apply to our images and label.
a:focus img:first-child,
a:hover img:first-child {
transform: var(--scale);
}
a:focus img:nth-child(2),
a:hover img:nth-child(2) {
transform: var(--scale) translate(0.4rem, 0.25rem) rotate(3deg);
}
a:focus img:last-child,
a:hover img:last-child {
transform: var(--scale) translate(-0.3rem, -0.345rem) rotate(-2deg);
}
a:focus span.album-name,
a:hover span.album-name {
transform: translateY(0.5rem)
}
And... Voilà!
Full Code
The code below is also available on Codeberg.
<!DOCTYPE html>
<html lang=" en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Album Component</title>
</head>
<body>
<a href="#">
<span class="imgs-holder">
<img src="1.png" alt="" />
<img src="2.png" alt="" />
<img src="3.png" alt="" />
</span>
<span class="album-name">
Album Name
</span>
</a>
</body>
<style>
/* This was for screenshots. */
body {
background: #f5f5f5;
}
:root {
--scale: scale(1.075);
}
a {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5rem;
width: 16rem;
height: 16rem;
color: black;
font-family: Arial, sans-serif;
text-decoration: none;
}
span.imgs-holder {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
align-items: center;
justify-items: center;
}
img {
grid-column: 1;
grid-row: 1;
max-height: 12rem;
max-width: 12rem;
padding: 0.25rem;
background: linear-gradient(rgb(240, 240, 240), rgb(220, 220, 220));
filter: drop-shadow(0 0 2px rgba(96, 96, 96, 0.75));
}
img:first-child {
order: 3;
}
img:nth-child(2) {
order: 2;
}
img:last-child {
order: 1;
}
img,
span.album-name {
transition: transform 0.1s;
}
a:focus,
a:hover {
text-decoration: underline;
}
a:focus img:first-child,
a:hover img:first-child {
transform: var(--scale);
}
a:focus img:nth-child(2),
a:hover img:nth-child(2) {
transform: var(--scale) translate(0.4rem, 0.25rem) rotate(3deg);
}
a:focus img:last-child,
a:hover img:last-child {
transform: var(--scale) translate(-0.3rem, -0.345rem) rotate(-2deg);
}
a:focus span.album-name,
a:hover span.album-name {
transform: translateY(0.5rem)
}
</style>
</html>