3D Restaurant Menu Concept

A responsive 3D menu concept for a restaurant website. The idea is to show the menu as a folded flyer and unfold it in order to show the menu items.

3D Restaurant Menu Concept

Applying CSS 3D transforms to components can bring some more realism to normally flat web elements. We’ve experimented with some simple concepts for restaurant websites and came up with a 3D folded menu (a real menu, not a “web” menu). The result is a restaurant website template where the menu will open by unfolding. Clicking the linked items will reveal a modal overlay which contains some more info.

Since this “flyer” structure requires a decent amount of space, and, although we will make this responsive, we’ll add a media query for smaller screens where everything will fall back to a simplified stacked structure. The same will be applied for browsers that don’t support 3D transforms.

The thumbnails and delicious recipes are by Michael Natkin from herbivoracious.com (all the ones that have a link).

Please note: this only works as intended in browsers that support the respective CSS properties.

Alright, so let’s get started!

The CSS will not contain any vendor prefixes, but you will find them in the files.

The Markup

Our structure will consist of a main container with the class “rm-container” and we’ll have a wrapper inside. The wrapper will contain the three panels. Initially, we only want to see the cover of the folded menu; that is the division with the class “rm-cover”. The last panel is the one with the class “rm-right”. The middle panel is the one we see in the middle when we open the menu:

<div id="rm-container" class="rm-container">

	<div class="rm-wrapper">
		<div class="rm-cover"></div>
		<div class="rm-middle"></div>
		<div class="rm-right"></div>
	</div><!-- /rm-wrapper -->

</div><!-- /rm-container -->

Inside of the rm-cover and the rm-right division, we will have a front- and a backface:

<div class="rm-front">

	<div class="rm-content">
		<!-- Some content -->
	</div><!-- /rm-content -->
	
</div><!-- /rm-front -->

<div class="rm-back">

	<div class="rm-content">
		<!-- Some content -->
	</div><!-- /rm-content -->

	<div class="rm-overlay"></div>

</div><!-- /rm-back -->

We’ll also add an element for an overlay gradient that will give the menu parts a bit more realism. Note that the front will be empty in the last element.

The middle part of the menu will simply have another wrapper inside, no front or back part:

<div class="rm-inner">

	<div class="rm-content">
		<!-- Some content -->
	</div><!-- /rm-content -->

	<div class="rm-overlay"></div>

</div><!-- /rm-inner -->

We will never see the back part of this part of the menu, so we won’t need that structure.

The content will consist of some text elements, like definition lists for the dishes and headlines:

<div class="rm-content">

	<h4>Appetizers</h4>

	<dl>
		<dt>Bella's Artichokes</dt>
		<dd>Roasted artichokes with chipotle aioli and cream cheese</dd>

		<dt><a href="http://herbivoracious.com/2011/11/crostini-with-young-pecorino-grilled-figs-and-arugula-mint-pesto-recipe.html" class="rm-viewdetails" data-thumb="images/1.jpg">Green Love Crostini</a></dt>
		<dd>Crostini with young pecorino, grilled figs and arugula & mint pesto</dd>
		
		<dt>Focaccia di Carciofi</dt>
		<dd>Artichoke focaccia with fresh thyme</dd>

		<!-- ... -->
	</dl>

	<h4>Salads & More</h4>
	
	<dl>
		<!-- ... -->
	</dl>

</div><!-- /rm-content -->

Notice the anchor with the class “rm-viewdetails” and the data attribute “data-thumb”. We will use this as the content for a modal overlay box that will appear when clicking on the link:

<div class="rm-modal">
	<div style="background-image: url(images/1.jpg)" class="rm-thumb"></div>
	<h5>Green Love Crostini</h5>
	<p>Crostini with young pecorino, grilled figs and arugula & mint pesto</p>
	<a href="http://herbivoracious.com/2011/11/crostini-with-young-pecorino-grilled-figs-and-arugula-mint-pesto-recipe.html">See the recipe</a>
	<span class="rm-close-modal">x</span>
</div>

It will contain the same headline and description that we get from the followed dd element (definition description), the thumbnail that we get from the data-attribute and the link.

And that’s the HTML, now, let’s take a look at the style.

Note that we won’t go into the styling of the text elements or inner content parts. Instead we will focus on the 3D structure, transition classes and media queries.

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

We want to make the whole thing fluid, so we will give the main container a percentage width, and, since we want some 3D awesomeness, some perspective:

.rm-container {
	width: 33%;
	height: 700px;
	max-width: 370px;
	margin: 0 auto 40px auto;
	position: relative;
	perspective: 1600px;
	color: #2a323f;
}

The wrapper and its children divisions (the cover, middle part and right part) will all have the full width and height and we’ll position them absolutely:

