Responsive restaurant menu with filter

Muhammad Saqib Ilyas
22 min readNov 11, 2023

--

In another blog, I wrote about creating a restaurant menu using only HTML, and CSS. In this blog, we’ll create another restaurant menu page with filtering functionality. By filtering, we mean that, by default, all dishes on the menu are displayed, but you can click on a specific category to show only dishes that are in that category. For example, showing only breakfast items, or only lunch items, or only dinner items.

We also want the menu to be responsive. On large screens, we want our page to have a two-column layout like the following:

Our target page on large screens

On smaller computer screens, the page should have a one column layout like the following:

Our target page on medium sized screens

On small screens, the item details should be below the item image like the following:

The HTML structure

We’ll create an elaborate HTML structure for this web page. We start with the following:

<!DOCTYPE html>
<html>
<head>
<title>Restaurant Menu with Filtering</title>
<link rel="stylesheet" href="assets/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&family=Great+Vibes:wght@400;600&family=Noto+Sans:wght@400;600&family=Playpen+Sans:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Icons" />
</head>
<body>
<nav>
<h2 class="logo">Eater's Delight</h2>
</nav>
<section class="menu">
<div class="menu-container">
<div class="menu-header">
<h2>Our menu</h2>
<p>Choose from our delightful menu items. We cater to all kinds of culinary taste. You may filter the menu items by type.</p>
</div>
<div class="menu-buttons">
<button type="button" class="menu-button active-button" id="All">All</button>
<button type="button" class="menu-button" id="breakfast">Breakfast</button>
<button type="button" class="menu-button" id="lunch">Lunch</button>
<button type="button" class="menu-button" id="dinner">Dinner</button>
</div>
<div class="menu-items">
<!-- Menu items go here -->
</div>
</div>
</section>
<script src="app.js"></script>
</body>
</html>

In the head section, we refer to our external style sheet file. I want to use Google Fonts, and some Material Icons. That’s what the next four link tags are for. For the Google Fonts, notice that we have specified different weights for some of the fonts.

In the body section, we have a nav element that houses the logo, in the form of an h2 element. Next up, we create a section for the rest of the page. We give this section a class of menu.

You’ll notice several HTML elements with class names, above. These are all hypothetical at the moment. We haven’t defined the style for these classes. But it helps to visualize in our heads what the distinct styling categories are.

Within the menu section, we create a div element with a class of menu-container. As the class name suggests, this will serve as the container for the menu items. Inside it, we create another div element with a class of menu-header. This serves as the header of the menu, housing the h2 heading for the menu, and a brief message to the customers.

After this, we create another div element to house the buttons that the user would eventually be able to click to filter the menu items. This div element is given a class of menu-buttons. It includes button elements for filtering. The first one is “All”, to show all the menu items. This one is the default. The second button is to filter and show only the breakfast items. This is followed by other buttons for lunch, and dinner. Each button is given a unique id, which will help in getting hold of the button object in JavaScript. All buttons are also given a class of menu-button which will serve to provide a consistent styling. At any point, only one of these buttons can be in the “pressed” state. To indicate that, we’ll use a class named active-button. By default, this class is assigned to the “All” button.

After the buttons, we have another div with a class of menu-items. This will house the menu items. It’ll have one div element for each menu item. What will a menu item look like? Here’s what we’ll go with:

<div class="menu-item">
<div class="item-image">
<img src="assets/img/img1.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Omnivorous Delight</h3>
<h4 class="item-price">$13.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Dinner</span></p>
</div>
</div>

We give the outer div element a class of menu-item. Inside it, we include an image of the food item. Notice that we’re using the alt attribute, which is a good practice from an accessibility perspective. Next up, we have a div element for the item details, with a class named item-details. The item details include the item name, an item price, a rating, and the category to which the item belongs. For this blog, we’ll use categories for breakfast, lunch, and dinner.

For the rating, we are using an unordered list to hold the stars representing the rating. We are using star icons from the Material icon library inside span elements. To display the stars, we need to use the class of material-icons. To display a filled star, we need to type star as the text inside the span element. Similarly, we can display a hollow star using star_outline, and a half filled star using star-half.

Copy and paste the menu item so that you have nine menu items with a class of menu-items inside the div element with a class of menu-container immediately after the div element with a class of menu-buttons. Give each item a different name, different image, price, description, and rating. The document structure should resemble the following:

