# Restaurant Page

> A React restaurant website template: live opening hours, a tabbed menu with dietary filters, table booking, a gallery, events and a map.

- Type: React template (Templates)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add restaurant-page`
- Docs: https://wingo-ui.com/templates/restaurant-page

## Interaction

A restaurant's home page. The full-bleed hero shows whether the restaurant is open right now in its own time zone (Open until 11 PM, Closing soon, Closed now, opens tomorrow at 10 AM) and updates every minute; Reserve a table glides down to the booking form and focuses it, Order online links out. The menu has category tabs (arrow keys move between them) and dietary filter toggles (Vegan, Vegetarian, Gluten free, Spicy) in one bar that sticks under the header while you scroll the list; on phones both are swipeable chip rows. Filters combine, the tabs then show how many dishes match, and an empty section offers Clear filters. Every dish shows its price (glass and bottle for wines), dietary tags and a highlight badge. The reservation form takes the party size (steppers, typing, arrow keys), a date from the calendar (closed weekdays and past days are disabled, a bottom sheet on phones) and a time from chips per service (Brunch, Dinner) every timeStep minutes, with booked times struck through and past times hidden; the next bookable day is preselected. Name and email are validated on submit and focus moves to the first problem; the button spins while onReserve runs, then a confirmation shows the date, time, guests and a confirmation number with Add to calendar (an .ics file) and Make another booking. The gallery opens a full-screen lightbox (swipe, arrows, pinch zoom). Private event cards are one link each. The visit section has the address, Get directions, call, the week's hours grouped by day with today marked, and a map that opens full screen on touch. On phones a Reserve a table bar sticks to the bottom while the form is off screen.

## Installation

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

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/restaurant-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/templates/restaurant-page.tsx`
- `components/templates/restaurant-page-data.ts`

npm dependencies: `npm i lucide-react motion`

Wingo UI dependencies (installed automatically):

