Nightly Rate Calendar

Rates and dates

Pickyournights.Thepriceistheprice.

Every rate below is for the Sea Suite with breakfast, and it is the same one you would find anywhere else, only lower. Choose an arrival, then a departure.

Lowest rate this month

June 2027
ArriveChoose a date
About this block

Nightly Rate CalendarPRO

A nightly rate calendar for direct bookings built on the shadcn calendar: two months side by side, every night shows its own price, the lowest rate of each month carries a dot and full nights are struck through. Picking an arrival then a departure skips stays that cross a full night, sums the total, enforces a minimum and hands the dates to your booking engine.

Hotel5: Nightly Rate Calendar

An open rate calendar where every night shows its own price under the date, the cheapest nights of each month carry a small dot and full nights are struck through, with a footer that reads back the stay, sums the total and hands the dates to your booking engine.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://beste.dev/r/hotel5?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://beste.dev/r-base/hotel5?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/hotel5.tsx plus the button22 sliding-marker pill used for the submit button, the text1 word stagger behind the heading, and the shadcn/ui calendar primitive whose day buttons are replaced to print each night's rate.

Quick start

The installed file exports hotel5Demo alongside the block: the exact props behind the preview above. Spread it to get a working section in one line.

tsx
import { Hotel5, hotel5Demo } from "@/components/beste/block/hotel5";

export default function Page() {
  return <Hotel5 {...hotel5Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Hotel5 } from "@/components/beste/block/hotel5";

export default function Page() {
  return (
    <Hotel5
      eyebrow="Rates and dates"
      heading="Pick your nights. The price is the price."
      description="Every rate below is for the Sea Suite with breakfast. Choose an arrival, then a departure."
      months={[
        {
          month: "2027-06",
          rates: [440, 420, 400, 480, 480, null, null, 440, 460, 420, 520, 480, 420, 420, 400, 400, 420, 480, null, null, 400, 420, 460, 420, 480, 520, 420, 460, 460, 400],
        },
      ]}
      currency="€"
      minNights={2}
      booking={{
        action: "https://booking.example.com/ostria",
        params: { checkIn: "checkin", checkOut: "checkout" },
        submitLabel: "Continue to booking",
      }}
      labels={{
        arrive: "Arrive",
        leave: "Leave",
        pick: "Choose a date",
        pickLeave: "Now choose when you leave",
        nights: { one: "night", other: "nights" },
        total: "total with breakfast",
        lowest: "Lowest rate this month",
        soldOut: "Full",
        minStay: "Two nights at least",
      }}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstring–Uppercase line above the heading
headingstring–Serif <h2>, staggered word by word through Text1
descriptionstring–Paragraph to the right of the heading
monthsRateMonth[][]One entry per bookable month with a rate for each night
currencystring""Prefix for the summed total
minNightsnumber1Shortest stay that can be submitted
bookingBooking–Footer form with the stay readout and submit pill; hidden when omitted
labelsLabels{}Text for the footer, the legend and full nights
classNamestring–Extra classes for the outer <section>
ts
type RateMonth = {
  month: string; // "YYYY-MM"
  rates: (number | null)[]; // index 0 is the 1st; null is a full night
};

type Booking = {
  action: string;
  params?: { checkIn?: string; checkOut?: string };
  submitLabel: string;
};

type Labels = {
  arrive?: string;
  leave?: string;
  pick?: string;
  pickLeave?: string;
  nights?: { one: string; other: string };
  total?: string;
  lowest?: string;
  soldOut?: string;
  minStay?: string;
};

Behavior notes

  • The calendar is bounded to the first and last entry in months. Days without an entry are disabled, a null rate marks a full night (struck through, showing labels.soldOut), and every night whose rate equals the lowest open rate of its month gets a dot, explained by labels.lowest above the grid.
  • It shows two months side by side from the medium breakpoint and one below it, with larger day cells on wider screens.
  • The first click sets the arrival (full nights cannot be an arrival). A second click later than it sets the departure only if every night in between has a rate; otherwise that day becomes the new arrival. Clicking again after a full range starts over.
  • The total adds up each night's own rate, from the arrival up to the night before departure. A polite live region shows the arrival, departure, and the night count with the total, or labels.minStay while the stay is shorter than minNights.
  • The form submits with GET to booking.action. Hidden inputs named by params.checkIn and params.checkOut (default checkin and checkout) carry local YYYY-MM-DD dates, and the submit button stays disabled until a departure is picked and the minimum is met.
  • The heading block and calendar fade up as they enter the viewport (once). Reduced motion skips the entrances.

Wiring the form up

The form posts to the action you pass. To validate on the client and handle submission in React instead, see React Hook Form, TanStack Form, and Formisch.

More Hotel blocks

View all Hotel
PRO

hotel27

Seasons to Visit

PRO

hotel49

When to Come Year Chart

PRO

hotel4

Room Detail with Reserve Card

Get Pro