.rm-wrapper,
.rm-wrapper > div {
	width: 100%;
	height: 100%;
	left: 0;
	top: 0;
	position: absolute;
	text-align: center;
	transform-style: preserve-3d;
}

Since we will be working in 3D perspective, we want the transform style of these elements to be “preserve-3d”.

The cover needs a higher z-index than all the other parts and we’ll set the transform origin to be on the left edge (left center). The transition delay is for when we close the menu. The transition to the open state will contain another value, but let’s look at that a bit later:

.rm-wrapper .rm-cover {
	z-index: 100;
	transform-origin: 0% 50%;
	transition-delay: 0.2s;
}

The middle part will have the lowest z-index of all three parts and we’ll add a subtle box shadow:

.rm-wrapper .rm-middle {
	z-index: 50;
	box-shadow: 0 4px 10px rgba(0,0,0,0.7);
}

The right most part will have a z-index that is higher than the middle part one but lower than the cover one. The transform origin will be on the right edge (right center) and there will be no transition delay when we close the menu:

.rm-wrapper .rm-right {
	z-index: 60;
	transform-origin: 100% 50%;
	transition-delay: 0s;
}

The inner divisions which are the ones with the classes “rm-front”, “rm-back” and “rm-inner” will have a paper texture as background and we’ll add a inset box shadow that will simulate a multi-line decorative border:

.rm-wrapper > div > div {
	background: #fff url(../images/white_paperboard.jpg);
	width: 100%;
	height: 100%;
	position: absolute;
	padding: 30px;
	box-shadow: 
		inset 0 0 0 16px #fff, 
		inset 0 0 0 17px #e6b741, 
		inset 0 0 0 18px #fff, 
		inset 0 0 0 19px #e6b741, 
		inset 0 0 0 20px #fff, 
		inset 0 0 0 21px #e6b741;
}

Now, let’s add the important 3D properties. The front and the back face need backface-visibility set to hidden:

.rm-container .rm-front,
.rm-container .rm-back {
	backface-visibility: hidden;
}

Let’s rotate the backfaces so that they are behind the front parts:

.rm-container .rm-back {
	transform: rotateY(-180deg);
}

The overlay will generally be an almost transparent gradient from light to a bit darker:

.rm-overlay {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	pointer-events: none;
	background: linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,0.05) 100%);
}

For the middle overlay we will turn the gradient to the other side:

.rm-middle .rm-overlay {
	background: linear-gradient(to right, rgba(0,0,0,0) 64%, rgba(0,0,0,0.05) 100%);
}

Let’s add some padding the the content divisions:

.rm-content {
	padding: 20px;
}

The modal layer will be invisible: we’ll set the opacity to 0, the pointer-event to none and we’ll translate it on the Z-axis:

.rm-modal {
	position: absolute;
	z-index: 10000;
	width: 120%;
	margin-left: -10%;
	top: 50%;
	padding: 40px;
	background: #fff url(../images/white_paperboard.jpg);
	box-shadow: 
		inset 0 0 0 16px #fff, 
		inset 0 0 0 17px #e6b741, 
		inset 0 0 0 18px #fff, 
		inset 0 0 0 19px #e6b741, 
		inset 0 0 0 20px #fff, 
		inset 0 0 0 21px #e6b741,
		0 4px 20px rgba(0,0,0,0.4);
	opacity: 0;
	pointer-events: none;
	transform: translateZ(1000px);
}

The idea is to reveal the modal when we click on one of the links on the menu. We’ll scale the menu down and make the modal appear from “above”. This concept is inspired by Hakim El Hattab’s modal concept Avgrund.

Let’s add some transitions and define some classes for opening the menu.

First, we’ll give a transition to the wrapper (for scaling it when opening the modal) and to the children of the wrapper, i.e. our three menu parts (for animating the “opening”/rotation):

.rm-wrapper, .rm-wrapper > div { transition: all 0.6s ease-in-out; } The modal will also have a transition for the transformation (to scale down) and its opacity:
.rm-modal {
	transition: 
		transform 0.6s ease-in-out,
		opacity 0.6s ease-in-out;
}

The idea is to add a class called “rm-open” which will trigger the menu to unfold. This class will be added with JavaScript when we click on the “View the menu” link.

When adding the class, we will define what will happen to all the elements when we open the menu.

The children of the wrapper will all get a box shadow:

.rm-container.rm-open .rm-wrapper > div {
	box-shadow: 0 4px 5px -3px rgba(0,0,0,0.6);
}

The cover will rotate (without any delay) -180 degrees on the Y-axis. Since we defined the transform origin to be on the left center, it will open to the left:

.rm-container.rm-open .rm-cover {
	transform: rotateY(-180deg);
	transition-delay: 0s;
}

