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. The one comment I’d make here is that it relies on mystery meat action to open the remainder of the page content. I would think a vertical scroll trigger combined with a “more” or directional arrow might provide some additional clue to the possible actions on the page.

  2. Hello, thank you very much for sharing your very nice works Mary Lou.

    This time download link links to… 404 🙂

  3. Wonderful transition! And it looks great on mobile as well!
    I tried downloading the source but it is giving me and error 404.

    Help please!?

  4. wow…amazing…elegant…typically Mary Lou. Thank you for yet another great design idea.

  5. This is seriously awesome!

    I would add body {overflow: hidden} to the css, just to hide the scrollbar for absolute perfection. 😀

    • A very nice idea ! As usual your work.
      Merry Christmas to you Mary Lou and Happy New Year !

  6. Total noob here 🙂

    Is there an easy way to have the circular buttons align vertically (one below the other) rather than horizontally?

  7. I can’t make the grid items link to anything. I update the <a> tag but when I go to test it and click on the image, nothing happens.
    Feel like I’m missing something really obvious…


    • Hi Matt,
      you need to remove the following line:
      // For Demo purposes only (prevent jump on click) [] document.querySelectorAll('.items-wrap a') ).forEach( function(el) { el.onclick = function() { return false; } } );


  8. I am amatuer to web development and design and your website help me a lot to understand every aspect of HTML5 and CSS3.

    Thanks a lot


  9. This design is truly awesome! But unfortunately I hit a bump with the trigger button, there’s nothing happens when I click on the button, any ideas how to deal with it? Thanks

  10. Sorry for the novice question – “How to change the size of the buttons (1, 2, 3) in the navigation?”

  11. I get a weird clipping issue when I create a container that doesn’t have any items in it (like an intro screen) but is still a part of the navigation. It seems that when the items in the items-wrap section are low enough that it doesn’t create a scroll bar on the right it clips the background and the text disappears. Putting a min height works but that seems to be a hack.

  12. the toggle button nearly wents outside the viewport on the right while viewing this with TFF 24 (FF).

    • Agreed.
      I absolutely love this layout, but some its functions are buggy.
      Sometimes the toggle button is outside of the viewport, images scroll to the right horizontally instead of vertically, and it would be nice if the thumbnails and header image re-sized for mobile.

      Any help for fixing these bugs so that the website translates flawlessly at multiple resolutions? Anyone?

  13. This is truly awesome work as always. I have been trying to make the grids into links that can open new pages but it doesn’t work and the title text on each grid is unclickable . Can someone please help as I plan to use this as a catalog page. Thank you

  14. ma’am you have some really amazing designs and i have been following your work on codrops (by following i mean in a total admirer, non-stalking way ^-^) n i really really like your work with your effects on headers and menus and backgrounds…

    I am doing some work on html and css and beginning on js myself and would really appreciate some tips or like pointers you would have for an aspiring front-end designer… thankyou

  15. These tutorials really make me understand all the amazing web technologies we have such as new css/js methods and using svg and so on.
    Great tutorials from you as usual Mary and very practical. Thank you so much!

  16. Hi,

    All your tutorials at codrops are great!

    Is there any reason you couldn’t just give the background image in the CSS instead of using besides not being able to give the “alt” information? I’m trying this currently so that I can use the image as a background and have it cover the page (shrink and grow correctly) before the transform.


  17. I can’t get the links in the grid to work, a quick check and the same issue exists in your published demo, how do I fix that?

  18. Same thing as Jess. Links in the grid do not function. What is going on? What can be done to fix this?

  19. @Kevin @jess You need to remove the following line:

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


  20. For anyone that is having problems with links not working in the “items-wrap” section. Remove the bottom line of code:

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

  21. Thanks Pedro Botelho. You told us just as I figured it out. Thanks for the help bud. much appreciated

  22. Is there any way I can change the transition between the different containers?? I would like some fading or just a slide transition

  23. I have only just discovered this site, and I absolutely love it, keep up the good work codrops 🙂

  24. I seem to be having trouble with safari and chrome on ios. flex box doesn’t seem to be recognized, even with the webkit prefix. Any suggestions?

  25. I like the Demo 2 (multi) effect but to be honest I wish I can follow only a markup and code for that one only, I am new to Css, I hope I can receive some help with this. Thanks in advance.

  26. As someone who uses a lot of commercial templates and then amends the code, this site has given me some great resources on getting more hands on with my coding

  27. I adore your work. Totally awesome.

    I checked the multi version both on desktop and my android phone. On desktop Explorer won’t render the grid as it is intended. Could fit only one cell short per row. ie 2 cells on 1366 px and 3 cells on 1920 px display width while other browsers do 3 cells and 4 cells respectively.

    On mobile only Chrome and Dolphin did it properly (as i see it on desktop). However both failed smooth page transition (probably my device’s lack of number crunching capacity has an effect on this) Dolphin renders the page transitions interestingly though.

    Just my five cents and thank you again for your huge contribution to my passion to learn more about these.

  28. Hey, I am trying to use the fancybox ( (or any other modal popup script) script for the images but every time I apply the fancyclass tag to images (that have the “.item” class), the grid gets ruined and every image then overlaps one another and are enlarged to fit the whole width of the screen.. any suggestions on how I can fix this? thanks.

  29. Wow! I knew I had a lot to take in being a newbie, but I am simply blown away by your skill and coding know how.
    Simply brilliant and of course bookmarked

    Thank you for sharing your expertise

  30. Hi Mary Lou

    really amazing designs demo I really like your work can I use for my personal projects web site?


  31. Great work, thank you!

    Does anyone know how to make it trigger by scrolling instead of clicking the trigger button?

  32. Even after removing the single demo line in the index, I still can’t get this to work.
    What are the images in the grid supposed to do? Am I missing something?

  33. Hi Mary Lou. Thanks so much for this tutorial. It is really awesome.
    I am tryign to use it for my website (i have thanked you and copyrite it to you in my footer. (I hope this is ok?)
    but i am having an issue – on the grid i would like the image to link to clients portfolio but i cannot get the button to work.

    Would you be able to give me some advice – i am really new at web and i do fumble through code until it works, having said that i just cannot get his to work at all. Please could you help me with a bit of advice?

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

  35. Hi, your work inspires me a lot. I hope some day I can create something amazing like this.

  36. I just love Mary Lou. I don’t like peppercorns though. I got one stuck in my nose when I was a kid. So, I just can’t stand the smell of them now.

  37. I am very new to programming and want to completely remake my present website using your sliding header. How do I get the grid images to work as links to further pages?
    I would appreciate your reply.
    Thanks, David.

  38. Great demo but need little help.

    I am trying to make number 1,2,3 (in multi demo) to fade in an out like picture in circle. And it was success. But can make that number stay visible (opaque) like picture when on active page. Any suggestion?

    And one more thing. Grid items. When set to have 6 or 8 in u row instead of 4 (.item -> flex: 1 0 15%; height: 15vw; -webkit-flex: 1 0 15%;) strange behavior in chrome (cutting bottom of background picture) and mozilla (pictures in grid blink on first hover). Any clues?

    Thanks in advance.