# Nextjs Restaurant Template: Open Now, Menu, Book a Table

> A nextjs restaurant template built around three questions: are you open now, what is on the menu, can I book a table. Live hours, menu filters and booking.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Nov 6, 2026
- Category: [Build it: screens and apps](https://wingo-ui.com/blog/category/build)
- Reading time: 10 min
- Canonical: https://wingo-ui.com/blog/nextjs-restaurant-template

## TL;DR

A good Next.js restaurant template answers the three questions guests otherwise phone in for: is it open right now, what is on the menu, and can I get a table. Wingo UI's Restaurant Page does that with an open status computed in the restaurant's own time zone, a tabbed menu with dietary filters, a booking form with real time slots and an onReserve callback, plus a gallery, private events and a map. It is a Pro template you install as source and feed with your own hours, menu and booking API.

Three questions send guests to the phone: are you open, what is on the menu, and do you have a table at eight. A nextjs restaurant template is worth using when the page answers all three before the guest thinks of calling, on a phone, in the restaurant's own time zone. This guide walks through Wingo UI's Restaurant Page, a Next.js restaurant website built from source components: what each section does, how to feed it your hours, menu and booking API, and how it compares with the next js restaurant template listings on ThemeForest and in CMS galleries.

## What should a restaurant website answer first?

The guest's question, in the order they ask it:

1. **Open now?** One line the guest can read in a second: Open until 11 PM, Closing soon, or Closed now, opens tomorrow at 10 AM. A table of hours makes them do the math.
2. **What is on the menu?** Real text with prices, split into sections, with a way to find the vegan or gluten free dishes without reading everything.
3. **Can I book?** A form that knows which days you are closed, which times are left and when the last table sits.

After those come the things that help someone choose you: photos, the chef's story, reviews, private events, the address with directions and a map. Most restaurant landing page template listings we read cover the photos and the story well. Few say anything about time zones, last seating, booked slots or what the menu does on a 390px screen.

## What is in the Wingo UI restaurant template?

[Restaurant Page](https://wingo-ui.com/templates/restaurant-page) is one React component with typed sample content for Olive & Ember, a fictional wood-fired restaurant in Chicago. Top to bottom:

- **Hero** with the open status, the cuisine, the rating and two buttons: Reserve a table glides to the form and focuses it, Order online links out.
- **Menu** with category tabs and dietary filter toggles in one bar that sticks under the header while you scroll the dishes.
- **Reservation form** with party size, a [date picker](https://wingo-ui.com/components/date-picker), time slots per service, name, email, phone and notes, then a confirmation with an Add to calendar file.
- **Chef story**, **gallery** with a full-screen [image lightbox](https://wingo-ui.com/components/image-lightbox), **private event** cards and **reviews**.
- **Visit** section with the address, Get directions, a call link, the week's hours with today marked, and a [location map](https://wingo-ui.com/components/location-map).
- **Gift card band** and a footer. On phones, a Reserve a table bar sticks to the bottom while the form is off screen.

> Live demo (Restaurant Page): Pick Vegan, then Gluten free: the filters combine and each tab shows how many dishes still match. Then press Reserve a table, pick a date and notice the struck-through 7 PM times. Try it at [Restaurant Page](https://wingo-ui.com/templates/restaurant-page) and install it with `npx wingo-ui@latest add restaurant-page`.

There are three designs (`design="warm" | "dark" | "minimal"`), and every color, radius and section is a prop. `showGallery={false}` or `showEvents={false}` drops a section; `tone` and `ctaTone` move accents and the Reserve buttons from neutral to your brand.

## How do you install it in a Next.js app?

Run one CLI command while signed in with a Pro account. The template is Pro, and the CLI copies it with every component it imports:

```bash
npx wingo-ui@latest add restaurant-page
```

You get `components/templates/restaurant-page.tsx` and `components/templates/restaurant-page-data.ts`, plus the components it builds on (tabs, toggle group, date picker, time picker, lightbox, map, sticky bar) in `components/ui/` and the marketing header, footer and sections in `components/blocks/`. The data file holds the types and the sample content. Keep your own content in a separate file and pass it as props, as the examples below do: the template files stay as installed, so `npx wingo-ui@latest update` has fewer edits to merge.

## How does the open now status work?

You pass the weekly hours and the restaurant's IANA time zone; the page works out the status in that zone after hydration and refreshes it every minute. Hours are a list of weekdays, each with zero or more services in 24 hour time:

```ts
// app/restaurant/content.ts
import type { RestaurantDayHours, RestaurantInfo } from "@/components/templates/restaurant-page-data"

export const INFO: RestaurantInfo = {
  name: "Harbor Table",
  cuisine: "Seafood and raw bar",
  priceRange: "$$",
  street: "212 Water St",
  city: "New York, NY 10038",
  neighborhood: "South Street Seaport",
  phone: "(212) 555-0172",
  email: "hello@harbortable.com",
  point: { lat: 40.7069, lng: -74.0031 },
}

export const HOURS: RestaurantDayHours[] = [
  { day: 0, ranges: [{ label: "Brunch", open: "10:00", close: "15:00" }] },
  { day: 1, ranges: [] }, // closed Mondays
  { day: 2, ranges: [{ label: "Dinner", open: "17:00", close: "22:00" }] },
  { day: 3, ranges: [{ label: "Dinner", open: "17:00", close: "22:00" }] },
  { day: 4, ranges: [{ label: "Dinner", open: "17:00", close: "23:00" }] },
  // a close before the open runs past midnight
  { day: 5, ranges: [{ label: "Dinner", open: "17:00", close: "01:00" }] },
  { day: 6, ranges: [{ label: "Dinner", open: "17:00", close: "01:00" }] },
]
```

The page reads the current minute in the `timeZone` you pass (an IANA name such as `America/New_York`) with `Intl.DateTimeFormat`, so a guest in Los Angeles checking a New York restaurant sees New York's status. Within 60 minutes of a close the line turns into Closing soon. A Friday service that closes at 01:00 still counts as open at 00:30 on Saturday.

The live status is read after hydration and refreshed every minute. The server renders `labels.hoursFallback` instead ("Dinner Tuesday to Sunday from 5 PM" in the sample), so the HTML never disagrees with the client and crawlers still get a sentence about your hours. Set that label for your restaurant. For screenshots and tests, pass a fixed `now`.

The same hours drive three other places: the hours table in the Visit section, the closed days in the calendar, and the time slots in the form.

## How does the restaurant menu work in React?

A restaurant menu React component needs two things from you: categories, which become tabs, and dishes, each with its category and its dietary tags:

```ts
import type { RestaurantMenuCategory, RestaurantMenuItem } from "@/components/templates/restaurant-page-data"

export const CATEGORIES: RestaurantMenuCategory[] = [
  { id: "raw", label: "Raw bar", description: "Shucked to order." },
  { id: "mains", label: "Mains" },
  { id: "wine", label: "Wine" },
]

export const MENU: RestaurantMenuItem[] = [
  { id: "oysters", category: "raw", name: "East Coast oysters", description: "Half dozen, mignonette.", price: 21, tags: ["gluten-free"], highlight: "Chef's pick" },
  { id: "chowder", category: "mains", name: "Clam chowder", price: 14, soldOut: true },
  { id: "muscadet", category: "wine", name: "Muscadet, Loire", price: 0, variants: [{ label: "Glass", price: 13 }, { label: "Bottle", price: 52 }] },
]
```

The tag ids `vegan`, `vegetarian`, `gluten-free` and `spicy` get drawn icons; you can add your own through `dietaryTags`, or pass an empty array to hide the filters. Filters combine (Vegan and Spicy means dishes with both), the tabs then show how many dishes match, and an empty section offers Clear filters instead of a blank panel. Arrow keys move between tabs. On phones the tabs and the filters become swipeable chip rows, and the dishes stack in one column. `variants` replace the price for wines, and `currency` plus `locale` format every price with `Intl.NumberFormat`.

## How does table booking work?

The form offers only times the restaurant can seat, and two props connect it to your booking system. It builds time chips per service every `timeStep` minutes (30 by default) and stops `lastSeating` minutes before the close (60 by default). Today's times inside `minNotice` (30 minutes) are hidden, the calendar opens `bookingWindow` days ahead (60) and the next bookable day is preselected. The date picker opens a bottom sheet on phones.

`isBooked(date, time)` strikes through times that are gone, and `onReserve` receives the reservation once the form passes validation (a date, a time, a name and a valid email). Here is the wiring for an App Router project:

```tsx
// app/actions.ts
"use server"

import type { RestaurantReservation } from "@/components/templates/restaurant-page"

export async function reserveTable(reservation: RestaurantReservation) {
  // check the slot again here: another guest may have taken it since the page loaded
  const response = await fetch(`${process.env.BOOKING_API_URL}/reservations`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(reservation),
  })
  if (!response.ok) throw new Error("Booking failed")
  const { id } = (await response.json()) as { id: string }
  return { confirmation: id }
}
```

```tsx
// app/restaurant-home.tsx
"use client"

import Link from "next/link"
import { RestaurantPage } from "@/components/templates/restaurant-page"
import { reserveTable } from "@/app/actions"
import { CATEGORIES, HOURS, INFO, MENU } from "@/app/restaurant/content"

export function RestaurantHome({ taken }: { taken: string[] }) {
  const booked = new Set(taken) // "2026-11-13 19:30"
  return (
    <RestaurantPage
      linkComponent={Link}
      restaurant={INFO}
      hours={HOURS}
      categories={CATEGORIES}
      menu={MENU}
      timeZone="America/New_York"
      partySizeMax={8}
      isBooked={(date, time) => booked.has(`${date} ${time}`)}
      onReserve={reserveTable}
      labels={{ hoursFallback: "Dinner Tuesday to Saturday from 5 PM, brunch on Sundays" }}
    />
  )
}
```

While the promise runs, the button spins. A resolved `{ confirmation }` shows your number with the date, time and guests; a rejection keeps every field filled and shows `labels.reserveFailed`. Parties larger than `partySizeMax` are pointed to the private events link. If you already use a booking service with its own widget, set `showReservation={false}` and put a link to it in `headerProps.actions`: the Reserve buttons and the sticky bar go with the form.

## How do you get the hours into search results?

Add `Restaurant` JSON-LD with `openingHoursSpecification` in your route, built from the same `HOURS` array. The template does not print any structured data, because only your route knows your canonical URL. Next.js recommends a JSON-LD script in the page component ([Next.js JSON-LD guide](https://nextjs.org/docs/app/guides/json-ld)). Building it from `HOURS` keeps the page and the markup from drifting apart:

```tsx
// app/page.tsx
import { RestaurantHome } from "@/app/restaurant-home"
import { HOURS, INFO } from "@/app/restaurant/content"

const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]

export default async function Page() {
  const taken: string[] = [] // load booked times from your booking API
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Restaurant",
    name: INFO.name,
    servesCuisine: INFO.cuisine,
    telephone: INFO.phone,
    acceptsReservations: true,
    priceRange: INFO.priceRange,
    // INFO.city is display text ("New York, NY 10038"), so split it here
    address: { "@type": "PostalAddress", streetAddress: INFO.street, addressLocality: "New York", addressRegion: "NY", postalCode: "10038", addressCountry: "US" },
    openingHoursSpecification: HOURS.flatMap((day) =>
      day.ranges.map((range) => ({ "@type": "OpeningHoursSpecification", dayOfWeek: DAYS[day.day], opens: range.open, closes: range.close })),
    ),
  }
  return (
    <>
      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />
      <RestaurantHome taken={taken} />
    </>
  )
}
```

Keep your Google Business Profile hours in sync with the same data. Guests trust whichever one they see first. Our [Next.js JSON-LD guide](https://wingo-ui.com/blog/nextjs-json-ld) turns that script into a typed component with a CI check that fails when the markup drifts from the page.

## What does it do on a phone?

It keeps the three answers within thumb reach on a 390px screen, because a guest checking hours is often on a phone, on the way there. In detail:

- The open status sits in the hero, above the fold, with a live region so screen readers hear it.
- Menu tabs and filters are chip rows you swipe, and the bar sticks under the header so the filters stay in reach.
- The calendar is a bottom sheet, time chips are at least 44px tall on touch screens, and booked times are announced as booked.
- On submit, focus moves to the first field with a problem.
- The gallery lightbox opens from the tapped photo; swipe between photos, pinch to zoom, pull down to close.

> Live demo (Image Lightbox): Tap a photo: it grows into the full-screen viewer. Swipe to the next one, pinch to zoom, then pull down to close it. Try it at [Image Lightbox](https://wingo-ui.com/components/image-lightbox) and install it with `npx wingo-ui@latest add image-lightbox`.

- The map does not steal the page scroll on touch: a tap opens it full screen.
- The sticky Reserve a table bar respects the safe area and hides while the form is on screen.

## What should you watch out for?

Five things the sample cannot decide for you, and each one bites at launch if you skip it:

- **Sample copy in the labels.** Some default labels describe the sample restaurant: `home`, `ctaTitle` and `legal` name Olive & Ember, `visitTitle` and `tagline` name Fulton Market, and `heroTitle` talks about an oak fire. Read through `DEFAULT_RESTAURANT_PAGE_LABELS` and set yours through `labels`.
- **Holidays.** The calendar disables weekdays with no hours. `datePickerProps.disabled` replaces that rule, so include the closed weekday too: `disabled: [{ dayOfWeek: [1] }, new Date(2026, 11, 25)]`.
- **Double bookings.** `isBooked` is a hint built when the page loaded. Check availability again in the server action.
- **Map tiles.** The map uses OpenStreetMap tiles by default. For a busy site, read the [OSM tile usage policy](https://operations.osmfoundation.org/policies/tiles/) and set `mapProps={{ tileUrl }}` to your own provider.
- **Real photos.** Without `heroImage` and gallery `src` values the page draws its pictures in code. That is fine for a prototype, never for launch.

## How does it compare with other restaurant templates?

Most alternatives are marketplace themes and CMS starters: that is what searches like "restaurant website template react" and "nextjs restaurant website" returned when we checked on November 6, 2026. The listings below are the ones that ranked, with what each listing itself says:

| Template | Price | Stack | Hours and booking |
| --- | --- | --- | --- |
| [Restho](https://themeforest.net/item/restho-restaurant-react-nextjs-template/42711636) (ThemeForest) | $19 regular license | React, Next.js, Bootstrap 5; last updated January 7, 2023 | A reservation date form |
| [Beef](https://themeforest.net/item/beef-restaurant-react-nextjs-template/58512774) (ThemeForest) | $14 regular license | Next.js, Tailwind CSS, TypeScript; updated March 15, 2026 | Not described on the listing |
| [Maison](https://www.sanity.io/templates/maison) (Sanity) | Paid, priced on the vendor's site | Next.js, Tailwind v4, Sanity CMS | A reservation form with React Hook Form; hours not mentioned |
| [BCMS restaurant starter](https://thebcms.com/starters/next/restaurant) | Not stated | Next.js, Tailwind CSS, BCMS | Hours and maps from the CMS; booking through external links |
| [Wingo UI Restaurant Page](https://wingo-ui.com/templates/restaurant-page) | Pro: $8 a month, $80 a year or $150 once | Next.js App Router, React 19, Tailwind v4, Radix, Motion | Live status in the restaurant's time zone, slots, booked times, onReserve |

Pick Maison or the BCMS starter when the owner must edit the menu in a CMS without a developer: the content model comes ready. The Wingo UI template takes plain props, so a CMS is something you wire up yourself. Pick a ThemeForest theme when you want a cheap, one-off look and will build the hours and booking logic on your own. The Wingo UI page fits when booking and the phone experience matter more than a CMS, and when you already build other screens with the library. If the restaurant also sells products online, the [Storefront Page](https://wingo-ui.com/templates/storefront-page) shares the same header, footer and band system.

## Where to go from here

Try the demo on the [Restaurant Page template](https://wingo-ui.com/templates/restaurant-page): switch the design, the time zone and the time input in the playground, then install it with `npx wingo-ui@latest add restaurant-page` once you have [Pro](https://wingo-ui.com/pricing). For the time zone math behind booking slots, read our [React appointment booking guide](https://wingo-ui.com/blog/react-appointment-booking), and for more screens built this way, browse the [Build it category](https://wingo-ui.com/blog/category/build).

## Components in this post

- [Restaurant Page](https://wingo-ui.com/templates/restaurant-page) (Pro): A React restaurant website template: live opening hours, a tabbed menu with dietary filters, table booking, a gallery, events and a map. Install: `npx wingo-ui@latest add restaurant-page`
- [Date Picker](https://wingo-ui.com/components/date-picker) (Pro): A React date picker: type a date in your locale or pick it from a calendar, with presets, limits, optional time and a phone sheet. Install: `npx wingo-ui@latest add date-picker`
- [Image Lightbox](https://wingo-ui.com/components/image-lightbox) (Pro): A React image lightbox: a full-screen photo viewer that zooms from its thumbnail, with swipe, pinch zoom and swipe down to close. Install: `npx wingo-ui@latest add image-lightbox`
- [Location Map](https://wingo-ui.com/components/location-map) (Pro): A React location map to pick or show a place: a draggable pin, an optional radius with a towns count, offline search and a static fallback. Install: `npx wingo-ui@latest add location-map`
- [Storefront Page](https://wingo-ui.com/templates/storefront-page) (Pro): A React ecommerce store template: free shipping bar, hero, collections, a filterable product grid with quick add, a cart drawer and reviews. Install: `npx wingo-ui@latest add storefront-page`

## FAQ

### Is there a free Next.js restaurant template?

Yes. bloomtpl's Next.js Restaurant Template on GitHub (Next.js 16, Tailwind CSS 4, a reservation page) is free for personal and commercial use but may not be resold, as of November 6, 2026; check the license, the last commit and whether the booking form actually sends anything before you build on any free starter. The Wingo UI Restaurant Page is Pro, while the Tabs and Button it uses are free.

### How do I show whether a restaurant is open now in Next.js?

Store the hours per weekday in 24 hour time, read the current time in the restaurant's IANA time zone with Intl.DateTimeFormat, and render the status only after hydration so the server and the client agree. The Restaurant Page does this with its hours and timeZone props and updates the line every minute.

### Can the template take real reservations?

Yes, through onReserve. It receives the date, time, party size, name, email, phone and notes; return a promise that resolves with your confirmation number, or reject it and the form stays filled with an error. Use isBooked to strike through taken times, and check the slot again on the server.

### Should a restaurant menu be a PDF or a web page?

A web page. HTML text reflows on a phone, search engines and AI assistants can read it, and guests can filter it for vegan or gluten free dishes. Keep a PDF only as an optional download for printing.

### How much does the Wingo UI restaurant template cost?

It is part of Wingo UI Pro: $8 a month, $80 a year or $150 once, with every other Pro template and component included. The 14-day refund applies only while no Pro item has been downloaded.

---

Source: https://wingo-ui.com/blog/nextjs-restaurant-template
