# Custom content

Besides images, a slide can hold a video, an iframe, or your own content. Custom content is an item with `type: "custom"` and `render: "note"`. The viewer looks up the `note` renderer and draws it into the slide.

#### React

`renderers.tsx`

```tsx
import { createContext, useContext } from "react";
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/react";
import { media, theme, thumbnailSrc } from "./data";

const ThemeContext = createContext("light");

/** Custom content renders inside the app, so it reads the app's context. */
function Note() {
  const theme = useContext(ThemeContext);
  return <article className="pattern-note">Theme: {theme}</article>;
}

export function Renderers() {
  return (
    <ThemeContext.Provider value={theme}>
      {/* Items of type "custom" whose render is "note" render <Note />. */}
      <Fluidbox.Root items={media} renderers={{ note: () => <Note /> }}>
        <div className="pattern-grid">
          {media.map((item) => (
            <Fluidbox.Trigger
              key={item.id}
              item={item}
              className="pattern-thumb"
            >
              <img src={thumbnailSrc(item)} alt={itemLabel(item)} />
            </Fluidbox.Trigger>
          ))}
        </div>
      </Fluidbox.Root>
    </ThemeContext.Provider>
  );
}
```

#### Vue

`Renderers.vue`

```vue
<script setup lang="ts">
import { defineComponent, h, inject, provide, type InjectionKey } from "vue";
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox, type FluidboxVueRenderer } from "@ogdakke/fluidbox/vue";
import { media, theme, thumbnailSrc } from "./data";

// Provided here, injected by any component below, such as the note renderer.
const ThemeKey: InjectionKey<string> = Symbol("theme");
provide(ThemeKey, theme);

const Note = defineComponent(() => {
  const current = inject(ThemeKey);
  return () => h("article", { class: "pattern-note" }, `Theme: ${current}`);
});

// Draws items of type "custom" whose render is "note".
const renderers: Record<string, FluidboxVueRenderer> = { note: () => h(Note) };
</script>

<template>
  <Fluidbox.Root :items="media" :renderers="renderers">
    <div class="pattern-grid">
      <Fluidbox.Trigger
        v-for="item in media"
        :key="item.id"
        :item="item"
        class="pattern-thumb"
      >
        <img :src="thumbnailSrc(item)" :alt="itemLabel(item)" />
      </Fluidbox.Trigger>
    </div>
  </Fluidbox.Root>
</template>
```

#### Solid

`renderers.tsx`

```tsx
import { createContext, For, useContext } from "solid-js";
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/solid";
import { media, theme, thumbnailSrc } from "./data";

const ThemeContext = createContext("light");

function Note() {
  // Renderers run under Root, so context provided around it reaches them.
  const theme = useContext(ThemeContext);
  return <article class="pattern-note">Theme: {theme}</article>;
}

export function Renderers() {
  return (
    <ThemeContext.Provider value={theme}>
      {/* Draws items of type "custom" whose render is "note". */}
      <Fluidbox.Root items={media} renderers={{ note: () => <Note /> }}>
        <div class="pattern-grid">
          <For each={media}>
            {(item) => (
              <Fluidbox.Trigger item={item} class="pattern-thumb">
                <img src={thumbnailSrc(item)} alt={itemLabel(item)} />
              </Fluidbox.Trigger>
            )}
          </For>
        </div>
      </Fluidbox.Root>
    </ThemeContext.Provider>
  );
}
```

#### Angular

`renderers.ts`

