Sliding Header Layout

A tutorial on how to create a simple layout with a fullscreen header that slides up to reveal a content area with an image grid.

Today we’d like to show you how to create a simple grid layout with a special header effect. The idea is to initially show a fullscreen image with a title and a toggle button that allows to change the view to a grid. The header with the fullscreen image animates upwards and reveals a grid of image anchors with a title. We’ll be using some techniques like Flexbox and CSS Transitions for modern browsers. For the trigger button that toggles the views, we’ll create a vector graphic with two groups, one containing a little grid and the other one containing a cross. These two groups will have their visibility toggled according to the current view (fullsreen header or grid).


The inspiration for the layout and the effect come from this fabulous Dribbble shot by Dann Petty: iPad Magazine – Table of Contents

Web technologies we’ll be using in this tutorial:

The beautiful images used in the demo are by Dan Rubin. Find him on Instagram, Twitter, Flickr and VSCO. Please don’t use the images without his permission.

The grid will be laid out using Flexbox and we’ll control the number of items and their height with specific media queries and by using Viewport Units. Using the same vw values for the width and height (more specifically, for the flex-basis and the height) of the grid item, we can create an adaptive layout where we keep the proportions of the anchor/image close to a square. There are of course other techniques for doing this but let’s explore the relative viewport units in combination with a Flexbox layout today. If you’d like to know more about the support, we recommend Can I Use, the go-to site for browser support info and stats.


So, let’s get started!

The Markup

Let’s think for a moment, what blocks we’ll need for our little layout. We’ll need a header with an image and a title block, and we’ll need a main content block with a grid. Everything should be wrapped in a container that we’ll use to control what’s happening by toggling a view class.

Let’s build the header in the following way:

<header class="intro">

	<img class="intro__image" src="img/header.jpg" alt="Iceland glacier"/>

	<div class="intro__content">

		<h1 class="intro__title">Essential Feelings</h1>

		<div class="intro__subtitle">

			<div class="codrops-links">
				<!-- links -->

			<div class="intro__description"> <!-- ... --> </div>

			<button class="trigger">

				<svg width="100%" height="100%" viewBox="0 0 60 60" preserveAspectRatio="none">
					<g class="icon icon--grid">
						<rect x="32.5" y="5.5" width="22" height="22"/>
						<rect x="4.5" y="5.5" width="22" height="22"/>
						<rect x="32.5" y="33.5" width="22" height="22"/>
						<rect x="4.5" y="33.5" width="22" height="22"/>
					<g class="icon icon--cross">
						<line x1="4.5" y1="55.5" x2="54.953" y2="5.046"/>
						<line x1="54.953" y1="55.5" x2="4.5" y2="5.047"/>

				<span>View content</span>


		</div><!-- /intro__subtitle -->

	</div><!-- /intro__content -->

</header><!-- /intro -->

The header gets the class “intro”. The fullscreen image is the first child with the class “intro__image”. What follows is the intro content that contains the main title and a subtitle with three elements: some links, a description and the trigger button. The trigger button is a vector graphic that we have created previously. It consists of two shape groups: a grid and a cross. We give them suitable class names and we’ll control their appearance with a class that we’ll toggle in our script.

Next, we need to create the markup for our grid. For that we’ll have a main container and a wrapper for the grid anchors:

<section class="items-wrap">

	<a href="#" class="item">
		<img class="item__image" src="img/item01.jpg" alt="item01"/>
		<h2 class="item__title">Magnificence</h2>

	<a href="#" class="item">
		<img class="item__image" src="img/item02.jpg" alt="item02"/>
		<h2 class="item__title">Electrifying</h2>

	<!-- ... -->

</section><!-- /items-wrap -->

You can also use a list here if you like and wrap each anchor into a li, or a figure element. We choose to spare the extra markup and keep it simple.

So, that’s our HTML. Let’s get to the juicy CSS.


Note that we don’t use vendor prefixes here, but you can of course find them in the stylesheet files.

