Skip to content Go to Sitemap
You're 0% done reading!

Positioning Tutorial

Many beginners have trouble understanding how to position elements and create layouts in CSS. So I wrote this guide to teach you the most important stuff!

Hello mobile user! Due to the large amount of layout previews on this page I highly recommend viewing it on a desktop instead!

If you have questions or feedback regarding the tutorial, please use the comment section at the end of the page! Don't be shy; I know it's frustrating when you're stuck, and I'd love to help.

The Box Model (box-sizing)

The "box model" describes how paddings and borders work in CSS.

By default, paddings and borders are added to any width/height you define:

height: 100px;
width: 300px;

Default.

Actual height: 100px + padding (2x 10px) + border (2x 5px) = 130px.
Actual width: 300px + padding (2x 10px) + border (2x 5px) = 330px


With box-sizing: border-box you can change this behaviour. The paddings and borders will now be included in the width/height defined:

height: 100px;
width: 300px;
box-sizing: border-box;

Actual height: 100px
Actual widht: 300px

If you prefer this behaviour in general you can give every element on your page box-sizing: border-box by using

* {
  box-sizing: border-box;
}

I highly recommend doing this.

The display property

The display property is one of the most important CSS properties, so it's important to understand it well. It can take these values:

  • display: block: the element is full-width by default and takes up a new line, and can be given a fixed width and height
  • display: inline: the element does not take up a new line (instead, it flows with the text), and cannot be given a fixed width and height
  • display: inline-block: same as inline, but it's possible to set a size, and margins/paddings work as expected, and you can give it a fixed width and height.
  • display: flex: Flexbox (see later)
  • display: inline-flex: Flexbox (see later) which does not take up a new line
  • display: grid: Grid layout (see later)

Defaults

All HTML elements have either display: block or display: inline as a default setting.

Most elements, like <div>, <p>, <h1> are display: block by default. After them, there's an automatic line break. That's why - by default - multiple <div>s are shown underneath one another, not next to each other.

Some elements, like <span>, <img>, <b>, <i>, <u> are display: inline by default, which means they flow with the text. This is why you can make a word in a line bold without creating a line break. Unfortunately, you can't set a fixed size to display: inline elements. But you can make them display: inline-block instead and then you can, and margins and paddings will work as expected.

Examples

In each of the following examples I added a box in the middle of the text. The boxes have the following styling additionally to the display property:

height: 60px;
padding: 10px;
margin: 10px;

Notice how display: inline does have the padding and margin, but it's not quite behaving as expected: The top and bottom marings are ignored by other elements. The height we set is ignored completly (and width would have been ignored too).

Lorem ipsum, dolor sit amet consectetur adipisicing elit. display: blockDolores corporis, tempore incidunt sit optio facilis, quae aliquam non odio, debitis mollitia nihil culpa voluptatum odit voluptate voluptatem cupiditate sed suscipit!

Lorem ipsum, dolor sit amet consectetur adipisicing elit. display: inline Dolores corporis, tempore incidunt sit optio facilis, quae aliquam non odio, debitis mollitia nihil culpa voluptatum odit voluptate voluptatem cupiditate sed suscipit!

Lorem ipsum, dolor sit amet consectetur adipisicing elit. display: inline-block Dolores corporis, tempore incidunt sit optio facilis, quae aliquam non odio, debitis mollitia nihil culpa voluptatum odit voluptate voluptatem cupiditate sed suscipit!

Show more examples

Here are the rest of the possible values. Note that they are - so far - not much different from the ones above. Flexbox and Grid mostly become interesting once your element includes multiple elements inside of it. More to that later.

Lorem ipsum, dolor sit amet consectetur adipisicing elit. display: flex Dolores corporis, tempore incidunt sit optio facilis, quae aliquam non odio, debitis mollitia nihil culpa voluptatum odit voluptate voluptatem cupiditate sed suscipit!

Lorem ipsum, dolor sit amet consectetur adipisicing elit. display: inline-flex Dolores corporis, tempore incidunt sit optio facilis, quae aliquam non odio, debitis mollitia nihil culpa voluptatum odit voluptate voluptatem cupiditate sed suscipit!

