Shown at forty lux on the conservator's instruction. The viewer follows the same rule: it comes up slowly.
Inverted low-light room where staggered frames hang on a dark panel, opening a viewer with no chrome at all: the work fades up like a lamp on a dimmer, the caption follows it, and a dot rail walks the hang.
A room kept dark so the work can be seen: the section itself is an inverted panel, and the viewer brings each work up like a lamp on a dimmer, with a dot rail instead of arrows and the whole plate as the way out.
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://ui.beste.co/r/gallery19?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/gallery19?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/gallery19.tsx plus the badge7 eyebrow it uses.
The installed file exports gallery19Demo alongside the block: the exact props behind the preview above. Spread it to get a working gallery in one line.
import { Gallery19, gallery19Demo } from "@/components/beste/block/gallery19";
export default function Page() {
return <Gallery19 {...gallery19Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Gallery19 } from "@/components/beste/block/gallery19";
export default function Page() {
return (
<Gallery19
badge={{ label: "Low light" }}
heading="A room kept dark so the work can be seen."
note="Shown at forty lux on the conservator's instruction."
frames={[
{
src: "/dark/stair.jpg",
alt: "Stairwell with one window",
title: "Stair, One Window",
artist: "Idris Kane",
meta: "Silver gelatin print, 2023",
},
{
src: "/dark/ribs.jpg",
alt: "Ceiling ribs from below",
title: "Ribs, From Below",
artist: "Marta Ekelund",
meta: "Pigment print, 2024",
},
]}
labels={{
openLabel: "Bring this work up",
closeLabel: "Bring the lights back",
jumpLabel: "Go to this work",
counterSeparator: "of",
}}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
badge | { label: string } | – | Eyebrow label rendered as a Badge7 |
heading | string | – | Centred section heading inside the dark panel |
note | string | – | Centred paragraph under the heading |
frames | Frame[] | [] | Works in the room, in order |
labels | Gallery19Labels | {} | Fixed strings that are not content: viewer controls, the dot rail label and the counter separator |
lightbox | boolean | true | Render the built-in viewer; set false to leave the frames static |
className | string | – | Extra classes for the outer <section> |
type Frame = {
src: string;
alt: string;
title: string;
artist: string;
meta: string;
};
type Gallery19Labels = {
openLabel?: string;
closeLabel?: string;
jumpLabel?: string;
counterSeparator?: string;
};requestAnimationFrame, and it goes dark again for a moment whenever the work changes, so every change reads as a light being brought up rather than a swap.delay-200, so the picture arrives first and its credit follows.stopPropagation to jump between works without closing the viewer underneath.text-background tokens are hardcoded rather than adaptive, and the Badge7 is passed className="text-background/70" for the same reason.lg:mt-10, so the wall reads as hung at two heights rather than as a straight row.lightbox={false} disables every frame button and switches the cursor back to default, leaving the dark room as a static wall.