CSS-Only Responsive Layout with Smooth Transitions

A tutorial on how to create a 100% width and height smooth scrolling layout with CSS only. Using a radio button navigation and sibling combinators we will trigger transitions to the respective content panels, creating a "smooth scrolling" effect.

CSS-Only Responsive Layout with Smooth Transitions

View demo Download source

In this tutorial we will create a responsive 100% width/height layout with some smooth page transitions. The idea is to have some content panels and a navigation which will allow us to navigate between the panels. We’ll use radio buttons for the navigation and animate the content to the right position with a transition, creating a “smooth scrolling” effect. This layout idea could be useful for web pages or web apps where the content should be strictly the size of the screen (width and height). Note that this is, of course, highly experimental and just a proof-of-concept.

Please note: the result of this tutorial will only work as intended in browsers that support the respective CSS properties.

Note that we will exclude vendor prefixes in this tutorial. You will, of course, find them in the files.

The Markup

The structure will be consist of a main container with the class st-container which will contain the radio buttons and link, and the wrapper with the class st-scroll for the panels. Each panel will have some content elements:

<div class="st-container">
			
	<input type="radio" name="radio-set" checked="checked" id="st-control-1"/>
	<a href="#st-panel-1">Serendipity</a>
	
	<input type="radio" name="radio-set" id="st-control-2"/>
	<a href="#st-panel-2">Happiness</a>
	
	<input type="radio" name="radio-set" id="st-control-3"/>
	<a href="#st-panel-3">Tranquillity</a>
	
	<input type="radio" name="radio-set" id="st-control-4"/>
	<a href="#st-panel-4">Positivity</a>
	
	<input type="radio" name="radio-set" id="st-control-5"/>
	<a href="#st-panel-5">Passion</a>
	
	<div class="st-scroll">

		<section class="st-panel" id="st-panel-1">
			<div class="st-deco" data-icon="H"></div>
			<h2>Serendipity</h2>
			<p>Banksy adipisicing eiusmod banh mi sed...</p>
		</section>
		
		<section class="st-panel st-color" id="st-panel-2">
			<!-- ... -->
		</section>
		
		<!-- ... st-panel-3, st-panel-4, st-panel-5 -->

	</div><!-- // st-scroll -->
	
</div><!-- // st-container -->

What we want to do is basically move the panel wrapper by changing it’s top value and bringing the respective panel into the viewport. This we can do by selecting the sibling of a checked radio button, the st-scroll division, with the sibling combinator and target the correct panel inside. Because of this technique, we need to keep the radio buttons in the same level like the st-scroll and on top of the links (they will be invisible though, since we’ll give them 0 opacity). For being able to select the correct panel, we give IDs to them and to the radio buttons.

The reason why we use links and not, like usually, labels, is that we want to be able to create some kind of “fallback” for non-supportive browsers (sibling combinators don’t work in older browsers). The links have the href value of the panels’ IDs, so for the fallback, we’ll simply hide the radio buttons, making the links clickable which will make it possible to “jump” to the right panel.

I know that it might be desirable to have the respective hashtag in the URL once we click on an input but with this technique that we’ll use for this layout it is not (yet) possible with CSS-only (parent selectors would be really nice to have!).

OK, let’s style this thing!

The CSS

Now, how do we make this layout flexible and its panels exactly the size of the screen? The trick is to make the main container absolute with a width and height of 100% while setting the panels and their wrapper to position relative. But they will also have a width and height of 100%. This will make each panel be exactly the size of the screen (since the main container and the panel wrapper are) but allow an overflow of the content, stacking the panels in the classic way.

Since we’ll do the content navigation by animating the panel wrapper, we’ll set the body overflow to hidden:

body {
	overflow: hidden;
}

Let’s take a look at the main container’s style:

.st-container {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	font-family: 'Josefin Slab', 'Myriad Pro', Arial, sans-serif;
}

We’ll put the “navigation” at the bottom of the page by giving it a fixed position. Note that we are setting the same width and height for both, the input and the link. The idea is to overlay the radio button on the link elements so that they are clickable, but giving them 0 opacity so that they are not visible. Ans because of that it’s also important that we set the z-index of the radio buttons higher than the one of the link elements:

.st-container > input,
.st-container > a {
	position: fixed;
	bottom: 0px;
	width: 20%;
	cursor: pointer;
	font-size: 16px;
	height: 34px;
	line-height: 34px;
}

.st-container > input {
	opacity: 0;
	z-index: 1000;
}