Lorem ipsum, dolor sit amet consectetur adipisicing elit. display: grid Dolores corporis, tempore incidunt sit optio facilis, quae aliquam non odio, debitis mollitia nihil culpa voluptatum odit voluptate voluptatem cupiditate sed suscipit!

I never use the display: inline rule. In 99% of cases, you're better off using display: inline-block instead. But it's important to know that display: inline exists because it is the default styling of some elements, as I mentioned before.

The position property

The position property is important to understand when using the properties top, bottom, left and/or right to set an element's positon.

It can take the following values:

  • position: static (Default, does nothing)
  • position: relative: sets the element's position relative to itself
  • position: absolute: sets the element's position relative to the closest positioned element
  • position: fixed: sets the element's position relative to the browser window
  • position: sticky: Like fixed, but "sticks" to the top of the screen when you try to scroll past it. Not used much these days, because behaviour such as this is usually created using JavaScript

top, bottom, left, right

You can use top, bottom, left, right to set the position of an element. What exactly it does depends on the value of the positon property, as I just described.

These properties only work on positioned elements. (You will learn about this in a second.)

As a unit you can use px (Pixels) or % (percent) or vh (percent of browser height) or vw (percent of browser width), or any other distance unit (such as em).

Examples:

.element {
  top: 50px;
  left: 50px;
}
.element {
  top: 50%;
  left: 50%;
}

"Positioned" Elements

When you hear "positioned element" it means any element that has any position value other than static. So elements with position: relative or position: absolute or position: fixed, for example, are "positioned".

By default, all elements have position: static and are therefore not positioned.

Whenever you want to use position: absolute you have to make sure that one of its wrappers is positioned (= e.g. has position: relative). Otherwise it will not work!

Here is a very common example of absolute positioning:

<div class="container">
   <div class="element">Element</div>
</div>
.container {
  display: relative;
}

.element {
  display: absolute;
  left: 50%;
  top: 50%;
}
	
Container
position: relative;
Element
position: absolute;

Notice that the left side of the inner element is at 50% of the outer one, and the top side of the inner element is at 50% of the outer one. Therefore, using 50% is not the same centering as an element. We will cover how to center an element later in the this tutorial.

Examples

To illustrate the different values for the position property visually, here are some examples:

position: static
position: relative
(no top/bottom/left/right)
position: relative;
bottom: 20px;
right: 20px;
(This bigger box has position: relative, otherwise this would not work!)
position: absolute;
bottom: 20px;
right: 20px;
position: fixed;
bottom: 20px;
right: 20px;
For position: fixed look on the bottom right of your browser :)

(For an example for position: sticky click here.)

Transforming Elements (transform)

The transform property can do a lot of stuff, like repositioning ("translating"), resizing ("scaling"), rotating and mirroring an element.

Some examples:

transform: scale(1.5);
transform: rotate(90deg);
transform: translate(10px);
transform: translate(10px, 10px);
transform: translateX(10px);
transform: skew(10deg, 10deg);

You can use multiple transforms at once by writing them next to each other, seperated by a space. Example: transform: translate(15px, 15px) scale(1.2) rotate(5deg);

For a full list of all possible values go here.

