diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
new file mode 100644
index 0000000..00fb810
--- /dev/null
+++ b/src/components/Modal/Modal.tsx
@@ -0,0 +1,103 @@
+import { ReactNode } from "react";
+import { Backdrop } from "../Backdrop/Backdrop";
+import { Button } from "../Button/Button";
+import { classnames } from "../utils/classnames";
+import "./modal.css";
+
+type Props = {
+ open: boolean;
+
+ /**
+ * Event triggered whenever the close button is clicked.
+ */
+ onClose: () => void;
+
+ /**
+ * If true the close button will be displayed
+ * Default: true
+ */
+ displayCloseButton?: boolean;
+
+ /**
+ * If true, the action button will be disabled.
+ * Default: false
+ */
+ displayActionButton?: boolean;
+
+ /**
+ * Event triggered whenever the action button is clicked.
+ * The action button should be considered the "primary" action.
+ */
+ onAction?: () => void;
+
+ /**
+ * Change the label of the close button.
+ * Default: Close
+ */
+ labelCloseButton?: string;
+
+ /**
+ * If true, the disable button will be disabled.
+ */
+ disableCloseButton?: boolean;
+
+ /**
+ * Change the label of the close button.
+ * Default: Action
+ */
+ labelActionButton?: string;
+
+ /**
+ * If true, the action button will be disabled.
+ */
+ disableActionButton?: boolean;
+
+ children: ReactNode;
+};
+
+export function Modal({
+ open,
+ onClose,
+ disableActionButton,
+ disableCloseButton,
+ displayCloseButton = true,
+ displayActionButton = false,
+ labelActionButton = "Action",
+ labelCloseButton = "Close",
+ children,
+ onAction,
+}: Props) {
+ return (
+ <>
+
+
+
+
{children}
+
+
+ {displayCloseButton && (
+
+ )}
+
+ {displayActionButton && (
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/components/Modal/modal.css b/src/components/Modal/modal.css
new file mode 100644
index 0000000..dc38aee
--- /dev/null
+++ b/src/components/Modal/modal.css
@@ -0,0 +1,56 @@
+.modal {
+ transition: transform 0.15s;
+ max-width: 800px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ opacity: 0;
+ z-index: -1;
+ max-height: 100%;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%) scale(0);
+ position: fixed;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--codex-background);
+ padding: 1.5rem;
+ border-radius: var(--codex-border-radius);
+}
+
+.modal--open {
+ transform: translate(-50%, -50%) scale(1);
+}
+
+.modal--open {
+ opacity: 1;
+ z-index: 10;
+}
+
+.modal-buttons--center {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: center;
+}
+
+.modal-buttons--between {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: space-between;
+}
+
+.modal-body {
+ flex: 1;
+}
+
+.modal-title {
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 1rem;
+ font-size: 1.25rem;
+}
+
+@media (min-width: 801px) {
+ .modal {
+ min-width: 500px;
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 95742f6..a0f9eec 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -34,3 +34,4 @@ export { Collapse } from "./components/Collapse/Collapse";
export { Placeholder } from "./components/Placeholder/Placeholder";
export { Sheets } from "./components/Sheets/Sheets";
export { Tabs } from "./components/Tabs/Tabs";
+export { Modal } from "./components/Modal/Modal";
diff --git a/stories/Modal.stories.tsx b/stories/Modal.stories.tsx
new file mode 100644
index 0000000..e79760d
--- /dev/null
+++ b/stories/Modal.stories.tsx
@@ -0,0 +1,97 @@
+import type { Meta } from "@storybook/react";
+import { useState } from "react";
+import { Modal } from "../src/components/Modal/Modal";
+import { fn } from "@storybook/test";
+
+const meta = {
+ title: "Overlays/Modal",
+ component: Modal,
+ parameters: {
+ layout: "fullscreen",
+ inlineStories: false,
+ },
+ tags: ["autodocs"],
+ argTypes: {},
+ args: {
+ onAction: fn(),
+ onClose: fn(),
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Props = {
+ onAction: () => void;
+ onClose: () => void;
+};
+
+const Template = (props: Props) => {
+ const [open, setOpen] = useState(false);
+
+ const onOpen = () => setOpen(true);
+
+ const onClose = () => {
+ props.onClose();
+ setOpen(false);
+ };
+
+ return (
+
+
+
+ Hello world
+
+
+ );
+};
+
+export const Default = Template.bind({});
+
+const ActionTemplate = (props: Props) => {
+ const [open, setOpen] = useState(false);
+
+ const onOpen = () => setOpen(true);
+
+ const onClose = () => {
+ props.onClose();
+ setOpen(false);
+ };
+
+ const onAction = () => {
+ props.onAction();
+ setOpen(false);
+ };
+
+ return (
+
+
+
+ Hello world
+
+
+ );
+};
+
+export const Action = ActionTemplate.bind({});
+
+const NoButtonTemplate = (props: Props) => {
+ const [open, setOpen] = useState(false);
+
+ const onOpen = () => setOpen(true);
+
+ const onClose = () => {
+ props.onClose();
+ setOpen(false);
+ };
+
+ return (
+
+
+
+ Hello world
+
+
+ );
+};
+
+export const NoButton = NoButtonTemplate.bind({});