Let’s revise what exactly we want to achieve with our layout and what effects we want to happen:

  1. The header should occupy the full screen initially and the title area should be shown at the bottom of this intro page.
  2. When the trigger button is clicked
    • the intro should slide up and be 250px of height while darkening and the image should move down a bit so that we create a slight parallax effect
    • the content should be revealed with an overlay effect simulating light entering the dark content area
    • the trigger button should change from a grid icon to a closing cross
  3. The grid items in the content area should have a hover effect where the title fades out while moving down and the images zooms a bit and becomes brighter.

In order to achieve the main effects, we’ll have to define the default states of the elements/blocks and the states when the grid is shown.

Note that some default styles will be predefined and we’re not going to mention them here as it’s something you might want to adjust by yourself (body, default anchor styles and colors). We usually define those in our demo.css where we also set the box-sizing to border-box. Additionally, we also use Normalize.css.

Let’s start by styling the header. We want it to occupy all the width and height of the screen and detach it from the rest of the content layout, so let’s define the dimensions as well as its position (fixed). We’ll set the overflow to hidden because we don’t want anything to stick out, in particular, the image. For the sliding effect, we’ll add a transition and a nice easing function. When we add the class for opening the content area, i.e. container–open, on the main container, we will set the transform of the intro. We use two translate3d transforms: the first one will move it up completely (100% of its own height) and the second one will move it down 250px which is the final height we want for our header. (We could as well use just one transformation on the Y-axis and say calc(-100% + 250px) but transitioning to that won’t work in IE.)

.intro {
	position: fixed;
	z-index: 10;
	overflow: hidden;
	width: 100%;
	height: 100%;
	background: #2a2e39;
	transition: transform 0.6s;
	transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);