.st-container > a {
	z-index: 10;
	font-weight: 700;
	background: #e23a6e;
	color: #fff;
	text-align: center;
	text-shadow: 1px 1px 1px rgba(151,24,64,0.2);
}

Since we are using percentages to spread the links and inputs across the width of the screen, we might get into some rounding trouble that will make some gaps appear. In order to hide that, well use a pseudo element that will be under the links and inputs. It will have the same background color like the link elements:

.st-container:before {
	content: '';
	position: fixed;
	width: 100%;
	height: 34px;
	background: #e23a6e;
	z-index: 9;
	bottom: 0;
}

Our links and inputs are still not positioned, so let’s give them their respective left values:

#st-control-1, #st-control-1 + a {
	left: 0;
}

#st-control-2, #st-control-2 + a {
	left: 20%;
}

#st-control-3, #st-control-3 + a {
	left: 40%;
}

#st-control-4, #st-control-4 + a {
	left: 60%;
}

#st-control-5, #st-control-5 + a {
	left: 80%;
}

As you can see, we are using the adjacent sibling selector to “reach” the direct sibling of an input which is the related link element.

Using the same principle, we will define a “selected” state for the link elements. Once we click on an input, we will give the sibling link element a different background color:


.st-container > input:checked + a,
.st-container > input:checked:hover + a{
	background: #821134;
}

Let’s also add a little triangle using the pseudo-class :after and give it the same color:

.st-container > input:checked + a:after,
.st-container > input:checked:hover + a:after{
	bottom: 100%;
	border: solid transparent;
	content: '';
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none;
	border-bottom-color: #821134;
	border-width: 20px;
	left: 50%;
	margin-left: -20px;
}

You can check out CSS Arrow, Please! if you want a quick way to create those arrows.

Let’s also define a hover state for the link element:

.st-container > input:hover + a{
	background: #AD244F;
}

.st-container > input:hover + a:after {
	border-bottom-color: #AD244F;
}

The wrapper for the panels and the panels will have relative position and we’ll give them a width and height of 100%. The panel wrapper will also get a top and left position of 0 while we don’t touch the the values for the panels (it will be auto).

The transition will be for animating the transform property value to the respective position:

.st-scroll,
.st-panel {
	position: relative;
	width: 100%;
	height: 100%;
}

.st-scroll {
	top: 0;
	left: 0;
	transition: all 0.6s ease-in-out;
	
	/* Let's enforce some hardware acceleration */
	-webkit-transform: translate3d(0, 0, 0);
	-webkit-backface-visibility: hidden;
}

.st-panel{
	background: #fff;
	overflow: hidden;
} 

Although I usually don’t add any vendor prefixed properties, I did wanted to leave these Webkit ones since they will help tremendously in creating a smooth experience.

Let’s define the positions for the st-scroll wrapper for each checked radio button. Since we know that every panel has a height of 100% we know the exact positions. We will use the transform property to translate the panel wrapper in the Y-dimension (up and down):

#st-control-1:checked ~ .st-scroll {
	transform: translateY(0%);
}
#st-control-2:checked ~ .st-scroll {
	transform: translateY(-100%);
}
#st-control-3:checked ~ .st-scroll {
	transform: translateY(-200%);
}
#st-control-4:checked ~ .st-scroll {
	transform: translateY(-300%);
}
#st-control-5:checked ~ .st-scroll {
	transform: translateY(-400%);
}

Now, let’s style the content elements. For the upper triangle with the icon we’ll simply rotate and translate the st-deco division. We’ll position it in the center top of the screen by setting the top to 0 and the left to 50% while giving it a left margin of minus half of it’s width. Translating it -50% will make only half of the box appear thus creating a triangle:

.st-deco{
	width: 200px;
	height: 200px;
	position: absolute;
	top: 0px;
	left: 50%;
	margin-left: -100px;
	background: #fa96b5;
	transform: translateY(-50%) rotate(45deg);
}

For the icon we’ll use the Raphaël Icon-Set via @font-face and the data-attribute/pseudo-class technique. The content of the pseudo-element :after will be the data-icon value that we’ve set in the HTML for that element. Note, that we need to rotate it back into the opposite direction of the parent element in order to have it back to “normal”:

[data-icon]:after {
    content: attr(data-icon);
    font-family: 'RaphaelIcons';
    color: #fff;
	text-shadow: 1px 1px 1px rgba(151,24,64,0.2);
	position: absolute;
	width: 200px;
	height: 200px;
	line-height: 200px;
	text-align: center;
	font-size: 90px;
	top: 50%;
	left: 50%;
	margin: -100px 0 0 -100px;
	transform: rotate(-45deg) translateY(25%);
}

