INF-250: Web Design and Development — Layout Strategies
Last week of class
- Good work on Assignment #3!
- Quiz #5 is due Sunday
- Final Project is due May 3
- No in-person class next week! Work on your final project.
- Today, we'll go over some responsive design techniques.
- Thursday: Feedback form and final project check-in
Today: Building a Responsive Navbar
Last week we covered media queries and responsive layout patterns. Today we are going to apply those ideas to one of the most common real-world problems: a navigation bar that works on both desktop and mobile.
On a wide screen, nav links sit side by side in a horizontal row. On a small screen, there is not enough room — the links either overflow or squeeze together. The standard solution is the hamburger menu: hide the links behind a button, and show them in a dropdown when the user taps that button.
- HTML structure for the navbar
- CSS for the desktop layout
- CSS to adapt it for mobile using a media query
- A small amount of JavaScript to toggle the menu open and closed
Step 1 — HTML Structure
The navbar needs three things: a logo (or site name), a toggle button for mobile, and the list of links.
<header>
<nav class="navbar">
<a href="#" class="logo">MySite</a>
<button class="menu-toggle" aria-label="Toggle navigation">
☰
</button>
<ul class="nav-links">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>- The
☰character (HTML entity☰) renders as the ☰ hamburger icon — no image needed. aria-label="Toggle navigation"gives the button a descriptive label for users who cannot see the icon.
Step 2 — Desktop Styles
We start with the desktop layout. The navbar uses justify-content: space-between to push the logo to the left and the links to the right. The toggle button is hidden with display: none — it only appears on mobile.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
header {
background: #2c3e50;
position: relative; /* needed for the mobile dropdown to position against */
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
max-width: 1200px;
margin: 0 auto;
}
.logo {
color: white;
text-decoration: none;
font-size: 1.5rem;
font-weight: bold;
}
.nav-links {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-links a {
color: white;
text-decoration: none;
font-size: 1rem;
}
.nav-links a:hover {
text-decoration: underline;
}
/* Hidden on desktop — only visible on mobile */
.menu-toggle {
display: none;
background: none;
border: none;
color: white;
font-size: 1.75rem;
cursor: pointer;
padding: 0.25rem;
}header has position: relative. This is required so that when we make the mobile dropdown position: absolute, it positions itself relative to the header — not the whole page. Step 3 — Mobile Styles
Inside a max-width: 768px media query, we flip the layout: show the toggle button, hide the nav links by default, and reveal them with a special .open class that JavaScript will add and remove.
@media (max-width: 768px) {
/* Show the hamburger button */
.menu-toggle {
display: block;
}
/* Hide the links by default */
.nav-links {
display: none;
flex-direction: column;
gap: 0;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #2c3e50;
padding: 0.5rem 2rem 1rem;
}
/* Show when JavaScript adds the .open class */
.nav-links.open {
display: flex;
}
.nav-links li {
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.nav-links a {
display: block;
padding: 0.75rem 0;
}
}The key pattern here is using a CSS class as a switch. The CSS defines what "open" looks like; JavaScript just adds or removes the class. This keeps styling in CSS and behavior in JavaScript, where they belong.
Step 4 — JavaScript Toggle
The JavaScript is short: select the button and the link list, then listen for a click on the button and toggle the .open class.
const toggle = document.querySelector('.menu-toggle');
const navLinks = document.querySelector('.nav-links');
toggle.addEventListener('click', () => {
navLinks.classList.toggle('open');
});classList.toggle('open')adds the class if it is missing, removes it if it is already there — one line handles both directions.
<script> tag just before the closing </body> tag, or wrap your code in a DOMContentLoaded listener. If the script runs before the HTML elements exist, querySelector will return null and the click listener will never attach. Step 5 — Smooth Animation (Optional)
Switching between display: none and display: flex is abrupt — the menu appears and disappears instantly. A smoother approach uses max-height and transition instead.
/* Remove display:none / display:flex toggling.
Instead, control visibility with max-height: */
@media (max-width: 768px) {
.menu-toggle {
display: block;
}
.nav-links {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #2c3e50;
flex-direction: column;
gap: 0;
padding: 0 2rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.nav-links.open {
max-height: 400px;
padding: 0.5rem 2rem 1rem;
}
.nav-links li {
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.nav-links a {
display: block;
padding: 0.75rem 0;
}
} The idea: instead of toggling display, we animate max-height from 0 to a value large enough to show all the links. CSS can animate max-height; it cannot animate display.
height: 0 to height: auto — CSS does not know what "auto" evaluates to ahead of time. Transitioning max-height to a fixed value like 400px is the workaround. Make the target value comfortably larger than your tallest possible menu so it never gets clipped. Responsive Column Layouts
Another extremely common pattern is a section that shows content in multiple columns on a wide screen and collapses to a single column on mobile. Think of a row of feature cards, a blog post grid, or a three-column "why choose us" section.
There are three main approaches, each with different tradeoffs:
- Flexbox with wrapping — fewest lines of CSS, automatic column count
- CSS Grid with breakpoints — explicit control over 1 → 2 → 3 columns
- CSS Grid with
auto-fill— automatic columns, no media queries needed
Column Layout — HTML Structure
All three approaches share the same HTML: a wrapper element and a set of child cards. The wrapper gets the grid or flex styles; the children just sit inside it.
<section class="content-grid">
<div class="card">
<h2>Card One</h2>
<p>Some content for this card.</p>
</div>
<div class="card">
<h2>Card Two</h2>
<p>Some content for this card.</p>
</div>
<div class="card">
<h2>Card Three</h2>
<p>Some content for this card.</p>
</div>
</section>Approach 1 — Flexbox with Wrapping
Add flex-wrap: wrap to the container and give each card a flex-basis (the minimum width before it wraps to a new row). The browser fills as many cards per row as it can; when there is not enough room it wraps automatically.
.content-grid {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.card {
flex: 1 1 280px; /* grow, shrink, start at 280px */
background: #f5f5f5;
padding: 1.5rem;
border-radius: 8px;
}flex: 1 1 280pxmeans: grow to fill extra space, shrink if needed, and use 280px as the starting width.- On a wide screen three 280px cards fit side by side. On a tablet two fit. On a phone one fills the full width.
- No media queries needed — the layout adapts automatically.
Approach 2 — CSS Grid with Breakpoints
CSS Grid gives you precise control. Define the column count explicitly for each screen size using media queries.
/* Mobile first: start with 1 column */
.content-grid {
display: grid;
gap: 2rem;
grid-template-columns: 1fr;
}
/* 2 columns on tablets */
@media (min-width: 600px) {
.content-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 3 columns on desktop */
@media (min-width: 1024px) {
.content-grid {
grid-template-columns: repeat(3, 1fr);
}
}grid-template-columns: 1frmeans one column that takes all available space.repeat(2, 1fr)creates two equal columns.repeat(3, 1fr)creates three equal columns.- The
gapproperty adds space between all rows and columns at once — no need for margins on individual cards.
This is the most explicit approach and the one to reach for when the design requires specific column counts at specific breakpoints.
Responsive Design in your Final Project
- Make sure your navbar works at any screen size and is easy to click on mobile. Don't rely only on the web inspector - load it on an actual mobile device!
- Focus on 1-2 breakpoints.
768pxfor the desktop/mobile divide, and possibly1024pxfor a widescreen/laptop divide or400pxfor small device/tablet divide. - Your site doesn't need to look perfect at every possible width - but no important content should be dropped at any width.
- Use these "reset" rules to help with your layouts:
* { box-sizing: border-box; margin: 0; padding: 0; }img { max-width: 100%; height: auto; display: block; } - Consider giving your content container a
max-widthvalue so it remains accessible on extra-wide screens. You could have a full-wdith "background" container that sets a color, but try to keep your content within a 1200px or so container. When content gets wider than that, it can be hard to follow. Includemargin: 0 autoto center your content if desired:.content { max-width: 1200px; margin: 0 auto; } - Don't forget your meta tag! Your site will render fine in the inspector without it, but not always on an actual device:
<meta name="viewport" content="width=device-width, initial-scale=1.0">