<!DOCTYPE html>
<html>
<head>
<title>Restaurant Menu with Filtering</title>
<link rel="stylesheet" href="assets/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Icons" />
</head>
<body>
<nav>
<h2 class="logo">Eater's Delight</h2>
</nav>
<section class="menu">
<div class="menu-container">
<div class="menu-header">
<h2>Our menu</h2>
<p>Choose from our delightful menu items. We cater to all kinds of culinary taste. You may filter the menu items by type.</p>
</div>
<div class="menu-buttons">
<button type="button" class="menu-button active-button" id="All">All</button>
<button type="button" class="menu-button" id="breakfast">Breakfast</button>
<button type="button" class="menu-button" id="lunch">Lunch</button>
<button type="button" class="menu-button" id="dinner">Dinner</button>
</div>
<div class="menu-items">
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
<div class="menu-item">
<!-- Details removed -->
</div>
</div>
</div>
</section>
<script src="app.js"></script>
</body>
</html>

At this point, the page looks really flat, with no styling. Let’s start to style the elements now.

Basic styling

First, let’s lay some ground work for the CSS styling:

*, ::after, ::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}

:root {
--Roboto: 'Roboto', 'Open Sans', sans-serif;
--Noto: 'Noto Sans', 'Open Sans', sans-serif;
--Monts: 'Montserrat', 'Open Sans', sans-serif;
--script: 'Playpen Sans', 'Open Sans', sans-serif;
--vibes: 'Great Vibes', 'Open Sans', sans-serif;
--bred: #AB3428;
--orange: #F49E4C;
--yellow: #F5EE9E;
--lightblue: #C4C6E7;
--darkblue: #3B8EA5;
--lightgray: #eeeeee;
--black: #000;
}

We start with a CSS reset. We select all the elements, and set the margins and padding to zero. We also set box-sizing to border-box so that borders are included in element sizes. These settings get rid of some browser-specific styling. Next, we define a few CSS variables for some Google fonts and color shades. Defining CSS variables is a useful approach in that if you want to change the color scheme or fonts used on your page later, all you need to do is to update the values in one place (the variable definitions).

Next up, some basic styling for the body element:

body {
font-family: var(--Monts);
font-size: 16px;
line-height: 1.3;
background-color: var(--bred);
color: var(--lightgray);
}

We declare that our default font is Montserrat, 16 pixels. We set line-height to 1.3 for comfortable reading (increased spacing between lines). We set the background and font color.

Styling the logo

Let’s give the logo a hand-writing touch:

.logo {
font-family: var(--vibes);
font-size: 2.5rem;
color: var(--yellow);
}

We select the Great Vibes font with a font size of 2.5 rem. We use a yellowish color for the logo, which contrasts well over the background color. But, the logo is a bit too close to the top and left edges. Let’s fix that:

nav {
padding: 1.5rem;
}

We apply a padding of 1.5 rem on all sides of the nav section, which contains the logo.

Here’s what the top of the page looks like at this point:

The styled navigation part

Styling the menu header

Let’s style the menu header section:

.menu {
margin: 1.5rem 0;
}

.menu-item img {
width: 100%;
}

.menu-container {
margin: 0 auto;
max-width: 1300px;
padding: 0 1rem;
}

.menu-header {
text-align: center;
}

.menu-header h2 {
font-family: var(--script);
}

.menu-header p {
padding: 1.5rem 0;
}

The menu header at present is fairly close to the logo. To fix that, we add a bit of top and bottom margin.

Although the images aren’t technically part of the menu header, but our images are pretty large, and displaying them in their original size is pretty annoying. To fix that, we use the descendant selector .menu-item img (any img elements that are descendants of an element wiht a class of menu-item), and set the width equal to the parent container.

The menu items are right at the left edge of the screen. To fix that, we select the menu-container class and set margin property to 0 auto, which centers the menu items horizontally. We set the max-width property to 1300 pixels, so that the menu doesn’t occupy all of the width on large screens. We also add a bit of padding on the left and right.

To center the “Our Menu” message horizontally, we set text-align to center on the element with the class of menu-header. We also change the font of that message to the Playpen Sans font, using the script CSS variable. To separate the text after the “Our Menu” message, we adding a bit of top and bottom padding to the p element descendant of the element with a class of menu-header.

Here’s what the top of the page looks like now:

Menu header styled up

Now, let’s style the buttons:

.menu-buttons {
display: flex;
justify-content: center;
margin: 1.5rem 0;
}

.menu-button {
font-family: var(--Noto);
font-size: 1.1rem;
background: none;
border: none;
color: var(--lightgray);
margin: 0 1rem;
cursor: pointer;
}

