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

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!

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 up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 91

Comments are closed.
  1. 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 🙂

  2. 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

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

  4. 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

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

  6. 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!

  7. 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

    • Great Idea.

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

      Regards,

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

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

  10. 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

  11. I have got a question, how am i suppose to scroll the content, if i put the radio buttons in a separate div box, like this:

    div class=”checkboxex”
    input type=”radio” name=”radio-set” class=”rd2″ value=”Helo world” /
    input type=”radio” name=”radio-set” class=”rd3″ value=”Helo world” /
    /div
    /code

    what will this line become?

    #st-control-5:checked ~ .st-scroll
    {
    }

  12. Hi Mary Lou.

    Thanks for the tutorials. I made a test run on this template, and somehow back in the Europe and Us some people could not see the transition correctly and also the (viewing size) size on the page. Please help…
    http://www.illusionsign.com

    what am I doing wrong?

  13. Very interesting technic, but how we can scroll using a link outside navigation bar?

    inside my page content i have

    title

    Text Linnk check this out

    how can i scroll to section-5 ????

  14. Hi,
    What a great job you did, I find it really cool.
    But it seems that there is a difference between the .zip file and the demo page when you check it from ipad and iphone :
    On the demo here, the nav sticks the bottom of the screen. Unfortunately, i don’t get the same result when i make a test on ipad and iphone with the elements in the zip file : the page looks bigger and the content is hidden, we need to slide down to see the elements in the foot.
    you can see what i mean on iphone or ipad HERE. this is just an upload of the elements in the zip file. The result is really different from what we expect to see as we can see on the demo page…
    Any idea ?

  15. Ok ok ok… I understood the problem.
    Forget about my previous message…
    On iphone and Ipad, if you make an anonymous redirection to a sub folder of an existant website (like i did for a website i hosted in a folder of my old website ), it creates a framework that split your content into 2 frames (with only 1 visible) : and the result is that it finally hides the content of the page (only on mobiles)
    I don’t know if i’m very clear, my english is not perfect… But you can see the difference here between the direct URL and the hidden redirection :
    direct URL
    anonymous redirection

    interesting, isn’t it ?

  16. Hi Mary Lou,

    I really like the navigation with sliding content. After studying the CSS I would like to use it,
    But when seen on an iPad, there still is the possibiliyy to scroll by finger and after that
    the system is messed up.
    I tried loading it in an Iframe, in which the content can not be scrolled in this way, but it
    The dimensions are nor correct anymore.
    http://www.bart-merkelbach.com/pad

    Kind regards, bart

  17. This is really great…look awesome but i wanted to ask that if pages can be slide by left or right ?

  18. Question: If I wanted to switch on the color on the NAV for an image, how and where would I change that?

  19. Hello,

    What a truly wonderful idea. I just wanted to ask a question; I would like to use this idea, but I have more than 5 items to navigate through, so I was asking if there was a way to make the bottom navigation to have left and right arrow buttons to navigate to the rest of the content. If that was possible could you assist me to achieve this concept.

    Thank you!