The right part will rotate 180 degrees, but here we will add a transition delay of 0.2s because we want the cover to open a bit first:

.rm-container.rm-open .rm-right {
	transform: rotateY(180deg);
	transition-delay: 0.2s;
}

When we click on one of the menu item that is an anchor, we will add another class called “rm-in” to the container and move the wrapper down on the Z-axis:

.rm-container.rm-in .rm-wrapper {
	transform: translateZ(-500px);
}

The cover and the right part will be rotated a bit more to the inside:

.rm-container.rm-in .rm-cover {
	transform: rotateY(-150deg);
}

.rm-container.rm-in .rm-right {
	transform: rotateY(150deg);
}

We need to set the transition delay to 0 seconds for this case:

.rm-container.rm-in .rm-cover, 
.rm-container.rm-in .rm-right,
.rm-container.rm-nodelay .rm-right {
	transition-delay: 0s;
}

The class “rm-nodelay” is an extra class that we’ll need for the right menu part when we close the modal. Remember, it had a transition delay for when we open the menu.

The modal will appear by translating it to 0px on the Z-axis and increasing the opacity to 1:

.rm-container.rm-in .rm-modal {
	transform: translateZ(0px);
	opacity: 1;
	pointer-events: auto;
}

Let’s define some media queries for smaller screens and devices. The first media query will simply resize the wrapper:

@media screen and (max-width: 1110px) {
	.rm-container {
		height: 800px;
	}
}

Since we made the wrapper fluid, it will need a larger height in order to contain the text.

From 960 pixels on, we no longer want to show the menu as a folded flyer but as normal, scrollable content.
For that, we’ll simply set the height of the wrapper to auto, give it 100% of width and define a maximum width:

@media screen and (max-width: 960px) {

	.rm-container {
		width: 100%;
		height: auto;
		max-width: 460px;
	}

All the inner divisions will no longer be absolute, but relative and we’ll give them 100% of width and set the height to auto:

	.rm-wrapper, 
	.rm-wrapper > div,
	.rm-wrapper > div > div {
		position: relative;
		width: 100%;
		height: auto;
	}

The content divisions will need some margin:

	.rm-wrapper > div > div{
		margin-bottom: 10px;
		box-shadow: 
			inset 0 0 0 16px #fff, 
			inset 0 0 0 17px #e6b741, 
			inset 0 0 0 18px #fff, 
			inset 0 0 0 19px #e6b741, 
			inset 0 0 0 20px #fff, 
			inset 0 0 0 21px #e6b741,
			0 3px 5px rgba(0,0,0,0.2);
	}

Let’s remove the rotations:

	.rm-container .rm-back,
	.rm-container.rm-open .rm-cover,
	.rm-container.rm-open .rm-right {
		transform: rotateY(0deg);
	}

We don’t need the overlays any more:

	.rm-overlay, .rm-middle .rm-overlay {
		display: none;
	}

And we set the position of the modal to fixed, so that it sticks on top when we scroll:

	.rm-container .rm-modal {
		position: fixed;
		width: 80%;
		top: 100px;
		left: 50%;
		margin: 0 0 0 -40%;
		transform: translateZ(0px);
		transition: opacity 0.6s ease-in-out 0s;
	}

When we click on a menu item link and the modal appears we won’t rotate anything any more:

	.rm-container.rm-in .rm-cover,
	.rm-container.rm-in .rm-right,
	.rm-container.rm-in .rm-wrapper {
		transform: rotateY(0deg);
		transition-delay: 0s;
	}
}

And that’s all the important style. For browsers that don’t support 3D transforms, we will use almost the same styling like we do for this last media query. Since we use Modernizr, we’ll just add those classes again, preceded by .no-csstransforms3d (which will be added to the body).

Now, let’s add some lines of JavaScript.

The JavaScript

We will start by caching some elements:

	// main container
var $container = $( '#rm-container' ),						
	// the cover, middle and right panels
	$cover = $container.find( 'div.rm-cover' ),
	$middle = $container.find( 'div.rm-middle' ),
	$right = $container.find( 'div.rm-right' ),
	// open and close elements
	$open = $cover.find('a.rm-button-open'),
	$close = $right.find('span.rm-close'),
	// the links for each recipe (photo and details)
	$details = $container.find( 'a.rm-viewdetails' ),

The events for opening/closing the menu and also for showing each item’s details (modal) are initialized:

init = function() {

		initEvents();

	},
initEvents = function() {

	$open.on( 'click', function( event ) {

		openMenu();
		return false;

	} );

	$close.on( 'click', function( event ) {

		closeMenu();
		return false;

	} );

	$details.on( 'click', function( event ) {

		$container.removeClass( 'rm-in' ).children( 'div.rm-modal' ).remove();
		viewDetails( $( this ) );
		return false;

	} );
	
},