.active-button {
color: var(--lightblue);
opacity: 0.8;
}

We select the div element wrapping the buttons with a class selector .menu-buttons. We use CSS Flexbox to layout the buttons neatly in a row. We configure Flexbox to center the buttons horizontally within the parent container. We also a bit of margin around the row of buttons on the top and bottom. We set the cursor property for the menu-button class to cursor so that the user gets a bit of visual feedback that they are on an active element, when hovering over the buttons. At any given time, the user would be viewing one menu category: All, Breakfast, Lunch, or Dinner. The button corresponding to the currently selected category has an additional class of active-button. To style the selected button, we give a distinctive color and opacity to the active-button class.

Here’s what the top of the page looks like now:

The menu buttons styled up

Styling the menu items

Let’s style the menu items section now. We start with the following:

.menu-items {
margin: 2rem 0;
}

.menu-item {
border-radius: 4px;
background: var(--lightgray);
color: var(--black);
margin: 0.6rem 0;
box-shadow: 0 0 16px rgba(0, 0, 0, 0.75);
}

.menu-item img {
border-radius: 4px;
}

We use the menu-items class selector and give it a bit of top and bottom margin. This sets it comfortably away from the menu header and the bottom of the page.

Next, we turn our attention to the individual item displays. We use the menu-item class selector and start with rounded borders. We set a light gray background color, and black text color. By default, the individual menu items are quite close to each other. To set them comfrotably apart, we apply a bit of top and bottom margins. We also opt for a shadow beneath the menu item. The first zero in the box-shadow statement is the x offset. A positive value for this attribute means that we want the shadow to appear to the right of the element (as if there was a light shining from its left hand side). A negative value means a shadow displaced towards the left (as if there was a light shining from its right hand side). A zero value means that the shadow falls right under the element, equally towards the left and right (as if there’s a light shining on it from the very front). Similarly, the second zero means that we want the shadow equally spreading above and below the element. We give the shadow a 16 pixels spread radius, and set its color to be black with a 75% opacity. Finally, we select the img elements inside the menu item and give it a rounded border to match its parent element.

Here’s what the top of the page looks like now:

Styling the item details

Now, let’s style the item name, price, and description.

.item-details {
padding: 1rem;
}

.item-name {
font-family: var(--Roboto);
color: var(--darkblue)
}

.item-price {
padding: 0.8rem 0;
font-size: 1.3rem;
opacity: 0.8;
}

.item-rating {
list-style-type: none;
display: flex;
}

.item-category {
font-weight: 600;
}

.item-category span {
font-weight: 400;
}

The item name and description are too close to the edge. So, we give it a bit of padding all around with the item-details class selector. We set the Roboto font for the item name, and give it a bluish color. We add a bit of padding on the top and bottom to the item price. We increase the font size for the item price a little bit and reduce the opacity. The rating stars were displayed vertically since they are in an unordered list. To set them up nicely horizontal, we call Flexbox to our rescue. Also, in order to get rid of the bullets in the list, we set list-style-type to none. We give a thicker stroke the text “Categories”, and a thinner stroke to the actual category.

Here’s what the item details look like now:

Item details styled up

Adding some oomph

Let’s make the design a bit jazzy. Firstly, we’ll give a bit of distinctive styling to a menu button when the user hovers the mouse over it. Secondly, we’ll have the menu item photos fade in, i.e., appear slowly on the screen when the page loads. The same effect will be visible when the user filters the items, but that filtering remains for us to implement.

Button animation

Let’s show a line underneath the menu button that a user hovers the mouse over. Also, let’s reduce the opacity of the button text while the user is hovering over it.

.menu-button::after {
content: '';
margin-top: 0.5rem;
display: block;
width: 0;
height: 2px;
background: var(--lightgray);
transition: width 0.4s ease-out;
}

.menu-button:hover::after {
width: 100%;
}

.menu-button:hover {
opacity: 0.8;
}

We create a pseudo-element (::after) and set its content to blank. We give this element a bit of a top margin so that it is a bit displaced from the corresponding menu-buttonelement. We set its width to 0, so that it isn’t visible. Don’t worry, we’ll define the condition when it should actually be visible. Since we want to give it a width, it needs to be a block element, which is why we set the display property. We give this a height of 2 pixels. We give it a light gray background color, and finally assign a value to its transition property. This last setting says that when the width of this pseudo-element changes, it should change over 0.4 seconds, and this change in width should be fast in the beginning, and slower towards the end (the ease-out keyword). Where does this change in width come from? That is defined next in the :hover pseudo selector. When a user hovers over an element with a class of menu-button::after, the width of this pesudo-element should increase to 100% of its parent, which is the same as the menu-button element. Finally, we also decrease the opacity of the text of the menu button when a user hovers the mouse over it.