- [Marketing Section](https://wingo-ui.com/components/marketing-section) ([Markdown](https://wingo-ui.com/components/marketing-section.md))
- [Marketing Header](https://wingo-ui.com/components/marketing-header) ([Markdown](https://wingo-ui.com/components/marketing-header.md))
- [Marketing Footer](https://wingo-ui.com/components/marketing-footer) ([Markdown](https://wingo-ui.com/components/marketing-footer.md))
- [Testimonials Section](https://wingo-ui.com/components/testimonials-section) ([Markdown](https://wingo-ui.com/components/testimonials-section.md))
- [CTA Section](https://wingo-ui.com/components/cta-section) ([Markdown](https://wingo-ui.com/components/cta-section.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Date Picker](https://wingo-ui.com/components/date-picker) ([Markdown](https://wingo-ui.com/components/date-picker.md))
- [Image Lightbox](https://wingo-ui.com/components/image-lightbox) ([Markdown](https://wingo-ui.com/components/image-lightbox.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Location Map](https://wingo-ui.com/components/location-map) ([Markdown](https://wingo-ui.com/components/location-map.md))
- [Number Input](https://wingo-ui.com/components/number-input) ([Markdown](https://wingo-ui.com/components/number-input.md))
- [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) ([Markdown](https://wingo-ui.com/components/sticky-cta-bar.md))
- [Tabs](https://wingo-ui.com/components/tabs) ([Markdown](https://wingo-ui.com/components/tabs.md))
- [Textarea](https://wingo-ui.com/components/textarea) ([Markdown](https://wingo-ui.com/components/textarea.md))
- [Time Picker](https://wingo-ui.com/components/time-picker) ([Markdown](https://wingo-ui.com/components/time-picker.md))
- [Toggle Group](https://wingo-ui.com/components/toggle-group) ([Markdown](https://wingo-ui.com/components/toggle-group.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/page.tsx
"use client"

import Link from "next/link"
import { RestaurantPage, type RestaurantReservation } from "@/components/templates/restaurant-page"

async function reserve(reservation: RestaurantReservation) {
  const response = await fetch("/api/reservations", { method: "POST", body: JSON.stringify(reservation) })
  if (!response.ok) throw new Error("Booking failed")
  return (await response.json()) as { confirmation: string }
}

export default function HomeRoute() {
  return <RestaurantPage linkComponent={Link} timeZone="America/Chicago" partySizeMax={8} onReserve={reserve} />
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `design?` | `"warm" \| "dark" \| "minimal"` | `"warm"` | The whole page's look. warm: a soft amber page with tinted bands and raised cards; dark: a night page in both themes (the tokens switch to dark under the root, overlays render inside it); minimal: a plain page with no bands, an inset hero and border-only cards. Each design picks its own backgrounds unless you set them. |
| `restaurant?` | `RestaurantInfo` | `"RESTAURANT_INFO"` | { name, cuisine, priceRange?, street, city, neighborhood?, phone, email, point: { lat, lng }, rating?, reviewCount?, directionsNote? }: the brand, hero line, contact block and map pin. |
| `hours?` | `RestaurantDayHours[]` | `"RESTAURANT_HOURS"` | { day: 0 Sunday ... 6 Saturday, ranges: { label?, open: "17:00", close: "23:00" }[] } per weekday; no ranges is closed, a close at or before the open runs past midnight. Drives the open status, the hours table, the closed days of the calendar and the time slots. |
| `categories?` | `RestaurantMenuCategory[]` | `"RESTAURANT_MENU_CATEGORIES"` | { id, label, description? }: the menu tabs, in order. |
| `menu?` | `RestaurantMenuItem[]` | `"RESTAURANT_MENU"` | { id, category, name, description?, price, variants?: { label, price }[], tags?: string[], highlight?, soldOut? }: the dishes and drinks. variants (Glass, Bottle) replace the price; tags are dietary tag ids. |
| `dietaryTags?` | `RestaurantDietaryTag[]` | `"RESTAURANT_DIETARY_TAGS"` | { id, label, icon?, tone? }: the dietary marks and filters. vegan, vegetarian, gluten-free and spicy get a drawn icon by id; an empty array hides the filters. |
| `story?` | `RestaurantStory` | `"RESTAURANT_STORY"` | { eyebrow, title, paragraphs, chefName, chefRole, stats: { value, label }[], scene?, image? }: the chef story beside a picture. |
| `gallery?` | `RestaurantGalleryImage[]` | `"RESTAURANT_GALLERY"` | { src?, scene?, alt, caption? }: the gallery tiles; without src the scene is drawn (pasta, pizza, salad, steak, fish, dessert, cocktail, wine, room, oven, cellar, terrace). |
| `eventSpaces?` | `RestaurantEventSpace[]` | `"RESTAURANT_EVENT_SPACES"` | { id, name, description, capacity, minimum?, scene?, image?, href? }: the private event cards; href defaults to eventsHref#id. |
| `reviews?` | `Testimonial[]` | `"RESTAURANT_REVIEWS"` | Guest quotes with names, the occasion as role and star ratings, shown by TestimonialsSection. |
| `heroImage?` | `string` | `""` | A photo url for the hero; without it the dining room with the oven is drawn in code. |
| `orderHref?` | `string` | `"/order"` | The hero's Order online button; empty hides it. |
| `eventsHref?` | `string` | `"/private-events"` | Plan an event and the event cards link here. |
| `giftCardsHref?` | `string` | `"/gift-cards"` | The gift card band's main button. |
| `directionsHref?` | `string` | `""` | Get directions; defaults to a Google Maps search for the address. |
| `labels?` | `Partial<RestaurantPageLabels>` |  | Every string on the page (DEFAULT_RESTAURANT_PAGE_LABELS), including the open status sentences, validation messages and the confirmation. |
| `partySizeMin?` | `number` | `1` | The smallest party the form books. |
| `partySizeMax?` | `number` | `10` | The largest party the form books; larger groups are pointed to private events. |
| `defaultPartySize?` | `number` | `2` | The starting party size, kept inside the range. |
| `timeStep?` | `number` | `30` | Minutes between bookable times (at least 5). |
| `lastSeating?` | `number` | `60` | Minutes before a service closes when the last table can be booked. |
| `minNotice?` | `number` | `30` | Minutes from now before the first time that can still be booked today. |
| `bookingWindow?` | `number` | `60` | How many days ahead the calendar opens. |
| `timeInput?` | `"slots" \| "picker"` | `"slots"` | slots: a grid of time chips per service with booked times struck through; picker: a TimePicker field limited to the open times. |
| `isBooked?` | `(date: string, time: string) => boolean` |  | Marks taken times ("2026-11-06", "19:30") as booked: struck through and not selectable. |
| `onReserve?` | `(reservation: RestaurantReservation) => void \| RestaurantReservationResult \| Promise<RestaurantReservationResult \| void \| unknown>` |  | Called with { date, time, partySize, name, email, phone, notes } once the form is valid. A returned promise keeps the button loading; resolve with { confirmation } to show your number, reject to keep the form and show labels.reserveFailed. Without it, a 900 ms pretend booking. |
| `timeZone?` | `string` | `"America/Chicago"` | The restaurant's IANA time zone: the open status, today's row, today's past times and the first bookable day follow it, wherever the guest is. |
| `now?` | `Date` |  | A fixed clock for screenshots and tests; default the live time, read after hydration (the server renders labels.hoursFallback). |
| `currency?` | `string` | `"USD"` | ISO 4217 code for the menu prices. |
| `locale?` | `string` | `""` | Intl locale for prices, dates and times; empty uses the UIProvider's, else en-US. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Accents: highlight badges, the active menu tab, pressed filters, the chosen time, the calendar selection and review stars. |
| `ctaTone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Reserve a table buttons, the form's submit, the sticky bar and the gift card band. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"lg"` | Corners of panels and cards (0 / 12 / 16 / 24px), with matching inner tiles and time chips. |
| `menuColumns?` | `1 \| 2` | `2` | Dish columns from 768px; phones always get one. |
| `background?` | `"plain" \| "soft" \| "glow" \| "aurora" \| "grid" \| "dots"` | `"soft"` | The main background. A design picks its own unless you set one (warm: soft, dark: glow, minimal: plain). |
| `backgroundColor?` | `string` | `"oklch(0.72 0.11 58)"` | The tint and glow color of the main background; each design has its own (warm amber, ember, sand). |
| `bandBackground?` | `"muted" \| "tinted" \| "gradient" \| "dots"` | `"tinted"` | The look of the alternating bands (warm: tinted, dark: muted; minimal has no bands). Cards and fields inside a band are lifted above it. |
| `bandColor?` | `string` | `"oklch(0.72 0.11 58)"` | The tint of tinted and gradient bands; defaults to backgroundColor. |
| `showMenu?` | `boolean` | `true` | The menu with its tabs and filters. |
| `showReservation?` | `boolean` | `true` | The booking form. Off, the Reserve buttons and the sticky bar go too. |
| `showStory?` | `boolean` | `true` | The chef story with its picture and numbers. |
| `showGallery?` | `boolean` | `true` | The photo grid and its lightbox. |
| `showEvents?` | `boolean` | `true` | The private event spaces. |
| `showReviews?` | `boolean` | `true` | The guest reviews. |
| `showLocation?` | `boolean` | `true` | The address, hours table and map. |
| `showCta?` | `boolean` | `true` | The gift card band before the footer. |
| `stickyReserveBar?` | `boolean` | `true` | On phones, a Reserve a table bar with the open status sticks to the bottom while the form is off screen. |
| `header?` | `boolean` | `true` | The sticky header with the brand, section links, call and Reserve. |
| `footer?` | `boolean` | `true` | The footer with Visit, About and Help links. |
| `headerProps?` | `Partial<MarketingHeaderProps>` |  | Anything for MarketingHeader (layout, variant, items...). |
| `footerProps?` | `Partial<MarketingFooterProps>` |  | Anything for MarketingFooter (variant, columns, socials...). |
| `tabsProps?` | `Partial<TabsRootProps>` |  | Anything for the menu TabsRoot (variant, size, activationMode...). |
| `datePickerProps?` | `Partial<DatePickerProps>` |  | Anything for the reservation DatePicker (disabled days, presets, calendarProps...). |
| `timePickerProps?` | `Partial<TimePickerProps>` |  | Anything for the TimePicker when timeInput is "picker". |
| `galleryProps?` | `Partial<LightboxGalleryProps>` |  | Anything for the LightboxGallery (columns, aspect, maxVisible, featured...). |
| `reviewsProps?` | `Partial<TestimonialsSectionProps>` |  | Anything for TestimonialsSection (layout, columns, card...). |
| `mapProps?` | `Partial<LocationMapProps>` |  | Anything for the LocationMap (tileUrl, height, staticOnly, interactive...). |
| `ctaProps?` | `Partial<CtaSectionProps>` |  | Anything for the gift card CtaSection (variant, title, hrefs...). |
| `stickyBarProps?` | `Partial<StickyCtaBarProps>` |  | Anything for the phone StickyCtaBar (variant, showOn, hideOnScroll...). |
| `animateIn?` | `boolean` | `true` | The hero copy rises in once when the page mounts on the client; never during hydration, under reduced motion or in thumbnails. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for the header, event cards and footer links (next/link in an app). |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<RestaurantPageSlot, string>>` |  | Classes for header, hero, heroMedia, status, menu, menuBar, filters, menuPanel, menuItem, reservation, reservationForm, slots, confirmation, story, gallery, events, eventCard, reviews, location, hours, map, cta, footer and stickyBar. |

## Parts

### RESTAURANT_PAGE_SAMPLE (constant)

All the sample content of Olive & Ember in one object: restaurant, hours, categories, dietary tags, menu, story, gallery, event spaces and reviews.

### RESTAURANT_INFO (constant)

Olive & Ember, a wood-fired Mediterranean restaurant on Fulton Market, Chicago.

### RESTAURANT_HOURS (constant)

Closed Mondays, dinner Tuesday to Sunday, brunch on weekends, until 11 PM on Fridays and Saturdays.

### RESTAURANT_MENU_CATEGORIES (constant)

Starters, Mains, Desserts and Drinks.

### RESTAURANT_DIETARY_TAGS (constant)

Vegan, Vegetarian, Gluten free and Spicy.

### RESTAURANT_MENU (constant)

22 dishes and drinks with prices, tags, highlights, glass and bottle prices and one sold out.

### RESTAURANT_STORY (constant)

Chef Sofia Marino's story with three numbers.

### RESTAURANT_GALLERY (constant)

Nine drawn scenes: the room, pasta, pizza, a negroni, salad, ribeye, branzino, tiramisu and wine.

### RESTAURANT_EVENT_SPACES (constant)

The Ember Room, The Cellar and a full buyout.

### RESTAURANT_REVIEWS (constant)

Six guest reviews.

### RESTAURANT_PAGE_DESIGNS (constant)

The design names: ["warm", "dark", "minimal"].

### DEFAULT_RESTAURANT_PAGE_LABELS (constant)

The English strings.

### drawRestaurantScene (function)

(kind) returns an svg data url of a drawn scene (a plate from above, a glass, the room, the oven), for an img or a lightbox image.

### drawRestaurantHero (function)

() returns the wide drawn hero picture (the dining room with the oven) as an svg data url.

### RestaurantPageProps (type)

Props of RestaurantPage, including the attributes of the root element.

### RestaurantPageLabels (type)

Every string of the page.

### RestaurantPageSlot (type)

The keys of classNames.

### RestaurantPageDesign (type)

"warm" | "dark" | "minimal".

### RestaurantPageRadius (type)

"none" | "sm" | "md" | "lg".

### RestaurantPageTimeInput (type)

"slots" | "picker".

### RestaurantPageMenuColumns (type)

1 | 2.

### RestaurantReservation (type)

What onReserve receives: { date: yyyy-MM-dd, time: HH:mm, partySize, name, email, phone, notes }.

### RestaurantReservationResult (type)

What onReserve may resolve with: { confirmation? }.

### RestaurantInfo (type)

The restaurant's name, cuisine, address, contact, map point and rating.

### RestaurantDayHours (type)

{ day, ranges }: one weekday's services.

### RestaurantHoursRange (type)

{ label?, open, close }: one service, "HH:mm" 24h.

### RestaurantMenuCategory (type)

{ id, label, description? }: one menu tab.

### RestaurantMenuItem (type)

One dish or drink.

### RestaurantMenuVariant (type)

{ label, price }: a glass or bottle price.

### RestaurantDietaryTag (type)

{ id, label, icon?, tone? }: a dietary mark and filter.

### RestaurantStory (type)

The chef story.

### RestaurantChefStat (type)

{ value, label }: one number of the story.

### RestaurantGalleryImage (type)

{ src?, scene?, alt, caption? }: one gallery tile.

### RestaurantEventSpace (type)

One private event card.

### RestaurantSceneKind (type)

The drawn scenes: pasta, pizza, salad, steak, fish, dessert, cocktail, wine, room, oven, cellar, terrace.

## Examples

- **Dark design**: A night page in both themes: the tokens switch to dark under the root, with an ember glow and muted bands.
- **Minimal design**: A plain page with an inset hero, no bands and border-only cards.
- **Brand accents**: ctaTone and tone set to brand: orange Reserve buttons, highlight badges, chosen time and calendar day.
- **A time picker and 15 minute slots**: timeInput="picker" with timeStep 15 and parties up to 6: the time field only offers open, free times.
- **When booking fails**: onReserve rejects: the button spins, the form stays filled and says the table could not be booked.

## See also

- [Storefront Page](https://wingo-ui.com/templates/storefront-page) ([Markdown](https://wingo-ui.com/templates/storefront-page.md))
- [Landing Page](https://wingo-ui.com/templates/landing-page) ([Markdown](https://wingo-ui.com/templates/landing-page.md))
- [Date Picker](https://wingo-ui.com/components/date-picker) ([Markdown](https://wingo-ui.com/components/date-picker.md))
- [Image Lightbox](https://wingo-ui.com/components/image-lightbox) ([Markdown](https://wingo-ui.com/components/image-lightbox.md))
- [Location Map](https://wingo-ui.com/components/location-map) ([Markdown](https://wingo-ui.com/components/location-map.md))

---

Canonical: https://wingo-ui.com/templates/restaurant-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
