Room Detail with Reserve Card

A linen bed facing a tall arched window that opens onto the sea

In the room

  • Private sea terrace
  • Stone bathtub and rain shower
  • Breakfast served in the room
  • Ceiling fan and quiet air
  • Bed turned down each evening
  • Fast wifi, no television

Sea Suite

Aroomthatopensontothecove.

The suite takes the whole lower floor of the east wing. An arched door leads to a private terrace, the bath is carved from a single block of island stone, and in the morning the light arrives before you do.

Size
42 m²
Sleeps
2 guests
Bed
King, linen
View
Sea and islet

€480a night

Minimum stay: 3 nights

Nothing is charged until 14 days before you arrive.

About this block

Room Detail with Reserve CardPRO

A single room page: a crossfading photo gallery with thumbnails and an amenities list on the left, and a sticky column with a serif title, a ruled facts grid and a reserve card whose range calendar works out the nights and total live, flag a minimum stay and submit to your booking engine.

Hotel4: Room Detail with Reserve Card

A single room page with a crossfading photo gallery, thumbnails and an icon list of amenities on the left, and a sticky column on the right holding a serif title, a ruled facts grid and a reserve card that counts nights and the total as soon as dates are picked.

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/hotel4?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

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

This installs the block to components/beste/block/hotel4.tsx plus the button22 sliding-marker pill used for the reserve button, the text1 word stagger behind the title, and the shadcn/ui calendar and popover primitives that hold the date range picker in the reserve card.

Quick start

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

tsx
import { Hotel4, hotel4Demo } from "@/components/beste/block/hotel4";

export default function Page() {
  return <Hotel4 {...hotel4Demo} />;
}

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

tsx
import { Bath, Coffee, Waves } from "lucide-react";
import { Hotel4 } from "@/components/beste/block/hotel4";

export default function Page() {
  return (
    <Hotel4
      eyebrow="Sea Suite"
      heading="A room that opens onto the cove."
      description="An arched door leads to a private terrace, and the bath is carved from island stone."
      images={[
        { src: "https://oud.pics/b/hotel/ostria-sea-suite.webp", alt: "A linen bed facing a tall arched window onto the sea" },
        { src: "https://oud.pics/b/hotel/ostria-stone-bath.webp", alt: "A bathtub carved from pale stone beside an arched window" },
      ]}
      facts={[
        { label: "Size", value: "42 m²" },
        { label: "Sleeps", value: "2 guests" },
      ]}
      amenities={[
        { icon: Waves, label: "Private sea terrace" },
        { icon: Bath, label: "Stone bathtub and rain shower" },
        { icon: Coffee, label: "Breakfast served in the room" },
      ]}
      booking={{
        action: "https://booking.example.com/ostria",
        params: { checkIn: "checkin", checkOut: "checkout", room: "room" },
        roomCode: "sea-suite",
        rate: 480,
        currency: "€",
        minNights: 3,
        submitLabel: "Reserve this suite",
        note: "Nothing is charged until 14 days before you arrive.",
      }}
      labels={{
        perNight: "a night",
        checkIn: "Arrive",
        checkOut: "Leave",
        pick: "Add a date",
        nights: { one: "night", other: "nights" },
        total: "Total",
        minStay: "Minimum stay",
        amenities: "In the room",
        showPhoto: "Show photo",
      }}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstring–Uppercase line above the title
headingstring–Serif <h1>, staggered word by word through Text1
descriptionstring–Paragraph under the title
imagesImage[][]Gallery photographs; the first is shown on load
factsFact[][]Label and value pairs in the two-column ruled grid
amenitiesAmenity[][]Icon list under the gallery
bookingBooking–Reserve card with rate, dates and submit; hidden when omitted
labelsLabels{}Text used in the card, the amenities title and thumbnail labels
classNamestring–Extra classes for the outer <section>
ts
type Image = {
  src: string;
  alt: string;
};

type Fact = {
  label: string;
  value: string;
};

type Amenity = {
  icon: LucideIcon;
  label: string;
};

type Booking = {
  action: string;
  params?: { checkIn?: string; checkOut?: string; room?: string };
  roomCode?: string;
  rate: number;
  currency: string;
  minNights?: number;
  submitLabel: string;
  note?: string;
};

type Labels = {
  perNight?: string;
  checkIn?: string;
  checkOut?: string;
  pick?: string;
  nights?: { one: string; other: string };
  total?: string;
  minStay?: string;
  amenities?: string;
  showPhoto?: string;
};

Behavior notes

  • The first photo fades in from a blur only after it has loaded. Clicking a thumbnail crossfades the main frame to that image while it scales down from 1.04. The active thumbnail is outlined and carries aria-current, the rest sit at 55% opacity. Thumbnails only render when there is more than one image.
  • The dates field opens a range calendar in a popover: two months from the medium breakpoint, one below, days before today disabled, and it closes once a departure after the arrival is picked.
  • Once a range exists, a polite live region shows the rate times the number of nights and the total, formatted as currency plus an en-US number. When minNights is set, the minimum stay line turns full strength and the submit button is disabled while the picked stay is shorter.
  • The form submits with GET to booking.action, with local YYYY-MM-DD dates in hidden inputs named by params.checkIn and params.checkOut (default checkin and checkout). roomCode, when set, goes out under params.room (default room). Submitting without both dates opens the calendar instead.
  • From the large breakpoint the layout is a 7 and 5 column split and the right column is sticky. Below it everything stacks, and the amenities grid is two columns from the small breakpoint.
  • The amenities block fades in when it scrolls into view (once). Its title only renders when labels.amenities is set. Reduced motion skips every entrance and blur.

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

hotel3

Room Collection List

PRO

hotel45

Gift a Stay List

PRO

hotel11

Seasonal Stay Offers

Get Pro