With these changes, when you hover the mouse over a menu button, you should see a line appear to grow under it from the left until it is equal in width to the menu button text. When you move the mouse pointer away from this button, you’ll see this line gradually disappear. Also, the text will appear to dim when you hover over a menu button.

Here’s a snapshot of a menu button that a user is hovering a mouse over:

A hover effect on the menu buttons

Menu item animation

Next up, let’s animate the menu items.

.menu-item {
/* Existing style specification */
animation: appear 2s;
}

@keyframes appear {
from{
opacity:0;
}
to {
opacity: 1;
}
}

We define a new animation named appear. We specify the animation to go from an opacity of 0, i.e., not visible, to an opacity of 1, i.e., visible in full glory. We connect this animation to the menu-item class specification and set it to appear over a period of 2 seconds. Now, when the page loads, you’ll see the menu items appear in view slowly over a period of 2 seconds.

Filtering the results

Next up, let’s enable filtering the items when the menu category buttons are clicked.

const buttons = document.querySelectorAll('.menu-button')
const items = document.querySelectorAll('.menu-item')

let selectedButton = 'All'

buttons.forEach( (btn) => {
btn.addEventListener('click', () => {
clearButtons()
selectedButton = btn.id
btn.classList.add('active-button')
showItems()
})
})

function clearButtons() {
buttons.forEach( (btn) => btn.classList.remove('active-button'))
}

function showItems() {
items.forEach( (item) => {
const cat = item.querySelector('p span').innerText
if ((selectedButton === 'All') || (cat === selectedButton)) {
item.style.display = 'grid';
}
else {
item.style.display = 'none';
}
})
}

We need access to the elements for the buttons and the menu items. To achieve that, we use the document.querySelectorAll() method and store all the buttons in the variable buttons and all the menu items in the variable items. We also define a variable named selectedButtonto hold the currently selected item category. The default active category is “All”, that’s why we initialized selectedButton to 'All'.

Next up, we associate mouse click event handlers with all of the button elements. We iterate over the button collection using the Array.forEach() method. For each button object, we call the addEventListener() method to associate an anonymous function as the event listener.

What needs to happen on a menu button click is that the currently active selection should be changed. We can do this by:

  • removing the active-button class from all buttons
  • adding this class to the button just clicked
  • update the display to hide any menu items that don’t have the category that we clicked on

In the click event handler anonymous function we first call a clearButton() function that removes the class active-button from all buttons. Then, we update the global variable selectedButton to the button we just clicked. Refer to the markup for the buttons: <button type=”button” class=”menu-button” id=”Breakfast”>Breakfast</button>. The id attribute holds the category text. That’s why we set selectedButton to btn.id. Next, we add the active-button class the button that was clicked. Finally, we call the showItems() function.

The showItems() function relies on the selectedButton variable to hold the currently selected category. It was intialized correctly, and our mouse click event handler updates it correctly, so that base is covered. What we do inside showItems() is to iterate over the items collection and either set the display property of an item to none to hide it, or to grid to make it visible.

How do we get the item category for an item as we iterate over the items collection? Recall from our markup that this is what an item description looks like:

<div class="item-details">
<h3 class="item-name">Hot Dog</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$3.50</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_half</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Dinner</span></p>
</div>

So, one way to access an item’s category is with the element selector p span. We can make it more specific, if we want, but I’ll keep it like this. We use the querySelector() method with an argument of ‘p span’ to access the span element with the item category, then use the innerText property to access the category text itself. We compare this text agains the value of the selectedButton variable. If the value ofselectedButton is 'All' or it matches that of the current item’s category, then we need to display this item. Note how we’ve put the condition that checks for 'All' first in the if statement. This helps speed up comparisons. If this comparison evaluates to true, then the language runtime will skip the other comparison since the or of anything with a true is true.

Responsive design

Now that our filtering is complete, let’s focus on making the design responsive. If you are on a large screen, I’m sure you share my feelings that the item displays are too big.

Let’s reduce the item height and image size for screens that are at least 600 pixels wide. Also, let’s put the item description and item image side by side. For that, let’s append the following to the CSS file we have so far:

