Your client has a restaurant. They need a website. You have two options:
- Spin up a Next.js project, install 40 packages, configure Tailwind, deploy to Vercel, and then explain to them why they can't change the phone number themselves.
- Send them one HTML file.
I've been doing option 2 for a while now, and I want to show you exactly what it looks like β including the parts people get wrong.
The skeleton
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Saffron & Sage β Modern Indian Kitchen</title>
<meta name="description" content="Modern Indian kitchen in downtown. Fresh spices, open fire, no shortcuts.">
<style>/* all the CSS */</style>
</head>
<body>
<!-- nav, hero, menu, gallery, reservation, footer -->
<script>/* all the JS */</script>
</body>
</html>
Everything else is detail. But the details are where it breaks.
Detail 1: the mobile menu that actually works
This is where most single-file attempts fall apart. People hide the nav under 768px and forget to give it a way back.
.nav-links { display: flex; gap: 28px; }
.burger { display: none; background: none; border: 0; cursor: pointer; }
@media (max-width: 768px) {
.nav-links {
display: none;
position: absolute; top: 64px; left: 0; right: 0;
flex-direction: column; gap: 0;
background: #fff; border-bottom: 1px solid #e5e7eb;
}
.nav-links.open { display: flex; }
.nav-links a { padding: 14px 20px; }
.burger { display: block; }
}
const burger = document.querySelector('.burger');
const links = document.querySelector('.nav-links');
burger.addEventListener('click', () => links.classList.toggle('open'));
links.addEventListener('click', e => {
if (e.target.tagName === 'A') links.classList.remove('open');
});
That last listener is the one everyone forgets: without it, the menu stays open after the user taps a link, and the page scrolls behind an open menu. It looks broken, and the client will notice.
Detail 2: images without images
A restaurant page wants photos. But every external image is a request, a layout shift and a thing that can 404. For the sections that are decoration rather than content, use gradients:
.dish {
aspect-ratio: 4 / 3;
border-radius: 12px;
background: linear-gradient(135deg, #f59e0b 0%, #b45309 55%, #7c2d12 100%);
display: grid; place-items: center;
color: #fff8ec; font-weight: 700; letter-spacing: .04em;
}
Real photos of the actual food still go in as real images β but the hero background, the section dividers, the card thumbnails in a "coming soon" state: those are gradients. Your page stays fast and nothing breaks while the client is still "getting the photos ready" (they will take three weeks).
Detail 3: an accordion in nine lines
Every business page needs an FAQ. You don't need a library:
<div class="faq">
<button class="q" aria-expanded="false">Do you take reservations?</button>
<div class="a" hidden>Yes β up to 8 people online, larger groups by phone.</div>
</div>
document.querySelectorAll('.q').forEach(q => {
q.addEventListener('click', () => {
const open = q.getAttribute('aria-expanded') === 'true';
q.setAttribute('aria-expanded', String(!open));
q.nextElementSibling.hidden = open;
});
});
Note the aria-expanded and the hidden attribute: that's what makes it work for screen readers, and it costs you nothing.
Detail 4: the reservation form that doesn't need a backend
You don't want to run a server for a form. Two honest options:
<!-- 1. mailto: crude but works everywhere -->
<form action="mailto:hello@restaurant.com" method="post" enctype="text/plain">
<!-- 2. a form endpoint (Formspree, Basin, Web3Forms...) -->
<form action="https://formspree.io/f/xxxxxxx" method="POST">
For a client site, use option 2 and put the endpoint in the file. It's one line, no server, and the emails land in their inbox.
What you end up with
One file. Around 600 lines. It loads in under a second, works offline, scores well on Lighthouse without any tuning, and the client can open it in Notepad to fix a typo.
If you want to see the finished version of the exact restaurant page I described, it's here, running live (not a screenshot):
π Restaurant demo
And here's a free one you can download and take apart β a coming-soon page with a countdown, no signup required:
π Free template
There are 61 of these now, across most of the niches a client will throw at you: segcom.net.
What's the piece you always end up rewriting from scratch?
Top comments (0)