The heading will be placed in the center of the screen with a negative top margin in order to “pull” it up a bit:

.st-panel h2 {
	color: #e23a6e;
	text-shadow: 1px 1px 1px rgba(151,24,64,0.2);
	position: absolute;
	font-size: 54px;
	font-weight: 900;
	width: 80%;
	left: 10%;
	text-align: center;
	line-height: 50px;
	margin: -70px 0 0 0;
	padding: 0;
	top: 50%;
	-webkit-backface-visibility: hidden;
}

Every time we click on an input, we want the respective heading to run an animation. It will animate a bit from the top and fade in at the same time. In order to select the correct heading, we will use the general sibling combinator:

#st-control-1:checked ~ .st-scroll #st-panel-1 h2,
#st-control-2:checked ~ .st-scroll #st-panel-2 h2,
#st-control-3:checked ~ .st-scroll #st-panel-3 h2,
#st-control-4:checked ~ .st-scroll #st-panel-4 h2,
#st-control-5:checked ~ .st-scroll #st-panel-5 h2{
	animation: moveDown 0.6s ease-in-out 0.2s backwards;
}

@keyframes moveDown{
	0% { 
		transform: translateY(-40px); 
		opacity: 0;
	}
	100% { 
		transform: translateY(0px);  
		opacity: 1;
	}
}

The paragraph will have the following style:

.st-panel p {
	position: absolute;
	text-align: center;
	font-size: 16px;
	line-height: 22px;
	color: #8b8b8b;
	z-index: 2;
	padding: 0;
	width: 50%;
	left: 25%;
	top: 50%;
	margin: 10px 0 0 0;
	-webkit-backface-visibility: hidden;
}

While the heading of a panel will move down, the paragraph will move up:

#st-control-1:checked ~ .st-scroll #st-panel-1 p,
#st-control-2:checked ~ .st-scroll #st-panel-2 p,
#st-control-3:checked ~ .st-scroll #st-panel-3 p,
#st-control-4:checked ~ .st-scroll #st-panel-4 p,
#st-control-5:checked ~ .st-scroll #st-panel-5 p{
	animation: moveUp 0.6s ease-in-out 0.2s backwards;
}

@keyframes moveUp{
	0% { 
		transform: translateY(40px); 
		opacity: 0;
	}
	100% { 
		transform: translateY(0px);  
		opacity: 1;
	}
}

In order to make out layout a bit more fun, we’ll add a color class and “invert” the colors for those panels and its content elements:

/* Colored sections */

.st-color,
.st-deco{
	background: #fa96b5;
}
.st-color [data-icon]:after {
	color: #fa96b5;
}
.st-color .st-deco {
	background: #fff;
}
.st-color h2 {
	color: #fff;
	text-shadow: 1px 1px 1px rgba(0,0,0,0.1);
} 
.st-color p {
	color: rgba(255,255,255,0.8);
}

Last, but not least, we will add some media queries to control the position and font size of the elements for smaller screens:

@media screen and (max-width: 520px) {
	.st-panel h2 {
		font-size: 42px;
	}
	
	.st-panel p {
		width: 90%;
		left: 5%;
		margin-top: 0;
	}
	
	.st-container > a {
		font-size: 13px;
	}
}

@media screen and (max-width: 360px) {
	.st-container > a {
		font-size: 10px;
	}
	
	.st-deco{
		width: 120px;
		height: 120px;
		margin-left: -60px;
	}
	
	[data-icon]:after {
		font-size: 60px;
		transform: rotate(-45deg) translateY(15%);
	}
}

For older browsers that don’t support some of the selectors we want to fall back to the classic “target jump”. We can do that by altering some of the style (simple.css). In particular, we will set the overflow of the body to “auto” and hide the inputs, making the link elements clickable (as href they have the ID of the respective panels):

body {
	overflow: auto;
}
.st-container > input{
	display: none;
}

And that’s all! I hope you enjoyed this tutorial and find it inspiring!

View demo Download source

Previous:
Next:

Tagged with:

ML is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

View all contributions by

Website: http://www.codrops.com

Related Articles

CSS Reference

Learn about all important CSS properties from the basics with our extensive and easy-to-read CSS Reference.

It doesn't matter if you are a beginner or intermediate, start learning CSS now.

Feedback 91