@media only screen and (min-width: 600px) {
.menu-item {
display: grid;
grid-template-columns: 25% auto;
align-items: center;
padding: 1rem;
column-gap: 1rem;
}
.item-details {
padding: 0;
}
}

We use a media query, and change the styles for two classes. First, we use CSS Grid to style the menu item. We configure CSS Grid to set up a two column layout by setting the grid-template-columns property to 25% auto. The presence of two values, here, indicates that we want a two-column display. The fist value indicates the width of the first column (25% of its parent), and the second value (auto) indicates that the second column should occupy all remaining width of the parent element.

The item description would look better if it were vertically centered within an item. For that, we set the align-items property to center. Furthermore, we give a bit of padding on all sides of the menu-item container, and set a bit of a column gap to set the item description apart from the item image. Now, that we’ve added a column gap, we no longer need the padding around that we previously had on the item-details class, so we set it to zero.

Here’s what our page looks like on a screen with at least 600 pixels width, such as a tablet:

Our page on a screen width greater than 600 pixels

For screens smaller than that, such as cellphones, here’s what a menu item looks like (this is the style that we have developed previously):

Small screen view of a menu item

For desktop or laptop screens with a greater resolution, we can afford to leave a bit of gap on either side of the navigation portion. Append the following to the CSS file we have so far:

@media only screen and (min-width: 768px) {
.menu-header p {
width: 75%;
margin: 0 auto;
}
}

Here’s what our navigation portion looks on a scree more than 768 pixels wide (on the right), and less than 768 pixels, but greater than 600 pixels (on the left):

Our page on two different screen sizes

Finally, for even larger screens, we’d like to squeeze the navigation portion’s width even further, and use a multi-column layout for the menu items. Here’s what we can append to the CSS file:

@media only screen and (min-width: 992px) {
.menu-header p {
width: 55%;
}

.menu-items {
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
}
}

The menu-header part should be self-explanatory by now. For the menu-items class, we select CSS Grid, and ask it to layout the menu items using the repeat function. The first argument represents the number of columns to produce, i.e., 2. The second argument says that all columns should have equal width. The repeat function suggests that the number of rows should be auto-adjusted. We also specify a column gap to set the menu items reasonably apart.

Here’s what our page looks like on a large screen:

Our page on large screens

That’s all folks!

That concludes this blog. Play with the code to practice your skills. If you are going for more of a full-stack thing, add a backend and fetch the menu items from an API, rather than hard-coding them on the frontend. Try adding a shopping cart, authentication, a feedback form, a rating system that uses actual customer-provided ratings.

You may download the source code from this github repository. You may also copy-paste the files given below. First, the HTML:

<!DOCTYPE html>
<html>
<head>
<title>Restaurant Menu with Filtering</title>
<link rel="stylesheet" href="assets/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&family=Great+Vibes:wght@400;600&family=Noto+Sans:wght@400;600&family=Playpen+Sans:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Icons" />
</head>
<body>
<nav>
<h2 class="logo">Eater's Delight</h2>
</nav>
<section class="menu">
<div class="menu-container">
<div class="menu-header">
<h2>Our menu</h2>
<p>Choose from our delightful menu items. We cater to all kinds of culinary taste. You may filter the menu items by type.</p>
</div>
<div class="menu-buttons">
<button type="button" class="menu-button active-button" id="All">All</button>
<button type="button" class="menu-button" id="Breakfast">Breakfast</button>
<button type="button" class="menu-button" id="Lunch">Lunch</button>
<button type="button" class="menu-button" id="Dinner">Dinner</button>
</div>
<div class="menu-items">
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img1.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Omnivorous Delight</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$13.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Dinner</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img2.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Hot Dog</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$3.50</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_half</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Dinner</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img3.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Double Trouble</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$7.50</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_half</span></li>
</ul>
<p class="item-category">Categories: <span>Lunch</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img4.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Vegetable Salad</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$3.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_half</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Lunch</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img5.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Champion's breakfast</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$9.50</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Breakfast</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img6.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Cylinder Salad</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$4.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_half</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Lunch</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img7.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Chicken Poppers</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$6.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_half</span></li>
</ul>
<p class="item-category">Categories: <span>Breakfast</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img8.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Veggie Patties</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$1.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Breakfast</span></p>
</div>
</div>
<div class="menu-item">
<div class="item-image">
<img src="assets/img/img9.jpeg" alt="food image">
</div>
<div class="item-details">
<h3 class="item-name">Seafood Salad</h3>
<!-- <div class="line"></div> -->
<h4 class="item-price">$13.99</h4>
<ul class="item-rating">
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star</span></li>
<li><span class="material-icons">star_outline</span></li>
<li><span class="material-icons">star_outline</span></li>
</ul>
<p class="item-category">Categories: <span>Dinner</span></p>
</div>
</div>
</div>
</div>
</section>
<script src="app.js"></script>
</body>
</html>