```ts
import { Component, InjectionToken, inject } from "@angular/core";
import { itemLabel } from "@ogdakke/fluidbox";
import { FluidboxModule } from "@ogdakke/fluidbox/angular";
import { media, theme, thumbnailSrc } from "./data";

export const THEME = new InjectionToken<string>("THEME");

/** Custom content renders inside the root, so it injects from the app. */
@Component({
  selector: "article[appNote]",
  standalone: true,
  host: { class: "pattern-note" },
  template: `Theme: {{ theme }}`,
})
export class Note {
  readonly theme = inject(THEME);
}

@Component({
  selector: "pattern-renderers",
  standalone: true,
  imports: [FluidboxModule],
  providers: [{ provide: THEME, useValue: theme }],
  template: `
    <!-- Items of type "custom" whose render is "note" render <article appNote>. -->
    <div fluidboxRoot [items]="media" [renderers]="renderers" class="pattern-grid">
      @for (item of media; track item.id) {
        <button [fluidboxTrigger]="item" class="pattern-thumb">
          <img [src]="thumbnailSrc(item)" [alt]="label(item)" />
        </button>
      }
    </div>
  `,
})
export class Renderers {
  readonly media = media;
  readonly renderers = { note: Note };
  readonly label = itemLabel;
  readonly thumbnailSrc = thumbnailSrc;
}
```

#### Elements

`renderers.html`

```html
<fluidbox-gallery class="pattern-grid">
  <button
    type="button"
    class="pattern-thumb"
    data-fluidbox-item-id="media-note"
  >
    <img src="/art/meadow.svg" alt="A note" />
  </button>
  <button type="button" class="pattern-thumb" data-fluidbox-item-id="media-map">
    <img src="/map/preview.svg" alt="Helsinki Market Square on OpenStreetMap" />
  </button>
  <button
    type="button"
    class="pattern-thumb"
    data-fluidbox-item-id="media-video"
  >
    <img src="/video/drift-poster.jpg" alt="Drifting clouds" />
  </button>
  <button
    type="button"
    class="pattern-thumb"
    data-fluidbox-item-id="media-youtube"
  >
    <img
      src="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg"
      alt="Rick Astley - Never Gonna Give You Up"
    />
  </button>
</fluidbox-gallery>
```

`renderers.ts`

```ts
import { media, theme } from "./data";

export function renderers(root: HTMLElement) {
  const gallery = root.querySelector("fluidbox-gallery")!;
  // Items from JS; the buttons marked data-fluidbox-item-id open them.
  gallery.items = media;
  gallery.renderers = {
    note({ target }) {
      const note = document.createElement("article");
      note.className = "pattern-note";
      note.textContent = `Theme: ${theme}`;
      target.append(note);
    },
  };
}
```

#### JavaScript

`renderers.ts`

```ts
import { createFluidbox, itemLabel, type FluidboxRenderer } from "@ogdakke/fluidbox";
import { media, theme, thumbnailSrc } from "./data";

// Draws items of type "custom" whose render is "note".
const note: FluidboxRenderer = ({ target }) => {
  const article = document.createElement("article");
  article.className = "pattern-note";
  article.textContent = `Theme: ${theme}`;
  target.append(article);
  return () => article.remove();
};

export function mountRenderers(root: HTMLElement) {
  const fluidbox = createFluidbox({ items: media, renderers: { note } });

  const grid = document.createElement("div");
  grid.className = "pattern-grid";
  media.forEach((item, index) => {
    const button = document.createElement("button");
    button.type = "button";
    button.className = "pattern-thumb";
    button.append(Object.assign(new Image(), { src: thumbnailSrc(item), alt: itemLabel(item) }));
    grid.append(button);
    fluidbox.bind(button, index);
  });
  root.append(grid);

  return () => {
    fluidbox.destroy();
    grid.remove();
  };
}
```

- **Framework components keep their context.** React and Vue render custom slides through portals inside `Root`, and Solid renders them under `Root`'s owner, so context, provide/inject and stores work. Angular renders an `ng-template`, which keeps the injector of the place you declare it.
- **Plain renderers** are functions that get `{ item, index, target, signal }`, append to `target`, and may return a function that releases their own resources. The viewer empties `target` itself.
- **A YouTube embed** is an iframe item. YouTube won't play an embed that sends no referrer, so the example data sets `referrerPolicy: "strict-origin-when-cross-origin"` on it.
- **Markup.** On custom elements, any element other than an image, video or iframe in a `<fluidbox-item>`'s content template is custom content. With `gallery.items`, set `gallery.renderers`.
- Every type needs `width` and `height` so the viewer can size the slide before anything loads. Videos show their `poster` until they play.

See [Media types](https://fluidbox.dwe.workers.dev/docs/media.md) for every field.