Comments are closed.
  1. 1

    Thank you so much for this!
    do you think it’s possible to use an image for the background of the pages (or panels)?
    If yes, where should I put that code?

    • 2

      Naiara, you can add this CSS code to add background image:

      #st-panel-1 {
      background-image:url(‘images/background.png’);
      background-size: cover;
      background-position:50% 50%;
      background-repeat:no-repeat;
      }

      #st-panel-5 {
      background-image:url(‘images/bg.png’);
      background-size: cover;
      background-position:50% 50%;
      background-repeat:no-repeat;
      }

  2. 3

    Hi there! Thanks for this marvelous tutorial/example!!

    I’ve found a rather odd bug on the functionality under safari( mobile and os version).

    When I try to create any <a> tag, they simply don’t work as they should, they show up but if they are not in st-panel-1, they will only show us as text or if they have img inside they will show as img not as a clickable anchor tag! The strangest thing is that in any other supported browser it works just fine!

    Any ideas?

  3. 6

    I was wondering if there is possibility to do main menu at first panel?
    I was tryin to use those inputs+a and didnt work out they do not “link”/”scroll” to other panels..
    any1 have idea?

    help would be very appreciate

  4. 7

    Beautiful!

    Just a question (I’m a novice, please, don’t kill me!): how can I embed the amazing layout? Any idea?

  5. 8

    Thank you! I encountered the same issue of Pedro. With Safari any <a> tag don’t work but if they are not in st-panel-1. It’s a problem of z-index but how to solve it?

  6. 12

    Really nice, but useless as long as there is no solution to the link problem in Safari… Too bad.

    I hope everyone make lot of browsers tests, or they will have some surprises…

  7. 13

    And by the way, you SHOULD warn people of this in the tutorial. The problem is report from july 14th… Long time.

  8. 14

    Ok, i’ve found the problem on Safari for the links :

    In .st-panel h2 {…}, it’s the combination of these two elements :
    position : absollute;
    -webkit-backface-visibility: hidden;

    Erase one or the other, and the problem disappear. Bad new, you may now encounter some visual problem, like flashes when the panels moving. I think it’s possible to overpass this with some work and maybe modification of the markup.

    • 15

      Hi, i solved it! i had to change a bit the css code to make it work.
      There was a problem with the z-index and with the
      positioning of elements.
      Here is my website:
      http://www.etnopsichiatria.net
      The site is a work in progress. There is still a lot do do, but you can check the code for an help.
      Anyway, thank you very much Mary Lou!

    • 16

      @ Fabrizio >>
      I’ve made it works too, without any flashes.

      But it’s strange, if i made in my CSS the same modifications you’ve made in your, mine doesn’t work anymore : the links are not accessible again ! Weird…

  9. 18

    How can i link to the various pages using a normal link WITHIN the st-scroll ?
    If i just do a #st-panel-3 it’ll jump down with no animation, and then ruin the entire effect!

    Help please :)

  10. 20

    where are the images inside the triangles at the top of the pages coming from and can i change them? i.e. tshirt, smiley face etc. thanks, bryan

  11. 21

    This template is brilliant, I’m going to use it for my next design, thanks a lot for sharing!

  12. 22

    which style sheet can i make the changes to the colors of the site and the icons that are appearing at the top of each page inside the triangle? thanks

  13. 23

    nice template!
    The biggest problem i have is that if viewers refresh the page for some reason, they return to “page1” .

  14. 24

    Awesome Mary:), amazing work, but can you fix it for safari ios browsers? I donno why but under homepage all pages doesnt locked for classic scrolling. I using this for my new nice project for kids so its so important lookin the same as in classic browser. Thanks a lot!

  15. 25

    Hi! This is a very nice template!
    Only one question: how can i chage the inputs for only two buttons “back” and “next”? It is possible? I would put a lot of st-panels and it will be impossible to make every st-controls…

    Thanks and apologize my english…

    Carles

    • 26

      Great Idea.

      i’m also trying to insert this kind of button. If I get it I post here

      Regards,

  16. 27

    Have anybody implemented FlowPlayer on this template working in firefox 15? It’s invisible. Ie9 and Chrome OK

  17. 28

    Looks like -moz-transform:translate animation does not implemented in Ubuntu Firefox.

  18. 29

    Hi, Lou:

    It seems i can run your demo with fancy effect in my firefox, but it does not work n my ie9. i read your comments, and i says it should work in ie9. what problem would it be ?

    Thx,

    APK

Comments are closed.