> ## Documentation Index
> Fetch the complete documentation index at: https://discord-markm-docs-lobby-membership-disconnect.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Using Modal Components

> Guide to creating and displaying modal dialogs with text input fields.

export const YouTubeEmbed = ({src, title = "YouTube video player", showDuration = false, chapters = ""}) => {
  const iframeRef = useRef(null);
  const detailsRef = useRef(null);
  const [duration, setDuration] = useState(null);
  const [activeChapter, setActiveChapter] = useState(-1);
  const formatDuration = seconds => {
    const h = Math.floor(seconds / 3600);
    const m = Math.floor(seconds % 3600 / 60);
    const s = Math.round(seconds % 60);
    const pad = n => String(n).padStart(2, "0");
    return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
  };
  const parsedChapters = useMemo(() => {
    if (!chapters) return [];
    return chapters.split("\n").map(line => {
      const match = line.match(/^\s*[-*•]?\s*(?:(\d+):)?(\d{1,2}):(\d{2})\s*[-–—:]?\s*(\S.*)$/);
      if (!match) return null;
      const [, h, m, s, chapterTitle] = match;
      const seconds = (h ? parseInt(h, 10) * 3600 : 0) + parseInt(m, 10) * 60 + parseInt(s, 10);
      return {
        seconds,
        label: formatDuration(seconds),
        title: chapterTitle.trim()
      };
    }).filter(Boolean);
  }, [chapters]);
  const jsApiEnabled = showDuration || parsedChapters.length > 0;
  useEffect(() => {
    if (parsedChapters.length === 0) return;
    const onPointerDown = event => {
      const details = detailsRef.current;
      if (details?.open && !details.contains(event.target)) details.open = false;
    };
    document.addEventListener("pointerdown", onPointerDown);
    return () => document.removeEventListener("pointerdown", onPointerDown);
  }, [parsedChapters.length]);
  useEffect(() => {
    if (!jsApiEnabled) return;
    const onMessage = event => {
      if (event.origin !== "https://www.youtube.com") return;
      if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) return;
      let data;
      try {
        data = JSON.parse(event.data);
      } catch {
        return;
      }
      const seconds = data?.info?.duration;
      if (typeof seconds === "number" && seconds > 0) setDuration(seconds);
      const currentTime = data?.info?.currentTime;
      if (typeof currentTime === "number" && parsedChapters.length > 0) {
        let index = -1;
        for (let i = 0; i < parsedChapters.length; i++) {
          if (parsedChapters[i].seconds <= currentTime) index = i;
        }
        setActiveChapter(index);
      }
    };
    window.addEventListener("message", onMessage);
    return () => window.removeEventListener("message", onMessage);
  }, [jsApiEnabled, parsedChapters]);
  const postToPlayer = payload => {
    iframeRef.current?.contentWindow?.postMessage(JSON.stringify(payload), "https://www.youtube.com");
  };
  const handleLoad = () => {
    postToPlayer({
      event: "listening",
      id: "ytembed",
      channel: "widget"
    });
  };
  const seekTo = seconds => {
    postToPlayer({
      event: "command",
      func: "seekTo",
      args: [seconds, true]
    });
    postToPlayer({
      event: "command",
      func: "playVideo",
      args: []
    });
  };
  const embedSrc = jsApiEnabled ? `${src}${src.includes("?") ? "&" : "?"}enablejsapi=1` : src;
  return <div>
      <iframe ref={iframeRef} className="w-full aspect-video rounded-xl" src={embedSrc} title={title} frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen onLoad={jsApiEnabled ? handleLoad : undefined}></iframe>
      {(showDuration || parsedChapters.length > 0) && <div className="relative mt-2 flex items-start justify-between gap-4 text-sm text-gray-500 dark:text-zinc-500">
          {parsedChapters.length > 0 && <details ref={detailsRef}>
              <summary className="cursor-pointer select-none">Chapters</summary>
              <ul className="absolute left-0 top-full z-10 mt-1 mb-0 max-h-72 w-max max-w-full list-none overflow-y-auto rounded-xl border border-gray-200 bg-white p-3 pl-3 shadow-lg dark:border-zinc-700 dark:bg-zinc-800">
                {parsedChapters.map((chapter, index) => <li key={chapter.seconds} className="m-0 pl-0">
                    <button type="button" className={`cursor-pointer rounded-md px-1.5 py-0.5 text-left hover:text-gray-700 dark:hover:text-zinc-300 ${index === activeChapter ? "bg-gray-100 text-gray-800 dark:bg-zinc-700 dark:text-zinc-200" : ""}`} onClick={() => {
    seekTo(chapter.seconds);
    if (detailsRef.current) detailsRef.current.open = false;
  }}>
                      <span className="font-medium text-primary dark:text-primary-light">
                        {chapter.label}
                      </span>{" "}
                      {chapter.title}
                    </button>
                  </li>)}
              </ul>
            </details>}
          {showDuration && duration !== null && <p className="m-0 ml-auto">Duration: {formatDuration(duration)}</p>}
        </div>}
    </div>;
};

## Overview

Modal components are a great way to collect freeform information from your users.

<YouTubeEmbed src="https://www.youtube.com/embed/N3MMLtbNzUU" title="Discord Modal Components: Introducing Pop-up Forms for Your Bots!" showDuration />

### Prerequisites

* You must have a Discord account and be a member of the Discord Developer Portal.
* You must have a Discord application created in the Discord Developer Portal.

***

## Displaying a Modal

Displaying a modal can be done in response to an [interaction](/developers/interactions/receiving-and-responding). When displaying a modal you'll use an [interaction response](/developers/interactions/receiving-and-responding#interaction-response-object) with the [`MODAL`](/developers/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type) interaction callback type and [modal fields](/developers/interactions/receiving-and-responding#interaction-response-object-modal).

An example of a modal with a [String Select](/developers/components/reference#string-select) and [Text Input](/developers/components/reference#text-input) both wrapped in [Labels](/developers/components/reference#label):

```json theme={null}
{
  "type": 9,
  "data": {
    "custom_id": "bug_modal",
    "title": "Bug Report",
    "components": [
      {
        "type": 18,
        "label": "What's your favorite bug?",
        "component": {
          "type": 3,
          "custom_id": "bug_string_select",
          "placeholder": "Choose...",
          "options": [
            {
              "label": "Ant",
              "value": "ant",
              "description": "(best option)",
              "emoji": {
                "name": "🐜"
              }
            },
            {
              "label": "Butterfly",
              "value": "butterfly",
              "emoji": {
                "name": "🦋"
              }
            },
            {
              "label": "Caterpillar",
              "value": "caterpillar",
              "emoji": {
                "name": "🐛"
              }
            }
          ]
        }
      },
      {
        "type": 18,
        "label": "Why is it your favorite?",
        "description": "Please provide as much detail as possible!",
        "component": {
          "type": 4,
          "custom_id": "bug_explanation",
          "style": 2,
          "min_length": 1000,
          "max_length": 4000,
          "placeholder": "Write your explanation here...",
          "required": true
        }
      }
    ]
  }
}
```

<img src="https://mintcdn.com/discord-markm-docs-lobby-membership-disconnect/vPjUBH8YmfYT11bk/images/components/modal.webp?fit=max&auto=format&n=vPjUBH8YmfYT11bk&q=85&s=feeb17f583ecd1ba7430474671c5c8a4" alt="Example of an Action Row with three buttons" style={{width: "auto", height: "350px"}} width="1024" height="1026" data-path="images/components/modal.webp" />
