diff --git a/.changeset/swift-apricots-double.md b/.changeset/swift-apricots-double.md
new file mode 100644
index 000000000..a845151cc
--- /dev/null
+++ b/.changeset/swift-apricots-double.md
@@ -0,0 +1,2 @@
+---
+---
diff --git a/packages/wonder-blocks-modal/src/components/__docs__/modal-dialog.stories.js b/packages/wonder-blocks-modal/src/components/__docs__/modal-dialog.stories.js
new file mode 100644
index 000000000..66bb7631c
--- /dev/null
+++ b/packages/wonder-blocks-modal/src/components/__docs__/modal-dialog.stories.js
@@ -0,0 +1,287 @@
+// @flow
+import * as React from "react";
+import {StyleSheet} from "aphrodite";
+
+import Button from "@khanacademy/wonder-blocks-button";
+import Color from "@khanacademy/wonder-blocks-color";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {Strut} from "@khanacademy/wonder-blocks-layout";
+import {
+ ModalLauncher,
+ ModalDialog,
+ ModalPanel,
+} from "@khanacademy/wonder-blocks-modal";
+import Spacing from "@khanacademy/wonder-blocks-spacing";
+import {Body, Title} from "@khanacademy/wonder-blocks-typography";
+
+import type {StoryComponentType} from "@storybook/react";
+
+import ComponentInfo from "../../../../../.storybook/components/component-info.js";
+import {name, version} from "../../../package.json";
+
+const customViewports = {
+ phone: {
+ name: "phone",
+ styles: {
+ width: "320px",
+ height: "568px",
+ },
+ },
+ tablet: {
+ name: "tablet",
+ styles: {
+ width: "640px",
+ height: "960px",
+ },
+ },
+ desktop: {
+ name: "desktop",
+ styles: {
+ width: "1024px",
+ height: "768px",
+ },
+ },
+};
+
+export default {
+ title: "Modal/Building Blocks/ModalDialog",
+ component: ModalDialog,
+ parameters: {
+ componentSubtitle: ((
+
+ ): any),
+ viewport: {
+ viewports: customViewports,
+ defaultViewport: "desktop",
+ },
+ chromatic: {
+ viewports: [320, 640, 1024],
+ },
+ },
+ // Make the following props null in the control panel
+ // because setting an object for a React node is
+ // not practical to do manually.
+ argTypes: {
+ children: {
+ control: {type: null},
+ },
+ above: {
+ control: {type: null},
+ },
+ below: {
+ control: {type: null},
+ },
+ },
+};
+
+export const Default: StoryComponentType = (args) => (
+
+
+
+
+ Modal Title
+
+ Here is some text in the modal.
+ >
+ }
+ />
+
+
+
+);
+
+Default.args = {
+ style: {
+ maxWidth: 500,
+ maxHeight: 500,
+ },
+};
+
+export const Simple: StoryComponentType = () => (
+
+
+
+
+ Modal Title
+
+ Here is some text in the modal.
+ >
+ }
+ />
+
+
+
+);
+
+Simple.parameters = {
+ docs: {
+ storyDescription: `This is a basic \`\` that wraps a
+ \`\` element. The \`\` is just a a wrapper
+ for the visual components of the overall modal. It sets
+ the modal's role to \`"dialog"\`. If it did not have another
+ element as a child here (a \`\` in this case),
+ nothing would be visible. If the \`\` were not given
+ a \`maxHeight\` or \`maxWidth\` style, it would take up the
+ entire viewport. To demonstrate the difference between
+ the \`\` and the \`\` elements, the panel
+ has been given a smaller height and width than \`\`,
+ and \`\` has been given a dark blue background.`,
+ },
+};
+
+export const WithAboveAndBelow: StoryComponentType = () => {
+ const aboveStyle = {
+ background: "url(./modal-above.png)",
+ width: 874,
+ height: 551,
+ position: "absolute",
+ top: 40,
+ left: -140,
+ };
+
+ const belowStyle = {
+ background: "url(./modal-below.png)",
+ width: 868,
+ height: 521,
+ position: "absolute",
+ top: -100,
+ left: -300,
+ };
+
+ return (
+
+
+ }
+ below={}
+ >
+
+ Modal Title
+
+ Here is some text in the modal.
+ >
+ }
+ />
+
+
+
+ );
+};
+
+WithAboveAndBelow.parameters = {
+ docs: {
+ storyDescription: `The \`above\` and \`below\` props work the same
+ for \`\` as they do for \`\`.
+ The element passed into the \`above\` prop is rendered in front
+ of the modal. The element passed into the \`below\` prop is
+ rendered behind the modal. In this example, a \`\` element
+ with a background image of a person and an orange blob is passed
+ into the \`below\` prop. A \`\` element with a background
+ image of an arc and a blue semicircle is passed into the \`above\`
+ prop. This results in the person's head and the orange blob
+ peeking out from behind the modal, and the arc and semicircle
+ going over the front of the modal.`,
+ },
+};
+
+export const WithLauncher: StoryComponentType = () => {
+ type MyModalProps = {|
+ closeModal: () => void,
+ |};
+
+ const MyModal = ({
+ closeModal,
+ }: MyModalProps): React.Element => (
+
+
+ Modal Title
+
+ Here is some text in the modal.
+ >
+ }
+ />
+
+ );
+
+ return (
+
+ {({openModal}) => (
+
+ )}
+
+ );
+};
+
+WithLauncher.parameters = {
+ chromatic: {
+ // Don't take screenshots of this story since it would only show a
+ // button and not the actual modal.
+ disableSnapshot: true,
+ },
+ docs: {
+ storyDescription: `A modal can be launched using a launcher. Here,
+ the launcher is a \`