The CSS:

*, ::after, ::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}

:root {
--Roboto: 'Roboto', 'Open Sans', sans-serif;
--Noto: 'Noto Sans', 'Open Sans', sans-serif;
--Monts: 'Montserrat', 'Open Sans', sans-serif;
--script: 'Playpen Sans', 'Open Sans', sans-serif;
--vibes: 'Great Vibes', 'Open Sans', sans-serif;
--bred: #AB3428;
--orange: #F49E4C;
--yellow: #F5EE9E;
--lightblue: #C4C6E7;
--darkblue: #3B8EA5;
--lightgray: #eeeeee;
--black: #000;
}

body {
font-family: var(--Monts);
font-size: 16px;
line-height: 1.3;
background-color: var(--bred);
color: var(--lightgray);
}

.logo {
font-family: var(--vibes);
font-size: 2.5rem;
color: var(--yellow);
}

nav {
padding: 1.5rem;
}

.menu {
margin: 1.5rem 0;
}

.menu-container {
margin: 0 auto;
max-width: 1300px;
padding: 0 1rem;
}

.menu-item img {
width: 100%;
}

.menu-header {
text-align: center;
}

.menu-header h2 {
font-family: var(--script);
}

.menu-header p {
padding: 1.5rem 0;
}

.menu-buttons {
display: flex;
justify-content: center;
margin: 1.5rem 0;
}

.menu-button {
font-family: var(--Noto);
font-size: 1.1rem;
background: none;
border: none;
color: var(--lightgray);
margin: 0 1rem;
cursor: pointer;
}

.menu-button::after {
content: '';
margin-top: 0.5rem;
display: block;
width: 0;
height: 2px;
background: var(--lightgray);
transition: width 0.4s ease-out;
}

.menu-button:hover::after {
width: 100%;
}

.menu-button:hover {
opacity: 0.8;
}

.active-button {
color: var(--lightblue);
opacity: 0.8;
}

.menu-items {
margin: 2rem 0;
}

.menu-item {
border-radius: 4px;
overflow: hidden;
background: var(--lightgray);
color: var(--black);
margin: 0.6rem 0;
box-shadow: 0 0 16px rgba(0, 0, 0, 0.75);
animation: appear 2s;
}

@keyframes appear {
from{
opacity:0;
}
to {
opacity:1;
}
}

.menu-item img {
border-radius: 4px;
}

.item-details {
padding: 1rem;
}

.item-name {
font-family: var(--Roboto);
color: var(--darkblue)
}

.item-price {
padding: 0.8rem 0;
font-size: 1.3rem;
opacity: 0.8;
}

.item-rating {
list-style-type: none;
display: flex;
}

.item-category {
font-weight: 600;
}

.item-category span {
font-weight: 400;
}

@media only screen and (min-width: 600px) {
.menu-item {
display: grid;
grid-template-columns: 25% auto;
align-items: center;
padding: 1rem;
column-gap: 1rem;
}
.item-details {
padding: 0;
}
}

@media only screen and (min-width: 768px) {
.menu-header p {
width: 75%;
margin: 0 auto;
}
}

@media only screen and (min-width: 992px) {
.menu-header p {
width: 55%;
}

.menu-items {
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
}
}

The JavaScript:

const buttons = document.querySelectorAll('.menu-button')
const items = document.querySelectorAll('.menu-item')

let selectedButton = 'All'

buttons.forEach( (btn) => {
btn.addEventListener('click', () => {
clearButtons()
selectedButton = btn.id
btn.classList.add('active-button')
showItems()
})
})

function clearButtons() {
buttons.forEach( (btn) => btn.classList.remove('active-button'))
}

function showItems() {
items.forEach( (item) => {
const cat = item.querySelector('p span').innerText
if ((selectedButton === 'All') || (cat === selectedButton)) {
item.style.display = 'grid';
}
else {
item.style.display = 'none';
}
})
}

--

--

Muhammad Saqib Ilyas

A computer science teacher by profession. I love teaching and learning programming. I like to write about frontend development, and coding interview preparation