To open/close the menu we will be adding/removing the class ‘rm-open’ from the $container. Remember, this is the class where the transitions are defined.

Note that on close, we are also removing the classes ‘rm-nodelay’ and ‘rm-in’. These are classes that are added if we click to see the menu item’s details.

openMenu = function() {

	$container.addClass( 'rm-open' );

},
closeMenu = function() {

	$container.removeClass( 'rm-open rm-nodelay rm-in' );

},

Finally, if we click to see an item’s details, a modal box will be shown with the image and the text for that item. We translate the main container on the Z-axis (and adjust the top margin in order to center it), and slightly rotate the left and right panels:

viewDetails = function( recipe ) {

	var title = recipe.text(),
		img = recipe.data( 'thumb' ),
		description = recipe.parent().next().text(),
		url = recipe.attr( 'href' );

	var $modal = $( '
' + title + '
' + description + ' See the recipex
' ); $modal.appendTo( $container ); var h = $modal.outerHeight( true ); $modal.css( 'margin-top', -h / 2 ); setTimeout( function() { $container.addClass( 'rm-in rm-nodelay' ); $modal.find( 'span.rm-close-modal' ).on( 'click', function() { $container.removeClass( 'rm-in' ); } ); }, 0 ); };

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

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 42

Comments are closed.
  1. Man, every restaurant site should have this… this should be required. No more garbage pdf view menus!

  2. Wow ! Impressive as always Mary Lou 🙂 Thanks a lot.
    Just one question : why do you use setTimeout in the viewDetails function ?
    Thanks again.

  3. This is impressive. I have to do a website for a restaurant anyway… I could include this very nice effect 🙂

    Thanks for sharing that 🙂

  4. Nice concept, looks amazing.

    Only usability tip/request i have is to increase the close buttons to make it much easier to use (or just make clicking on/next to the card make the view go back). Also i don’t really understand why one would want to see the recipe. Perhaps it would be better to view ingredients (perhaps somebody doesn’t want/like certain ingredients).

    Keep up the good work!

  5. Usually I don’t post comments after seeing a good post. But I now I can’t stop me from doing it. Hats off man!! Mind blowing one. God bless you!

    • Hi,
      my congratulation for the great job!
      I’ve some problem to run 3d menù in IE8, suggestions?!

      Thanks

  6. I tried this using wamp and it works a treat, but ie8 isn’t working at all. I saw the caveats re browser compatibility, but are there any workarounds short of deleting Internet Explorer from my PC?? 😉

    Thanks for the great tutes, i really am learning a great deal from you. So appreciative.

    D

    • Apologies, it must be at my end as i ran your demo in ie8 without a problem. Sorry for that. Thanks again for a great tute!!

  7. Wow. This is a really nice concept.

    Though i’m experiencing a problem setting the background-image for the body to
    body { background: url("../image.jpg") no-repeat scroll right bottom; }
    It looks like the image’s area below the bottom edge of the the menu would be cut of.

    Using firebug it seams that he body would be cut off right after the menu page(s).

    Maybe someone got a solution for me. Thanks!

  8. don’t know if u ran into IE10 strange behavior of the turning pages that shows the reversed front pages… any workaround?

  9. Oh my god! Thank you so much for this awsome job. I’m really glad. Everything is clear and easy to understand even for begginers as me. Thanks again for share you knowledge. Greets from Seville, Spain.

  10. Is it possible to accomplish the same effect on the single div? Example: not having 3 or 4 separate div boxes but have a solid div that has text inside of it in column layout: main column has some content and right column has some content, that might have different overall height both inside a main container. So, basically, my content is not really sectionalized like you have the menu right now… If that makes any sense

  11. Your profile is absolutely stunning.
    I’m working on a very large project right now that may take up to a year of development, and I feel a bit guilty because I’ll be using quite some of your code. But honestly, your demos are full of creativity and life so it is impossible to ignore them.
    For what concerns IE: I don’t care about the older versions. By the time I am finished with my project, they won’t exist anymore, and whoever still sticks to them is dumb and should not be on my website.

  12. Is there a way to expand the awesomeness of this concept so that it’s possible to “flip” the menu (or flyer) over and read content on the back? I have been trying to execute a pamphlet idea like this for a while now! Truly fantastic.

  13. Really great !
    However, it seems to don’t work on IE10..
    Do you have an idea how to fix it ? 🙂
    Thank you, and really great job !

  14. This is a great menu.

    Codrops how can I delete the “See the recipe” part on the menu?

    Many Thanks

  15. This is so great! However, it’s not working in IE10 (as mentioned above). Do you know how to solve this?