Bistro Theme¶
Bistro is a theme for restaurants and takeaways that take orders on their own site. It is built around finding food, not around the basket: the menu is the home page, its sections stay in reach as you scroll, dietary filters and a search box narrow it, and every dish shows its allergens. The order is always in view, beside the menu on a computer and as a bar at the foot of a phone.
It is built on top of Brock, which is built on Nova (Bistro → Brock → Nova → Bootstrap template fallback), so every page works as soon as a site switches to it, and Brock's type, spacing, buttons, product page, basket, mini basket, checkout and footer come through wherever Bistro doesn't override them. It needs Restaurant Ordering for anything to be ordered; without it the menu is a menu.
Site admins pick it under Admin → Shop Settings → Theme (Choosing a Theme); platform staff can also set it per site in System → Tenants → {site} → Theme (theme name Bistro).
Design principles¶
| Principle | What it means on the page |
|---|---|
| Brock underneath | Inter on white with Brock's spacing scale, neutral ramp, buttons and radii. Bistro adds only what a restaurant needs. |
| The menu card's dotted leader | Each dish runs into its price along a dotted leader. Section titles use the same sans as everything else; a site that wants a serif for them sets --rm-font-display (and --tb-font-display for booking titles) in its Design Brief custom CSS. |
| The brand colour is for ordering | Only Add, Add to order, the order bar and Checkout use the site's brand colour (Brock's green until a Design Brief sets one). |
| Allergens before purchase | UK food law wants allergens available before the customer buys: each row says what the dish contains, the item sheet lists them (or says none of the 14 are declared), and the site's allergen notice with the kitchen's phone number is on the menu and in every footer. |
| Open or closed, always | A service strip under the header on every page says whether the kitchen is taking orders, until when, how long orders take, or when it opens again. |
What comes from Brock and what Bistro replaces¶
| From Brock | Bistro's own |
|---|---|
Fonts, spacing, colour and radius tokens (brock.css, with Nova's bdgr.css under it) |
The header: sticky ordering bar, service strip, the brand layout's site header (templates/bistro/fragment/nav.html). Brock's search-first header, announcement and promise strips, category row and drawer never appear. |
| Header basket and mini basket drawer, "added to your basket" pop-over (opened from Bistro's Your order link and from any add-to-basket form outside the menu) | The menu (restaurantMenu), ordering status, the item sheet and its basket panel |
| Product page (with Choose options for a dish that has choices), listings, search results, basket, checkout (the step-by-step flow), confirmation | The allergen and kitchen band at the top of the footer (fragment/footerNotice) |
| Footer: the site's footer content, legal lines and payment marks | The site menu's template (extensions/menuGeneratorExtension/menu.html) and orderingStatus block |
| The head: fonts, meta, Stripe, Nova and Brock assets | Bistro's stylesheet and script, added by the fragment/themeAssets hook in Brock's head |
What Bistro redesigns¶
| Area | Highlights |
|---|---|
| Header | A 64px sticky bar: logo and name, the site menu behind More (a disclosure that works without JavaScript), and Your order (Brock's header basket, with its item count; it opens Brock's mini basket). Under it, the service strip: open until 22:00 and last orders, closed and when it opens (today, tomorrow or a weekday), or paused and when it's back; the current preparation estimate; and a Collection / Delivery switch with a postcode check. |
| Menu page | The restaurantMenu and orderingStatus extensions, normally through the menu page stereotype. |
| Baskets and checkout | Brock's basket, checkout and confirmation, with each line's choices, note and allergens (see Lines with choices). |
| Footer | Brock's dark footer, with a band above it holding the kitchen, its phone number and the allergen notice. |
Other pages (content, account, search, listings) are Brock's (or Nova's where Brock has none) inside Bistro's header and Brock's footer.
The More panel is capped to the screen height (100dvh less the bar) and scrolls inside, so a long site
menu never runs off the bottom. Bistro draws the site menu with its own template
(templates/bistro/extensions/menuGeneratorExtension/menu.html, adapted from Ridge's): inside More each item with
a drop-down is an accordion row.
Layouts: ordering and brand¶
The Site Setting Bistro layout (bistro-layout, under Settings → Ordering) picks how the site is laid out.
| Value | What it is |
|---|---|
ordering (default) |
The header above: one bar, the site menu behind More, the service strip. Right for a single restaurant whose site is mostly its menu. |
brand |
For a chain or brand whose site is also its marketing website (think a burger chain). The site splits in two sections, each with its own header. |
In the brand layout BistroLayoutInterceptor puts bistroLayout, bistroOrderingSection (and, in the site
section, bistroOrderUrl, the menu page) on the model, and the header only reads those.
- Ordering section: the menu page (any page with the
menustereotype),/p/basket,/p/checkout, collection pages (except the site's landing collection, which is the home page), product pages,/buy,/checkout, order confirmation (/o/), order tracking (/order/) and search. These keep the ordering bar and the service strip (open until, Collection / Delivery), with Back to {site name} in place of More and Your order. - Site section: everything else (home, content pages, restaurants, blog, booking, account, errors). A regular
header: the logo, the whole site menu as a row of links with drop-downs (use six or seven top-level items),
Book a table (
/book) and Order now (the menu page; left out if the site has no menu page) buttons, an account icon, and the order link once the basket has something in it. Below 992px a menu button opens a full-height panel that scrolls inside (the menu as an accordion) with the two buttons pinned to its foot. There is no service strip here.
The decision (BistroLayouts) is by path, so it needs no per-page setup. A site hides the name beside a wordmark
logo with the Hide site name in navigation site detail (hideSiteNameInNav); both headers honour it.
The menu page¶
Give the home page (or a page called Menu) the menu page stereotype. It carries orderingStatus in
topBanner, restaurantMenu in mainSection and the site menu in navMenu. New sites have the stereotype
from their seed data; existing sites get it from the restaurant site upgrade (MenuStereotypes.ensure).
Bistro already shows the ordering status in its header, so it draws the orderingStatus block as nothing.
Switch the site to another Nova theme and the block comes back without editing the page.
The menu: restaurantMenu¶
Any Nova-based theme can use it. Its templates live in templates/nova/extensions/restaurantMenu/, its
assets in themes/nova/css|js/extensions/restaurantMenu/.
| Setting | Default | What it does |
|---|---|---|
sections |
empty | The collections shown as menu sections, in order (comma-separated seoNames). Empty shows every collection that holds a menu item, by the collections' display order then name. |
heading |
empty | A heading above the menu. |
showSearch |
on | The search box and dietary chips. The chips only offer flags that some item has. |
showBasket |
on | The basket beside the menu on wide screens, and the order bar on phones. |
Rows are the enabled menuItem products of each section, in the order they were added. Each row shows the
name, a dotted leader and the price ("from" when sizes cost different amounts), a plain-text teaser, the
dietary marks (V, VG, GF, DF, NF, H, K and the heat levels) and the allergens, an optional photo, and
Add. While the kitchen is closed the menu stays browsable and Add says when ordering opens.
Tabs and search. The section tabs stick under the header and follow the scroll. Search matches names and descriptions; chips narrow to items with every chosen flag; empty sections and their tabs hide, and a live region says how many dishes match.
The item sheet. Add opens a sheet (a bottom sheet on phones, a dialog on wider screens) with the dish's photo and description, its allergens, sizes, each modifier group as radios (exactly one), checkboxes (each option once) or - / + steppers (an option more than once), a note for the kitchen and the quantity. Required groups are marked and checked, a full group takes no more, sold-out options are greyed and can't be chosen, and the button shows the running total. Adding posts in the background; the sheet closes, the basket panel refreshes, and a confirmation is announced.
Without JavaScript Add is a link to the item page (?item=<seoName> on the menu page), a plain form
that posts to the basket. Sizes post as vSkuId; each choice posts as modifiers[n] with the value
groupId:optionId (a stepper option is a select of groupId:optionId:count), which the basket reads as its
modifiers list. The sheet loads the same page and lifts its form out, so the server is the only renderer.
From a product page. A dish with modifier groups reached outside the menu (from search, say) can't be
added with a plain Add button, since its required choices need answers. Its product page (ProductController,
model menuItemUrl from MenuItemLinks) shows Choose options instead, a link to the dish's item page on
the first enabled page with the menu stereotype (/p/<menu>?item=<seoName>). A size picked on the product
page goes along as &size=<variant skuId>, and the item form starts on it. Nova's product page, Brock's (and so Bistro's) and every
theme that falls back to Nova (Ridge) show the link; Depot, Pop, Spec and Atelier have their own
product pages and keep Add, which the basket refuses with a message saying a choice is needed. Dishes without
choices keep the Add button everywhere.
The basket panel shows each line with its choices, note and allergens, - / + and Remove (posting to
/basket/update and /basket/remove, in the background with JavaScript), the subtotal and Checkout. It is
refreshed from ?rmView=basket.
Accessibility. Tabs are links with aria-current, and moving to a section focuses its heading. The sheet
is a modal <dialog>: the page behind is inert, Escape or the close button closes it, and focus returns to
the Add button. Steppers are real buttons with labels ("One more Extra halloumi"). Motion is reduced when the
visitor asks for it.
Re-skin the menu in another theme by setting the --rm-* tokens on :root (see the top of
restaurant-menu.css): --rm-bg, --rm-surface, --rm-ink, --rm-muted, --rm-line, --rm-accent,
--rm-accent-ink, --rm-radius, --rm-font-display, --rm-sticky-top (height of a sticky header) and
--rm-leader (dotted, or none). Brock, Depot, Pop, Atelier and Spec point --rm-sticky-top at their
measured header height (--b-header-offset and the like), Ridge sets its header's 73px (63px on phones), and
on Nova with the fixed header option the menu's script measures the header itself.
Ordering status: orderingStatus¶
A banner saying whether the kitchen is taking orders, how long orders take, and the Collection / Delivery switch. It renders nothing unless restaurant ordering is on.
| Setting | Default | What it does |
|---|---|---|
showWhenOpen |
on | Off shows the banner only while closed or paused. |
showModeSwitch |
on | The collection / delivery switch and postcode check. |
The postcode check asks GET /api/public/v1/local-delivery/check?postcode=&countryCode=GB (see
Restaurant Ordering) and shows its message under the switch, without moving the
switch or the opening hours beside it. A part postcode ("LS6") is sent as it is, and on the menu page the
basket's subtotal goes along (&subtotal=), so a basket under the minimum hears "Add £4.50 more for delivery". Without an answer it says the postcode
is checked at checkout. The choice and postcode are remembered in the browser (localStorage keys
bdgr.orderMode, collect or delivery, and bdgr.orderPostcode), never sent to the server on their own.
Unified checkout, on the same origin, reads them once for a food order that has no delivery method yet:
Collection preselects the kitchen's collection option (when there is exactly one); Delivery fills the
postcode into an empty delivery address, which brings back the local delivery options, and preselects
the first. The step-by-step checkout (deliveryChooser, Nova and Brock) preselects the matching option the
same way (its address step comes first, so it doesn't fill the postcode). The customer can change either,
and the server checks the address and method as usual.
Every page of a shop taking restaurant orders carries orderingStatus (an OrderingStatusView, put on the
model by OrderingStatusInterceptor) and orderingDeliveryCheck, so a theme header can show the status.
Lines with choices, in every Nova theme¶
fragments/lineItemAttributes :: customisation(lineItem) draws a line's choices ("Garlic mayo, Extra
halloumi ×2 (+£3.00)"), the customer's note and the allergens as ordered, compactly and escaped. Nova's
basket, checkout summary and order confirmation, Brock's basket, summary and confirmation, and the mini
baskets of Brock, Depot, Pop, Spec and Atelier all include it. It draws nothing for an ordinary line.
Implementation¶
| Piece | Where |
|---|---|
| Theme record | systemUpdates/2026-10-10-add-bistro-theme.json (InsertIfAbsent) and initialisation/templates.json; sort order 80 in ThemeDefaults. Bistro's base is brock and its second base nova (ThemeContext.secondBaseThemeTemplatePath); sites that had it on Nova are moved by systemUpdates/2026-10-16-rebase-bistro-on-brock.json (RebaseTheme), which changes the theme record and the copy embedded in each site. |
| Templates | templates/bistro/fragment/{nav,themeAssets,footerNotice}.html, templates/bistro/extensions/orderingStatus/main.html, templates/bistro/extensions/menuGeneratorExtension/menu.html. There is no Bistro head or footer: Brock's, through its two hooks. See Theme Resolution. |
| Layout | BistroLayoutInterceptor, BistroLayouts (which paths are ordering), bistro-layout in ConfigKeyDefaults |
| Assets | themes/bistro/css/bistro.css, themes/bistro/js/bistro.js |
| Menu and status | RestaurantMenuExtension, OrderingStatusExtension, MenuViews, OrderingStatusViews, OrderingStatusInterceptor, MenuStereotypes |
| Tests | ThemeChainResolutionTest (Bistro → Brock → Nova → Bootstrap), BistroLayoutsTest, BistroLayoutInterceptorTest, BistroHeaderRenderTest (both layouts and sections), RestaurantMenuRenderTest (menu, item form, status and line choices across the Nova themes), MenuStereotypesTest |