.container--open .intro {
	transform: translate3d(0, -100%, 0) translate3d(0, 250px, 0);

The fullscreen intro image will to be positioned absolutely and we’ll set the minimum height to 120% of its parent because we want to translate it 20% up. Setting the opacity to 0.8 will make the background color of our header shine through a bit. We should use the same easing function for the image and also the same time. This will make the effect feel more natural by syncing the acceleration of the elements. When we set the opening class to the main container, we’ll make the image completely transparent and translate it 20% down.

.intro__image {
	position: absolute;
	bottom: 0;
	min-width: 100%;
	min-height: 120%;
	width: auto;
	height: auto;
	opacity: 0.8;
	transition: transform 0.6s, opacity 0.6s;
	transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);

.container--open .intro__image {
	opacity: 0;
	transform: translate3d(0, 20%, 0);

The style for the intro content is the following. We’ll stuck it to the bottom of its parent and set a padding along with an enlarged font size.

.intro__content {
	position: absolute;
	bottom: 0;
	padding: 1.8em;
	width: 100%;
	font-size: 1.15em;

The style for the intro content is the following. We’ll stuck it to the bottom of its parent and set a padding along with an enlarged font size. Note that some default styles are predefined and we’re not going to mention them here as it’s something you might want to adjust by yourself (body, default anchor styles and colors).

.intro__title {
	margin: 0 0 20px;
	font-weight: 900;
	font-size: 4em;
	font-family: "Playfair Display", Georgia, serif;
	line-height: 1;

For the subtitle which contains the links, the description and the trigger button, we will use a flexbox layout. For that, we define the flex display on the parent. Setting align-items to center will center the children on a horizontal line. This kind of centering can also be achieved by setting the children to display: inline-block and their vertical-align to middle. But that’s the only useful thing we can do with that display value. Flexbox can do so much more for us.

.intro__subtitle {
	display: flex;
	align-items: center;

For example, we can push the trigger button to the right end by simply setting the right margin of the description to auto. Simply beautiful. No widths set, no floats, just awesome flexbox.

.intro__description {
	margin: 0 auto 0 1em;
	line-height: 1.2;

Next, let’s style our trigger button. So, we have an inline SVG where we’ve set the following attributes:

<svg width="100%" height="100%" viewBox="0 0 60 60" preserveAspectRatio="none"><!-- ... --></svg>

Setting the preserveAspectRatio to none will stretch/shrink our graphic to any dimension. It happens that we made a 60×60 sized icon but we actually want it to be a bit smaller. No problem, we simply set the desired width and height to its parent. Note that we set the flex value to none here because we don’t want the icon to be squeezed when we don’t have so much space.

.trigger {
	position: relative;
	overflow: hidden;
	margin: 0 0 0 20px;
	padding: 0;
	width: 40px;
	height: 40px;
	outline: none;
	border: none;
	background: none;
	flex: none;

Let’s hide the button text but leave it accessible:

.trigger span {
	position: absolute;
	top: 100%;

We want the shapes to have a stroke and no fill:

.icon rect,
.icon line {
	stroke: #dbdbdb;
	fill: none;
	stroke-width: 2px;

Note that when you resize a SVG you’ll have to take that into consideration when defining the stroke width. We’ve shrunk our icon so we need to set a stroke-width of 2 in order to look decent.

The icon groups will have a transition:

.icon {
	transition: opacity 0.3s, transform 0.3s;
	transform-origin: 50% 50%;

And we’ll hide the grid icon and show the cross when activating the button (by scaling and fading them in/out). This active class we’ll set in our script.

.trigger--active .icon--grid {
	opacity: 0;
	transform: scale3d(0.5, 0.5, 1);

.trigger--active .icon--cross {
	opacity: 1;
	transform: scale3d(1, 1, 1);

And that’s the styling for the header. Now, let’s write the styles for the main content section.

The wrap for the items which are anchors with images will have a flex display. Because we want a grid layout, we’ll wrap the items. We’ll set the top padding to the height of the intro header when the content is open.

.items-wrap {
	position: relative;
	display: flex;
	flex-wrap: wrap;
	padding: 250px 5px 0;

For the brightening effect, we’ll use the ::after pseudo-element of the wrap and animate its opacity from 1 to 0 using the same duration and timing-function as before:

.items-wrap::after {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background: #2a2e39;
	content: '';
	opacity: 1;
	transition: opacity 0.6s;
	transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
	pointer-events: none;

.container--open .items-wrap::after {
	opacity: 0;

The grid items will be flexible and we’ll set the flex-basis to a default of 25% because we want to show 4 items in a row (later we’ll define some media queries for displays that are equal/smaller than laptop size). What’s interesting here is the height. By using a height that is relative to the viewport width we can actually ensure that our grid item is close to a square. We don’t set 25vw here because we need to subtract some of the space so we set a value that looks good for a range of display widths. This combination works because our layout is close to full width. You could also play with viewport units for the flex-basis instead of percentages which will give you the same results considering that you subtract the other widths like item borders and paddings of the parent.
This is one possible technique and playing with it gives some insight on how useful viewport units can be. You could achieve this in different ways (think padding-hack) but today we want to play with viewport units and flexbox to create our fluid grid.

.item {
	position: relative;
	overflow: hidden;
	height: 22vw;
	flex: 1 0 25%;
	outline: none;
	border: 5px solid #2a2e39;
	border-width: 0 5px 10px;
	background: #2a2e39;

The image will be positioned absolutely and we’ll center it vertically by setting the top to 50% and pulling it up by half of its own height using a 3D transform. Additionally we’ll scale it up by default and set it to be slightly transparent. When hovering, we’ll make it opaque and remove the scaling, making it zoom out to its original dimensions.

.item__image {
	position: absolute;
	top: 50%;
	left: 0;
	min-height: 100%;
	width: 100%;
	opacity: 0.7;
	transition: transform 0.5s, opacity 0.5s;
	transform: translate3d(0, -50%, 0) scale3d(1.2, 1.2, 1);

.item:hover .item__image {
	opacity: 1;
	transform: translate3d(0, -50%, 0);

Note that you could also use a different color or gradient for the background of the item to create a unique look and mood for the images. If you’d like to give some visual feedback for users tabbing through the grid, you can also add a .item:focus style.
The title will also be positioned absolutely at the bottom of the element. When we hover the anchor, the title will fade out while moving down.

.item__title {
	position: absolute;
	bottom: 0;
	margin: 0;
	padding: 1em;
	color: #dbdbdb;
	font-size: 1.85em;
	font-family: "Playfair Display", Georgia, serif;
	line-height: 1;
	transition: transform 0.3s, opacity 0.3s;

.item:hover .item__title {
	opacity: 0;
	transform: translate3d(0, 20px, 0);

Finally, we want our grid to change the number of items in a row depending on the size of the screen. We also want to adjust the font-size for smaller viewports. Experimenting with different heights for the items we can make the layout look as we want and setting the right flex-basis will control the amount of items:

/* Media Queries */
@media screen and (max-width: 1440px) {
	.item {
		height: 30vw;
		-webkit-flex: 1 0 33.333%;
		flex: 1 0 33.333%;

@media screen and (max-width: 1000px) {
	.item {
		height: 45vw;
		-webkit-flex: 1 0 50%;
		flex: 1 0 50%;
	.intro__content {
		font-size: 0.85em;

@media screen and (max-width: 590px) {
	.item {
		height: 90vw;
		-webkit-flex: 1 0 100%;
		flex: 1 0 100%;

And that’s all the style. Next, let’s write a little script.

The JavaScript

In our script we want to control the adding and removing of the opening class and the active class for the trigger button. So, we’ll toggle the classes on click on the trigger button. Additionally, we want to make sure that the scrolling is disabled initially as this would make the content scroll under the intro header. Once we open the content, we allow scrolling. When we load the page, we also want to reset the scroll position so that the content starts at the top.

<script src="js/classie.js"></script>
	(function() {

		var container = document.getElementById( 'container' ),
			trigger = container.querySelector( 'button.trigger' );

		function toggleContent() {
			if( classie.has( container, 'container--open' ) ) {
				classie.remove( container, 'container--open' );
				classie.remove( trigger, 'trigger--active' );
				window.addEventListener( 'scroll', noscroll );
			else {
				classie.add( container, 'container--open' );
				classie.add( trigger, 'trigger--active' );
				window.removeEventListener( 'scroll', noscroll );

		function noscroll() {
			window.scrollTo( 0, 0 );

		// reset scrolling position
		document.body.scrollTop = document.documentElement.scrollTop = 0;

		// disable scrolling
		window.addEventListener( 'scroll', noscroll );

		trigger.addEventListener( 'click', toggleContent );


And that’s it! We’ve created a simple layout with an interesting effect while playing with some new properties 🙂

We really hope you enjoyed this tutorial and find it useful!

The second demo is an advanced variation, where we have multiple containers and a navigation. Check out the markup and code to see how it was augmented. The containers are moved by adding classes that will trigger CSS animations.

If you’d like to learn how to create a website with this layout, check out Velveteen Web Design’s video tutorial on YouTube.

Tagged with:

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

👾 Hey! Looking for the latest in frontend? Twice a week, we'll deliver the freshest frontend news, website inspo, cool code demos, videos and UI animations right to your inbox.

Zero fluff, all quality, to make your Mondays and Thursdays more creative!

Feedback 63

Comments are closed.
  1. Hi … all!

    I have successfully increased the number of thumbnail images present on the top left-hand corner of the Sliding Header from 3 to 6, along with changing the colors of both the sliding headers and grid items for all the 6 thumbnail images.

    But, I am unable to ‘click on’ and ‘link’ the 12 grid items located below the Sliding Headers to an external blueprint website such as ‘Background Slideshow’ even after deleting the line shown below:

    // For Demo purposes only (prevent jump on click)
    [] document.querySelectorAll(‘.items-wrap a’) ).forEach( function(el) { el.onclick = function() { return false; } } );

    Could anyone please assist me to link the 12 grid items located below the Sliding Headers to an external blueprint website such as ‘Background Slideshow’ by explaining the exact process of linking the grid items to an external blueprint website?

    I am eagerly awaiting your assistance, as I cannot wait to see the overall look and feel of my multi-colored parallax website when I can successfully link the 12 grid items to an external website 🙂 :-)!!

    I herewith thank everyone who worked hard using their creativity to innovate such a beautiful ‘Sliding Header Layout’ parallax website template and the accompanying tutorial:-) :-)!!



  2. Does anyone know how to make the slider autoplay for every 5 seconds? (without click the thumbnail buttons).

  3. Mary Lou.. Thank you for explaining things the way you do and breaking it down into ‘blocks’.. As a new web designer, I get the concepts, but I’ve been struggling with how to put all of the concepts together and make sense of it all. You nailed it, and explain things so well, and so clearly including the ‘whys’.. Thank you SO much!! I wish everyone broke it down, and explained things like you do!! I will be looking for more of your tutorials! : )