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!

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

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!

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

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 91

Comments are closed.
  1. Hi Mary Lou

    I have been looking into responsive web design quite a lot recently and have found numerous tutorials on the subject. The demonstration you have put together here is one of the more easy to follow tutorials that I have found. I will defiantly be putting the code to good use in the near future.

    Cathy

  2. Thank You so much for this lovely tutorial on such an amazing effect. 🙂

    I love Tympanus 😀

  3. Absolutely beautiful. The sliding effect for each page is fantastic; it’s amazing how much can be done with pure CSS these days.

  4. that is ok. it is art of css3. but i can’t understand that why i have to write too much css code instead of several line jquery for same thing.

    • jQuery is another resource needed to be loaded, and unless needed css3 always should be the preferred method.

    • jQuery is 92k minified, and around 32k gripped…
      92k of text is a whole lot of CSS, the code in this tutorial is nothing in comparison.
      You save 1 HTTP request also, and provide simple behaviour even if javascript is disabled.

      It’s not all chocolate and rainbows though, as this techniques doesn’t work in all modern browsers.

  5. It’s a nice, clean design but there are a few issues with it. On small mobile resolutions (240×320, 320×480) the menu text is very hard to read. The content isn’t scrollable, so the entire text can’t be read in case of overflow. The overflow: hidden is kinda problematic, but I understand why it’s there.

    • Yeah, other than being very beutifull this is quite problematic. I advise cheking out Ascensor.js and merging the good things about both of them.

    • Hi Karen, yes it should (and it does on my iPad). Which mobile Safari version do you have? Cheers, ML

  6. Amazing.. I want to create good design with funny look can you please suggest me.

  7. @fabian, @marc
    yes, i agree with you at some points. but a website has not only one element. if you load jquery file once and you use it for a lot of feature.
    i prefer every technology do own business. css build layout and gets elements and jquery play them and php adds dynamism.
    simple is better 🙂
    thanks

  8. D.A.M.N!

    Codrops you’re killin me with these tuts. Awesome stuff! I like the innovative stuff that’s
    happening here. I’m like a fiend on drugs hangin for every hit at the moment — or should I say drop!

    Awesome stuff, I’ve learnt alot from your tutorials.

  9. This looks great on the browser, however, on the iPhone it’s a different story. The scroll does not work. And if you’re not using iOS 5+, the position: fixed nor position: absolute will not work.

  10. WOW it’s like the parallax effect…
    usually awesome coding \m/
    ty ty ty

  11. Very interesting idea, thanks. We are working with responsive sites, but until some of your techniques are not used.

  12. Hi,
    How to change the icons brought from RaphaelIcons, and put some personnal icons please ?

  13. Is it possible to make a long page with this? Like a FAQ page that you have to scroll?

  14. This is great!

    My only gripe with it is that overflow is hidden. I understand why for y but surely it shouldn’t be hidden for x?

    Turning overflow on seems to play havoc with the links and the sections they anchor to. Not sure why

  15. Nice work! Anybody an idea why links in the content / st-panel are not working on iphone? They appear but the are not accessable! 🙁

  16. Really this is great. Love it. if possible make some variation for navigation on top or left / right ? btw nice work 🙂

  17. I’ve got one major question for this awesome tutorial. I’ve tried it, and it works fine at five element/panels, when I add more and even change the number to correspond and change the css to meet it, it’s broken. Also, when I add links to the paragraph tags it’s broken they don’t work at all, the layout still shows as it should, but the link is non-functional. How do I fix that?

    And one other thing, how can I implement a menu or an ordered/un-ordered list in the paragraph?. Please please help.

  18. Hi,
    I am using this code for at website that i am building.

    When I embed a Vimeo Video and try to press play, it jumps to another st-panel. Maybe it’s nothing but please advice me anyway.

    Best KLaus

  19. Hi: Guys
    I am beginner so please be gentle. 😉
    I have place the navigation on the top and that works fine however the pointer ‘small triangle’ that inform the user which page their viewing is the upside down (the point is pointing upwards) I would like to have it pointing downwards, Is this possible?.

    Thanks

  20. This doesn’t work for me in Firefox, only Chrome can handle this. Are there any modifications I can make to it?

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

    • 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;
      }

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

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

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

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

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

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

    • 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!

    • @ 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…