transform only changes the appearance of an element (e.g. it's apparent size, font size and position), but not the actual space it takes up on your page (or actual font size, etc)!

You can see an example of this below: In the last box, notice how the element overflows its container even though only the size and rotation was changed and not its position.

Therefore, in most cases, you should only use transform for effects and decoration, not for the basic positioning of the elements.

Transformed elements may appear slightly blurry in some browsers and resolutions. There's nothing you can do about that.

Values

The transforms always have parentheses () in which you add the values.

Some transforms (like scale and rotate) only have one value. For Example: Scale has a percentage (written as a number, e.g. 1 = 100%, 0.5 = 50%, and so on). Rotate uses degrees (deg).

Other transforms, such as translate, have two values: one for X (horizontal: left to right) and the second for Y (vertical: top to bottom). For example, translate(5px, 10px) moves an element 5px to the right (= horizontal) and 10px down (= vertical). You can use negative values to move an element to the left or up.
If you only want to change X or Y, you can also use a shortcut like so: translateX(5px) or translateY(10px).

Note that translate(5px) (without a second value) is the same as translateX(5px).

When you use % in translate, it refers to the element's own width/height! For example translate(50%) moves the element half of its own width to the right.

Examples

Default:

transform: none

Repositioning ("Translating"):

transform: translate(15px, 15px)

Resizing ("Scaling"): (This still has the same font size as the other examples!)

transform: scale(1.2)

Rotating:

transform: rotate(5deg)

Mirroring: (this works by using transform: scaleX(-1))

transform: scaleX(-1)

Combination of multiple transforms:

transform: scale(1.2) rotate(5deg)

Stack Order (z-index)

If you have two elements on the same position with one "in front of" the other (which happens a lot when you use position: absolute or position: fixed you might have to define the stack order of the elements so that the correct one is in the "front".

We can do this by defining a z-index, e.g. z-index: 1;

z-index only works on positioned elements (= elements that have a positon value other than the default, so either position: relative or position: absolute or position: fixed).


A higher z-index means that the element will be shown further in the front.

The stack order of elements is:

  • Of the elements with a z-index, the one with the highest one
  • All other elements with a z-index, ordered by how high it is
  • Lastly, all elements without a z-index

If two elements have the same z-index (or none at all) the last element will be shown the furthest in the front.


By default the last element in the furthest in front:

1
(no z-index)
2
(no z-index)
3
(no z-index)

We can overwrite this by giving an element (in this example the first one) z-index: 1:

1
(z-index: 1)
2
(no z-index)
3
(no z-index)

In this example the first two elements have the same z-index (1). Therefore the last element (which has a z-index) is shown in front:

1
(z-index: 1)
2
(z-index: 1)
3
(no z-index)

Obviously, if an element has a higher z-index than another one, it is shown in the front:

1
(z-index: 1)
2
(z-index: 1)
3
(z-index: 2)

You will often see people use z-index: 99999; to make sure that that element will always be shown on the very front.

This is often not necessary: As you can see in the examples above, often z-index: 1; or another small number already does the trick!

How to Center Elements

Horizontal

To horizontally center the content of an element you can simply use text-align: center:

text-align: center;

And yes, this works with images and other inline (or Iinline-block) elements too:


To horizontally center elements themselves you can use
margin-left: auto; margin-right: auto;
(short version: margin: 0 auto;).

This only works for block elements (= display: block) with a fixed width (e.g. width: 300px;)!

display: block;
width: 300px;
margin-left: auto;
margin-right: auto;

Vertical

One way to vertically center an element is to use absolute positioning with top: 50% and transform: translateY(-50%).

(Don't forget that the container element needs to have position: relative for your absolute positioning to work!)

position: relative;
position: absolute;
top: 50%;
transform: translateY(-50%);
Please explain !?

This works because top: 50% sets the top edge of the element into the vertical center, and transform: translateY(-50%) moves the element up half its own height.

To demonstrate, this is what it would look like without transform: translateY(-50%):

position: absolute;
top: 50%;
(No transform)

In-Text

If you have an inline element inside of text you can use vertical-align: middle to center it vertically. Instead of middle you could also try top or bottom; I recommend trying all values and seeing which one looks best. Often (but not always) the default (basline) actually looks the most centered, so you don't have to do anything at all.

Text Text Text Text. This is vertical-align: baseline (default)

Text Text Text Text. This is vertical-align: middle

Text Text Text Text. This is vertical-align: top

Text Text Text Text. This is vertical-align: bottom

Centering with Flexbox

The easiest and fastest way to center an element both horizontally and vertically is to use Flexbox. (I do this literally all the time!) I will explain Flexbox in detail in a moment; For now all you need to know is 3 magic lines.

These lines must be applied to the outer element, not the inner one! The inner element does not need any styling.

.container {
  display: flex;
  justify-content: center;
  align-items: center;
}

.element {
	
}
Hello World

To understand why this works, read the next section (CSS Flexbox)! But, in short: The first line defines the container as a flex box, the second horizontally centers its content, and the third line vertically centers its content.

CSS Flexbox (display: flex)

If you give a container element display: flex the behaviour of all its children (= elements in it) will change.

Let's look at some examples. (In all the examples below the inner elements have a bit of padding and margin, but otherwise no styling concerning size or position.)


This is what it would look like without Flexbox: (The boxes are all full-width because they are divs, which are display: block by default.)

1
2
3
4
5

Once we give the wrapper element display: flex things change:

1
2
3
4
5

We can add flex-direction: row-reverse to reverse the order:

1
2
3
4
5

justify-content

We use justify-content to define the position of the elements along the main axis (in this case horizontally).

By default, it's justify-content: flex-start, which does what it says: The elements are at the start:

1
2
3
4
5

We can change this to justify-content: flex-end:

1
2
3
4
5

To center the elements, we can use justify-content: center:

1
2
3
4
5

For even spacing, we can use justify-content: space-around:

1
2
3
4
5

...or justify-content: space-evenly:

1
2
3
4
5

...or justify-content: space-between:

1
2
3
4
5

align-items

We use align-items to define the position of the elements along the other axis (in this case vertically).

Do not confuse align-items with align-content, which does something else!

The default is align-items: stretch, which stretches the items to fill the height of the container. (If your container has an automatic height you won't notice this at all.):

1
2
3
4
5

We can change this to align-items: flex-start:

1
2
3
4
5

or align-items: flex-end:

1
2
3
4
5

And centering is just as easy too: align-items: center:

1
2
3
4
5

... In combination with justify-content: center this will center our elements along both axes:

1
2
3
4
5

flex-direction: column

To change the direction of a flexbox to vertical, use flex-direction: column:

1
2
3
4
5

(The boxes are full-width here because the default value for align-items (which controls the positiong along the second axis) is stretch.)

The main axis is now vertical, not horizontal.

That means we change the horizontal alignment with align-items, not justify-content.
And we change the vertical alignment with justify-content, not align-items.

Using align-items: flex-start:

1
2
3
4
5

or align-items: center:

1
2
3
4
5

To reverse, we use flex-direction: column-reverse:

1
2
3
4
5

flex-wrap: wrap

Let's imagine our elements don't all fit in our container.

By default, the elements shrink as much as they can: (These items all have a width of 50px, but shrink to try their best to fit into their container)

1
2
3
4
5

If we don't want that to happen we can give each of the items (not the wrapper) flex-shrink: 0:

1
2
3
4
5

The widths are correct now, but it's overflowing! What to do?

We'll use flex-wrap: wrap (on the wrapper)! (We don't need to use flex-shrink: 0 anymore.) The elements that don't fit in the container overflow into the next line:

1
2
3
4
5

flex-wrap works with flex-direction: column too:

1
2
3
4
5

To stop the default stretch, we'll use align-items: flex-start again:

1
2
3
4
5

We can add align-content: flex-start (notice that it's align-content now, not align-items!) to keep the items closer together:

1
2
3
4
5

flex-shrink and flex-grow

We've already seen one way to style the items themselves: flex-shrink, which controls whether an item is allowed to or "shrink" to fit the wrapper. The default value is 1, which means "Yes, this element will try to shrink to fit the container." We can set it to 0 to stop an item from shrinking.

Here's an example with the 3rd element having flex-shrink: 0:

1
2
3
4
5

There's also flex-grow you can use to allow items to "grow" to fill the space in a container. The default is 0, which means by default items do not grow. We can set it to 1 to allow growing.

Without using flex-grow our container would look like this:

1
2
3
4
5

Here's an example with the 3rd element having flex-grow: 1:

1
2
3
4
5

Values bigger than 1 can be used to define the importance of shrinking/growing of elements.

In this example, the 3rd element has flex-grow: 1 and the 4th element has flex-grow: 2, which means that the 4th element will grow more than the 3rd:

1
2
3
4
5

There's also flex-basis, which sets the inital size of an item (in px or %). You won't need this a lot. Go here to read about it.

A shortcut for flex-grow + flex-shrink + flex-basis (in this order) is flex.

For example, flex: 0 1 5px; means: flex-grow: 0 + flex-shrink: 1 + flex-basis: 5px.


CSS Grid (display: grid)

By using display: grid you can easily create grid-based layouts.

Let's look at an example:

Header
Sidebar
(100px)
Main Content
(remaining width)
<div class="container">
  <header>Header</header>
  <aside>Sidebar</aside>
  <main>Main Content</main>
  <footer>Footer</footer>
</div>
.container {
  display: grid;
  grid-gap: 10px;
  grid-template:
    "header header"
    "sidebar main"
    "footer footer"
    / 100px 1fr;
}

header { grid-area: header; }
aside { grid-area: sidebar; }
main { grid-area: main; }
footer { grid-area: footer; }

Now, the CSS code might look confusing, so let's go through it step-by-step:

  • First, give your wrapper display: grid to use a grid layout.
  • We use grid-gap: 10px, which is a great way to define the margins between our elements.
  • Then we will define the layout with grid-template. This property takes two values, seperated by a slash (/):
    • The first value is your template, in which you define so-called grid areas. You can give them any name you like. In this example I chose header, sidebar, main and footer.
      Every row of your layout is in quotation marks (""), seperated by a space or line break. In each row you define each column of your layout, seperated by a space.
      Note that you can use one grid area multiple times if it spans multiple columns or rows. (In this example header and footer both span two columns.)
    • The second value is a list of column widths, e.g. 100px 1fr which would make the first column 100px wide and the second column 1fr (= one fraction) of the remaining width. Since we only use 1 fraction in total that 1 fraction is 100% of the remaining width.
  • Finally, for each of the wrapper's child elements (our header, sidebar, main content, and footer) we must define which "grid area" (of those defined by us in the grid-template rule) it belongs to.

    Make sure not to use quotation marks (") when using the grid-area rules. It won't work.

    Correct: grid-area: blah;
    Incorrect: grid-area: "blah";

And that's it! You can set your elements' heights like you usually would.


Let's look at another example.

Header
Sidebar
(1/3 of width)
Main Content
(2/3 of width)

This time, we're using different names for our grid areas, but the main difference is our layout. By changing the grid-template we have created a different layout, one in which our sidebar spans two rows.

.container {
  display: grid;
  grid-template:
    "myHeader myHeader"
    "mySidebar myMainContent"
    "mySidebar myFooter"
    / 1fr 2fr;
  grid-gap: 10px;
}

header { grid-area: myHeader; }
aside { grid-area: mySidebar; }
main { grid-area: myMainContent; }
footer { grid-area: myFooter; }

We also changed our column widths to 1fr 2fr. Remember, fr means fraction (of the remaining width). In our example we have 3 fractions in total (1fr + 2fr = 3), which means that 1fr is one-third of the width (1/3), and 2fr is two-thirds of the width (2/3).


If we wanted the sidebar to take up a bit less space we could use a higher total of fractions, e.g. 1fr 3fr. The total is 4, so 1fr is one-quarter (1/4) and 3fr is three-quartes (3/4):

Header
Sidebar
(1/4 of width)
Main Content
(3/4 of width)

Mobile Responsiveness

We can easily make our layout mobile responsive by changing the grid-template rule for smaller screens. I highly recommend using only a 1-column layout for mobile, otherwise the columns are horribly small. (Because we only have one column we can also just leave out the column width information.)

@media (max-width: 800px) {
  .container {
    grid-template:
      "myHeader"
      "mySidebar"
      "myMainContent"
      "myFooter";
  }  
}

On mobile, our layout would now look like this:

Header
Sidebar
Main Content

In an example such as this you might also want to hide your sidebar on mobile:

@media (max-width: 800px) {
  aside {
    display: none;
  }  
}

Scrollboxes (overflow)

If you want to create a scrolling box, give your element a fixed height or max-height and overflow-y: auto.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quaerat cupiditate mollitia veniam officiis. Repellendus doloribus consectetur numquam sint quo, dolorem itaque officia provident illum tempore assumenda sunt ratione reprehenderit.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quaerat cupiditate mollitia veniam officiis. Repellendus doloribus consectetur numquam sint quo, dolorem itaque officia provident illum tempore assumenda sunt ratione reprehenderit.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quaerat cupiditate mollitia veniam officiis. Repellendus doloribus consectetur numquam sint quo, dolorem itaque officia provident illum tempore assumenda sunt ratione reprehenderit.


Btw, You can do the same thing but horizontally with a fixed width or max-width and overflow-x: auto:

Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quaerat cupiditate mollitia veniam officiis. Repellendus doloribus consectetur numquam sint quo, dolorem itaque officia provident illum tempore assumenda sunt ratione reprehenderit.

(In this example I gave the container display: flex and the inner element flex-shrink: 0 to stop the text from breaking automatically.)

Working with Images

By default, elements stretch automatically to fit the size of their content, so images aren't much of a problem.

But imagine we have a container with a fixed width and/or height, and an image inside it. By default, the image won't fit:



(That's me by the way, hello!!)

We could add overflow: hidden to hide the "overflowing" parts:

... but that's still not ideal.

The trick to get the width right is to give the image width: 100% and height: auto. This is what we get:


If your container had automatic height this would be enough, but in our example we still have vertical overflow! (Even with overflow: hidden it would still be noticable!)

So, clearly what we need to do is give the image height: 100%, right? Let's see:

Okay, that fits smugly into the container, but the image is horribly stretched! We can fix this by adding object-fit: contain:

...or object-fit: cover, if you're okay with parts of the image being cropped off:

You can change the image crop's position by defining object-position. For example, object-position: top will focus on the top part of the image:

This is the code we ended up with:

.container {
  width: 200px;
  height: 150px;
  padding: 10px;
}

.container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}

If you ever see an annoying little empty space under an image even though you didn't define a margin there, try giving the image element display: block. That often gets rid of that unwanted space.

(Don't ask me why this happens... I have no clue lol.)

Let's combine (almost) everything we've learned by creating an image gallery with 10 images. There should be 4 images per row, or 2 images per row on mobile. On hover, the images should zoom in and rotate a bit and show a caption.

At the end, it will look like this:


Show Full Code

You don't need to read all of this right now, we're going to build up to it step-by-step!

/* Wrapper */
.example-image-gallery {
  display: flex;
  flex-wrap: wrap;
}

/* Items */
.example-image-gallery div {
  box-sizing: border-box;
  width: calc(25% - 10px);
  margin: 5px;
  border: 5px solid;
  overflow: hidden;
  position: relative;
}

/* Images */
.example-image-gallery div img {
  transition: transform 0.5s ease;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Captions */
.example-image-gallery div span {
  position: absolute;
  bottom: 15px;
  left: 0;
  text-align: center;
  width: 100%;
  box-sizing: border-box;
  background: white;
  color: black;
  padding: 5px 10px;
  font-size: 20px;
  opacity: 0;
  transform: translateY(100px);
  transition: transform 0.4s ease, opacity 0.5s ease;
}

/* Images on Hover */
.example-image-gallery div:hover img {
  transform: scale(1.35) rotate(10deg);
}

/* Captions on Hover */
.example-image-gallery div:hover span {
  opacity: 1;
  transform: none;
}

/* Mobile */
@media (max-width: 800px) {
  .example-image-gallery div {
    width: calc(50% - 10px);
  }
}

Step 1: The HTML

The HTML for this project is very straightforward.

All our items are a simple div with an image (img) and a caption (span).

<div class="example-image-gallery">
    <div> <img src="./img/example1.jpg" /> <span>Image 1</span> </div>
    <div> <img src="./img/example2.jpg" /> <span>Image 2</span> </div>
    <div> <img src="./img/example3.jpg" /> <span>Image 3</span> </div>
    <div> <img src="./img/example4.jpg" /> <span>Image 4</span> </div>
    <div> <img src="./img/example5.jpg" /> <span>Image 5</span> </div>
    <div> <img src="./img/example6.jpg" /> <span>Image 6</span> </div>
    <div> <img src="./img/example7.jpg" /> <span>Image 7</span> </div>
    <div> <img src="./img/example8.jpg" /> <span>Image 8</span> </div>
    <div> <img src="./img/example9.jpg" /> <span>Image 9</span> </div>
    <div> <img src="./img/example10.jpg" /> <span>Image 10</span> </div>
</div>

Step 2: Item Sizing

We're going to start our CSS with a simple code to make sure the images aren't bigger than they should be. We will give our items (the divs) a width of 25% because we want 4 images per row. I will also add a margin and border. To make sure that the images fill up their wrapper and don't overflow we use the object-fit: cover trick we learned earlier.

.example-image-gallery div {
  width: 25%;
  margin: 5px;
  border: 5px solid;
}

.example-image-gallery div img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Now, our items should look like this:

Image 1

Step 3: Flexbox

Right now, all our items are underneath one another instead of next to each other.

We will use flexbox to get them to display in a row, and automatically overflow to the next line.

.example-image-gallery {
  display: flex;
  flex-wrap: wrap;
}
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 9
Image 10

Almost there! One problem: We have 3 items in a line instead of the 4 we wanted.

This is because we defined the width of each item as 25%, but we also have a margin of 5px and a border of 5px on the left and right.

First, we'll add box-sizing: border-box to our items so that the borders (and padding, if we had any) count towards the width. That takes care of the borders, but the margins are still a problem.

So we need to set the width of each item to 25% minus 5px (left margin) minus 5px (right margin). We can do this using calc(): width: calc(25% - 5px - 5px), or just width: calc(25% - 10px).

While we're at it, we will also fix the width for mobile browsers. On smaller screens everything will be a bit smaller, so we only want to show 2 images per row instead of 4. This means our width will be 50% (minus margins) instead of 25% (minus margins):

The code:

/* Items */
.example-image-gallery div {
  box-sizing: border-box;
  width: calc(25% - 10px);
}

/* Mobile */
@media (max-width: 800px) {
  .example-image-gallery div {
    width: calc(50% - 10px);
  }
}

Now our gallery is already looking pretty good:

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 9
Image 10

Step 4: Captions

It's time to style our captions. We want them to be "in front of" the images, which means we have to use absolute positioning (position: absolute) and place the caption where we want (bottom: 15px and left: 0). We'll also add some basic styling, like width: 100%. To make sure the width is correct, we add box-sizing: border-box again.

Remember that, because we're using absolute positioning, the parent element needs to be "positioned", so we will give the item position: relative to take care of that.

This is the code we added:

/* Items */
.example-image-gallery div {
  position: relative;
}

/* Captions */
.example-image-gallery div span {
  position: absolute;
  bottom: 15px;
  left: 0;
  width: 100%;
  box-sizing: border-box;
  background: white;
  color: black;
  text-align: center;
  padding: 5px 10px;
  font-size: 20px;
}

Here's what it looks like:

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 9
Image 10

We could be done now, but I want to show you how to add some hover effects!

Step 5: Captions on Hover

First, we want the caption to show on hover only. We can do this by giving the captions opacity: 0 by default and opacity: 1 on hover.

To add some movement I will also give the captions transform: translateY(100px) by default and reset that to transform: none on hover. This means that the caption will be 100px further down when the element is not being hovered over, which essentially means that it moves up as we hover on it.

I'm adding a transition (for both the transform and the opacity) to make the effect smoother.

transition rules should always be added to the normal CSS rules, not the on-hover rules, otherwise it will only work when the mouse moves in but not out.

Here's the code we're adding:

/* Captions */
.example-image-gallery div span {
  opacity: 0;
  transform: translateY(100px);
  transition: transform 0.4s ease, opacity 0.5s ease;
}

/* Captions on Hover */
.example-image-gallery div:hover span {
  opacity: 1;
  transform: none;
}

Hover over the images to preview the effect:

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 9
Image 10

For now, you might see the captions being visible outside of the frames as they move up. We will fix this in the next step.

Step 6: Image Hover Effects

Finally, we're gonna add some image effects on hover. We want the images to become larger and rotate a bit, so we can use something like transform: scale(1.35) rotate(10deg). We're adding that to the images, not the whole items, otherwise our captions would move too.

Again, we're adding a transition (not just on hover!) to make the movement smoother.

/* Images */
.example-image-gallery div img {
  transition: transform 0.5s ease;
}

/* Images on Hover */
.example-image-gallery div:hover img {
  transform: scale(1.35) rotate(10deg);
}

Now it looks like this:

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 9
Image 10

Oops! Our images are moving outside of their frames! We can add overflow: hidden; to the items to stop that from happening. This also fixes the problem that our captions show up slightly outside of the frames.

/* Items */
.example-image-gallery div {
  overflow: hidden;
}

Now we're done:

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 9
Image 10

Looks neat, doesn't it?

Congrats! This is all I can teach you for now. I hope this tutorial helped you a bit!

I spend hours of my free time writing these tutorials that I publish for free. If you'd like to say thanks, please share this tutorial with others and/or buy me a coffee!

Comments

Leave your questions, problems, suggestions, requests and thanks here!

To share very long code or error messages with me, please upload it to pastebin (or a similar site) and include the link. This is to make sure you don't hit the max character limit on comments.