Patterns

Headless controls

The viewer's layer is yours to fill. Leave out the default controls and render your own caption, counter and buttons. They read the state and call the commands.

Loading the live example…

headless.tsx
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox, useFluidbox } from "@ogdakke/fluidbox/react";
import { photos } from "./data";

function Caption() {
  const { item, index, count, hasPrevious, hasNext, previous, next, close } = useFluidbox();
  return (
    <div className="pattern-caption">
      <button type="button" disabled={!hasPrevious} onClick={() => previous()}>
        Previous
      </button>
      <span>
        <strong>{item && itemLabel(item)}</strong>{" "}
        <small>{index === null ? "" : `${index + 1} / ${count}`}</small>
      </span>
      <button type="button" disabled={!hasNext} onClick={() => next()}>
        Next
      </button>
      <button type="button" onClick={() => close()}>
        Close
      </button>
    </div>
  );
}

export function Headless() {
  return (
    <Fluidbox.Root items={photos}>
      <div className="pattern-grid">
        {photos.map((photo) => (
          <Fluidbox.Trigger
            key={photo.src}
            item={photo}
            className="pattern-thumb"
          >
            <img src={photo.src} alt={itemLabel(photo)} />
          </Fluidbox.Trigger>
        ))}
      </div>
      {/* Your own controls and caption replace the default ones. */}
      <Fluidbox.Viewer>
        <Caption />
      </Fluidbox.Viewer>
    </Fluidbox.Root>
  );
}
  • The framework Viewer part renders into the viewer's layer, above the slides, and useFluidbox() gives the state and commands. In plain JavaScript, the viewer option draws into it and subscribes to the state. On custom elements, set gallery.viewer, or put a <template slot="viewer"> in the gallery.
  • Buttons marked data-fluidbox-close, data-fluidbox-previous or data-fluidbox-next get the default styling, and run their command on the custom elements. Your own buttons call close(), previous() and next() directly.
  • Clicks on your buttons, links and inputs inside the viewer never count as zoom taps.