
Sea Suite
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.
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.
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.
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
npx shadcn add "https://beste.dev/r/hotel4?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
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.
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.
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:
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",
}}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
eyebrow | string | – | Uppercase line above the title |
heading | string | – | Serif <h1>, staggered word by word through Text1 |
description | string | – | Paragraph under the title |
images | Image[] | [] | Gallery photographs; the first is shown on load |
facts | Fact[] | [] | Label and value pairs in the two-column ruled grid |
amenities | Amenity[] | [] | Icon list under the gallery |
booking | Booking | – | Reserve card with rate, dates and submit; hidden when omitted |
labels | Labels | {} | Text used in the card, the amenities title and thumbnail labels |
className | string | – | Extra classes for the outer <section> |
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;
};aria-current, the rest sit at 55% opacity. Thumbnails only render when there is more than one image.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.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.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.