# NOIR Drive: a free, open-source car rental booking template

> A free, MIT-licensed car rental website template on Next.js 15: multi-day booking, delivery or garage collection, per-day extras, chauffeurs, accounts.

URL: https://opencals.com/blog/templates/noir
Author: Stanislav Tyshchenko
Published: 2026-10-10
Updated: 2026-10-10
Categories: engineering, announcement

Most car rental websites I've looked at end in the same place: a nice fleet page, and then a form that says _Request a quote_, or a WhatsApp button. The customer picks a car, types their dates into a text box, and waits for someone to check a spreadsheet and come back with a price. For a business renting cars at a few thousand dirhams a day, that's a lot of money sitting in an inbox overnight.

**NOIR Drive** is a full rental website where that last step is a real booking. The customer picks a car, picks a date range against live availability, chooses delivery or collection, adds extras, uploads their licence and pays. It's MIT licensed, it's on [GitHub](https://github.com/letsopencals/template-noir), and the availability, checkout and accounts behind it run on Opencals, so you aren't writing a date-range solver or touching card data yourself.

![NOIR Drive homepage: "Drive the night." over a dark showroom with three black cars and the Dubai skyline behind glass](https://media.opencals.com/seo-content/inline/1791620408693-noir-homepage-hero.png)

The demo is a fictional Dubai company with twelve black cars, a garage in Al Quoz and four chauffeurs. Every name, price and photo is data you replace.

> **Key takeaway**
>
> NOIR is a Next.js 15 frontend you own (MIT) on a booking backend you don't have to run (the Opencals Storefront API). Cars are booked by the day across a date range, delivered or collected, with per-day and fixed extras, document uploads at checkout, and a separate chauffeur flow. Live demo at [template-noir.vercel.app](https://template-noir.vercel.app).

## A rental is a range, not a slot

Almost every booking tool is built around slots: a 45-minute haircut at 14:15, a 90-minute court at 18:00. You ask the API for slots, it hands you a list, the customer taps one.

A car doesn't work like that. Nobody wants to see "slots" for a Ferrari. They want to say _Wednesday to Saturday_ and find out whether it's free, and if it isn't, find out which days are. Asking an availability engine for every possible pick-up and return combination over a 30-day maximum is a silly amount of computation for an answer the customer can read off a calendar.

So NOIR flips it. The booking page asks Opencals for the car's **merged availability ranges**, with no duration at all, and does the containment check in the browser: does pick-up 00:00 to return 00:00 sit entirely inside one free range? That's `fitsDates` in `lib/rental.ts`, a few dozen lines of pure functions. The range calendar uses the same data to strike through booked days, and `latestReturnDate` stops the customer selecting a return that runs into the next booking. The server re-checks containment when the booking is actually created, so the client can be wrong and nothing bad happens.

![The NOIR booking page: a two-month range calendar with 28 to 31 October selected, booked days struck through, two-hour handover and return windows, and a summary rail showing 3 days of a Lamborghini Revuelto at AED 37,500](https://media.opencals.com/seo-content/inline/1791620407411-noir-book-a-car.png)

Under the hood each car is a product with a base duration of one day (`86400` seconds), `allowCustomDuration: true` and a `maxDuration` of 30 days, on a schedule that runs 24/7. The price is the base unit times the number of units, which is why the summary rail above can say _3 days × AED 12,500 = AED 37,500_ and be sure the engine will charge exactly that.

## Why every car is its own product

The obvious way to model a fleet is one product, "Rolls-Royce Cullinan", with variants for each physical car. In Opencals that's the wrong shape: variants don't block each other. Two customers could each book "the Cullinan" for the same weekend and the engine would happily accept both.

So in NOIR each car is its own product with `maxAttendees: 1`, sitting in its own product pool. The pool matters more than it looks. Every car is attached to two locations, a garage you collect from and a delivery location, and without a pool a staffless product counts bookings per location. A delivery booking on Friday wouldn't stop someone collecting the same car from the garage on Friday. With the pool, one booking blocks the car everywhere.

If you run three identical G 63s, that means three products. It's a bit more admin in the dashboard, and it's honest: they are three cars, and each one has its own mileage, its own service history and its own week.

## Midnight in Dubai is 20:00 in UTC

Here's the bit that will bite anyone building this from scratch. A rental day starts at local midnight in the store's timezone, but the API stores everything in UTC.

The README's own example: a Dubai booking from 10 to 13 November becomes

```ts
{ fromDate: '2026-11-09', fromTime: '20:00:00',
  toDate:   '2026-11-12', toTime:   '20:00:00' }
```

Dubai is UTC+4, so local midnight on the 10th is 20:00 on the 9th in UTC. Three days, three units, three times the daily rate. `toAppointmentSlot` does that conversion with moment-timezone, and `rentalDays` counts days at noon UTC on both sides so a daylight-saving change in some other store timezone can't turn a three-day rental into two days and 23 hours. There's also a one-second tolerance when merging ranges, because a 24/7 schedule stored as 00:00 to 23:59:59 leaves a seam between days that would otherwise split every range at midnight.

None of this is visible to the customer, which is the point.

## The handover lives outside the booking

If the booking runs midnight to midnight, where does "deliver it to my hotel between 10:00 and 12:00" go?

Not into the slot. The handover window, the return window, a different collection address, the flight number for an airport pick-up and a preferred car for chauffeur bookings all travel as appointment `customAttributes`. The keys are listed in `siteConfig.customAttributeKeys`, they show up on the booking in the Opencals dashboard, and they're shown back to the customer in their account. The demo offers six two-hour windows between 08:00 and 20:00.

Keeping them out of the slot is what makes availability simple. A car delivered at 10:00 on Wednesday and one delivered at 18:00 on Wednesday both occupy Wednesday. Your driver's schedule is a logistics problem, and the booking engine doesn't pretend to solve it.

The same pattern covers documents. Driving licence and passport or Emirates ID uploads, date of birth and the rental terms are [checkout questions](https://opencals.com/features/checkout-questions) set up in the dashboard, so the template has no hardcoded form fields to fork. Extras come in two kinds: per-day [add-ons](https://opencals.com/blog/booking-add-ons) like the excess waiver or an additional driver, which use `durationMultiplied` and so cost price × days, and fixed ones like a child seat, Abu Dhabi delivery or prepaid fuel.

## Twelve cars, one rates table

The marketing side of the site does what a premium rental brand needs it to: a fleet grid filtered by Supercars, SUVs and Grand tourers, with an _available for your dates_ filter that checks live availability, and a page per car.

![A NOIR car page for the Rolls-Royce Cullinan, with a full-width studio shot and "from AED 7,500 per day"](https://media.opencals.com/seo-content/inline/1791620407953-noir-car-page.png)

Car pages have a scroll-driven hero, a spec strip that counts up (power, 0–100, top speed, seats), a gallery, the deposit and driver requirements, a two-month availability calendar and a sticky booking card. The rates page puts the whole fleet in one table: in the demo, daily rates from AED 2,900 for the Range Rover SV to AED 12,500 for the Revuelto, deposits from AED 8,000 to AED 20,000, and 200 or 250 km included per day.

![The NOIR rates table listing each car's daily rate, deposit and included kilometres, with the hovered row's car shown on the right](https://media.opencals.com/seo-content/inline/1791620409731-noir-rates.png)

I'd keep that page even if you change everything else. Rental customers compare. Giving them one honest table with the deposit next to the price saves you the phone call where someone asks why the deposit is AED 20,000.

The motion is heavy on purpose: Lenis smooth scrolling, masked text reveals, parallax, a car that drives in. Every effect has a `prefers-reduced-motion` fallback, and if your brand is quieter than this one, the `components/motion/` folder is where you take it out.

## Chauffeurs are the one part that's an appointment

The chauffeur packages are the exception to everything above. _Airport transfer, 2h, AED 650. An evening in Dubai, 4h, AED 1,800. A day in Abu Dhabi, 10h, AED 4,200. By the hour, AED 450, one to twelve hours._ Those are a real person's time, so they use the ordinary staff flow: pick a time, pick a driver, add extras, fill in details, pay. The preferred car is a request in a custom attribute rather than a reservation, and the site says so in plain words: if the car is out on rental, you offer the closest match.

That split is the useful thing to take away if you're building for another rental business. Physical units go in a range model with no staff; human services go in the slot model with staff. One store, both shapes, and the order history reads correctly either way.

![NOIR on three phones: the homepage with the quick booking bar, a Ferrari Purosangue car page, and the first booking step with a running total](https://media.opencals.com/seo-content/inline/1791620409238-noir-mobile.png)

## Where you'll still be on the phone

Rental has more paperwork around the booking than any vertical I've built a template for, and a booking site only covers some of it. These are the gaps you'll hit.

**The deposit isn't taken online.** NOIR shows the deposit per car and makes the customer tick a box accepting it, but the deposit itself is held on a card by your driver at handover. Nothing pre-authorises it at checkout. If you want the card hold to happen online, that's work the template doesn't do.

**Nobody checks the licence for you.** The uploads land on the booking for a person to look at. There's no identity verification and no automatic age check against the minimum on each car (25 for most of the demo fleet, 30 for some supercars).

**Fuel, tolls, fines and damage are settled afterwards.** There's no check-in inspection, no mileage capture and no automatic charge for the extra kilometres. In the demo those are "settled at cost" at the end, which in practice means you [invoice the customer after the booking](https://opencals.com/blog/invoice-customers-after-booking).

**Changing dates keeps the length.** From their account, a customer can move a rental to different dates with the same number of days, checked against availability, or cancel within the policy. Extending a three-day rental to five is a conversation with you, not a button.

**One price per day.** Price is always days × daily rate + extras. There's no weekend rate, no seasonal pricing and no long-rental discount in the template.

And the two that are true of every Opencals template: **Opencals itself is not open source**, only the templates and the [SDK](https://opencals.com/blog/booking-sdk) are, and **the free Dev Store runs in test mode**, so it can't take real money until you move to a live store.

## What it costs to run behind it

The template is free. The engine it talks to is a paid hosted service, and the two plans suit rental businesses differently (prices from [opencals.com/pricing](https://opencals.com/pricing), checked 10 October 2026).

**Elastic** is $0.99 per booking with no monthly fee and every feature included. At AED 2,900 to 12,500 a day, a dollar a booking is a rounding error, and a quiet month costs nothing. For most independent rental companies this is the plan.

**Build your plan** is $14.98 a month including 300 bookings, then $0.20 each, with features priced as add-ons. A rental site needs at least the **Flexible duration** add-on ($9.98/month) for multi-day bookings, and **Customer self-service** ($14.99/month) if you want customers changing dates from their account. That's $39.95 a month before you've taken a booking, so it only beats Elastic once you're past about 40 rentals a month.

## Swapping Dubai for your city

Two files carry nearly all the business-specific work. `lib/site-config.ts` holds the copy: name, wordmark, currency and timezone, fleet categories, per-car specs and deposits in `fleetContent`, handover windows, delivery zones, FAQs, testimonials, the journal routes and contact details. Car photos, chauffeur portraits, the hero image and the logo come from your Opencals store, not the repo, so changing a car is a dashboard job.

`app/globals.css` holds the design tokens as Tailwind v4 `@theme` properties. The names are shared across every Opencals template, so components keep working when the values change:

```css
@theme {
  --color-bg: #050505;          /* page black */
  --color-ink: #F4F1EA;         /* warm ivory text */
  --color-primary: #C8A96B;     /* champagne accent */
}
```

The stack is deliberately ordinary:

| Layer        | What it uses                                        |
| ------------ | --------------------------------------------------- |
| Framework    | Next.js 15 (App Router)                             |
| Styling      | Tailwind CSS v4, every radius squared to 2px        |
| Type         | Archivo (expanded), Inter Tight, Geist Mono         |
| Motion       | Framer Motion + Lenis                               |
| Forms        | react-hook-form + Zod                               |
| Payments     | Stripe Elements, with cash and no-payment fallbacks |
| Auth         | NextAuth.js v5, passwordless by default             |
| Booking data | Opencals Storefront SDK v0.3.14                     |

`AutoRental` and `Product` structured data, Open Graph cards, `robots.txt` and `sitemap.xml` ship configured.

## Live by this afternoon

1. **Create a Dev Store with the NOIR Drive preset**: Sign up at app.opencals.com, create a Dev Store and pick the NOIR Drive preset (category Car rental) when it asks for a dataset. It seeds the twelve cars on a 24/7 schedule, the garage and delivery locations, the extras, the checkout questions and the four chauffeurs, so the site renders like the demo.
2. **Generate a Storefront API key**: In your Opencals account settings, generate a Storefront API key. It starts with sfk\_ and is scoped to that store.
3. **Deploy to Vercel**: Use the button below. Vercel asks for OPENCALS\_API\_KEY and AUTH\_SECRET: paste the sfk\_ key and any random string. Add a Stripe publishable key when you want card payments, then set url in lib/site-config.ts to your domain.

[Deploy to Vercel](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fletsopencals%2Ftemplate-noir\&env=OPENCALS_API_KEY,AUTH_SECRET\&envDescription=API%20key%20from%20your%20Opencals%20dashboard%20and%20a%20random%20secret%20for%20auth\&project-name=noir-drive\&repository-name=template-noir)

> **Set the Storefront Base URL before the first real booking**
>
> In your Opencals dashboard (Settings → API), set **Storefront Base URL** to your deployed domain. Every emailed link (view booking, change dates, cancel, leave feedback) is built as `{storefrontBaseUrl}/link/{token}`. Left on localhost, they go nowhere.

- [NOIR Drive on GitHub](https://github.com/letsopencals/template-noir): The full source, MIT licensed, with the rental model written up in the README.
- [NOIR Drive live demo](https://template-noir.vercel.app): Pick a car and a date range and run the booking end to end in test mode.
- [VOLT: padel club template](https://opencals.com/blog/templates/volt): The other staffless-resource template: courts booked by duration on a grid.
- [Opencals for developers](https://opencals.com/developers): The Storefront API, SDK and every template.

If you run a rental company, the question NOIR puts to you is whether the step where a customer commits money should happen on your site, today, or in your inbox tomorrow. If you build sites for rental companies, it's a working reference for range-based booking on a [headless backend](https://opencals.com/blog/headless-booking-system), and the README spells out the model so you can lift it into your own design.

## Frequently asked questions

### Is the NOIR car rental template really free?

Yes. The template is MIT licensed, so you can fork it, rebrand it and ship it for clients with no per-site fee. The booking backend is Opencals, a paid hosted service: $0.99 per booking on the Elastic plan, or $14.98 a month including 300 bookings plus add-ons (prices checked 10 October 2026). You can build and demo the whole site free on a Dev Store, which runs in test mode.

### Can customers book a car for several days?

Yes, that's the core of the template. Each car has a one-day base duration with custom durations allowed up to 30 days in the seed. The customer picks a pick-up and return date on a range calendar backed by live availability, and the price is the number of days times the daily rate, plus extras.

### Does NOIR handle delivery and collection?

Yes. Each car is attached to a garage location and a delivery location, and the customer chooses one at booking. Delivery addresses go through the standard delivery-address flow at checkout, and handover and return windows, flight numbers and alternative collection addresses are stored as custom attributes on the booking.

### Does it take the security deposit online?

No. NOIR shows the deposit for each car and requires the customer to accept it at checkout, but the deposit is held on a card at handover by your team. Nothing is pre-authorised online in the template.

### Can I use it for vans, bikes, boats or equipment?

Yes. Nothing in the rental model is specific to cars: any physical unit booked by the day, exclusively, fits. Each unit is a staffless product with a one-day base duration and its own product pool. You'd rewrite the copy and the spec strip in site-config, and the booking logic stays the same.

### Do I need a developer to run it?

Realistically, yes. NOIR is a Next.js codebase, not a site builder. If someone can edit a config file and deploy to Vercel, you're fine. If nobody will touch code, a hosted Opencals booking page gets you online faster.

### How is NOIR different from the other Opencals templates?

Haar, Frisor and Clarity are appointment templates shaped around staff. VOLT books staffless courts by duration on a grid. NOIR is the first built on date ranges: availability is read as merged ranges and checked on the client, bookings run midnight to midnight in the store's timezone, and the handover details travel outside the slot.
