Modals surface critical information or confirm important actions without navigating away from the current context. In Aggarly Platform they must be calm, accessible, and consistent: strong focus management, token-driven surfaces, clear headers/footers, and predictable sizing across responsive breakpoints.
A focused confirmation pattern with explicit actions. Backdrop click and Escape close are supported by default.
import { useState } from "react";
import { Modal } from "@/design-system/components/Modal";
import { Button } from "@/design-system/components/Button";
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button size="sm" variant="primary" onClick={() => setOpen(true)}>
Open confirm modal
</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
title="End session?"
showCloseButton
footer={
<>
<Button size="sm" variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button size="sm" variant="danger" onClick={() => setOpen(false)}>
End session
</Button>
</>
}
>
<p>
Are you sure you want to end this session? All unsaved notes will be lost.
</p>
</Modal>
</>
);
}
Use for quick create/edit flows. The first field can receive focus via data-autofocus. Footer actions remain consistent with Buttons.
import { useState } from "react";
import { Modal } from "@/design-system/components/Modal";
import { Button } from "@/design-system/components/Button";
import { Input } from "@/design-system/components/Input";
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button size="sm" variant="primary" onClick={() => setOpen(true)}>
Add student
</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
title="Add student"
scrollable
showCloseButton={false}
footer={
<>
<Button size="sm" variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button size="sm" variant="primary" onClick={() => setOpen(false)}>
Save
</Button>
</>
}
>
<form className="modal-form" onSubmit={(e) => e.preventDefault()}>
<Input label="Student name" placeholder="Full name" data-autofocus />
<Input label="Workspace member email" type="email" placeholder="name@domain.com" />
<Input label="Notes" placeholder="Optional notes" />
</form>
</Modal>
</>
);
}
For long content: audit logs, policy text, or multi-paragraph explanations. Header and footer use separators; body scrolls internally.
import { useState } from "react";
import { Modal } from "@/design-system/components/Modal";
import { Button } from "@/design-system/components/Button";
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button size="sm" variant="outline" onClick={() => setOpen(true)}>
Open scrollable modal
</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
title="Audit log"
scrollable
size="lg"
showCloseButton
footer={
<Button size="sm" variant="primary" onClick={() => setOpen(false)}>
Done
</Button>
}
>
<div className="modal-long-content">
<p>Scrollable content example for long narratives, logs, or policy text.</p>
<p>Use this when content length exceeds typical viewport height.</p>
<p>Header and footer remain visually separated and stable.</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
<p>Content continues...</p>
</div>
</Modal>
</>
);
}
Size presets for predictable layouts across the platform. Use sm for confirmations, lg/xl for forms and decision surfaces.
import { useState } from "react";
import { Modal } from "@/design-system/components/Modal";
import { Button } from "@/design-system/components/Button";
export function Example() {
const [open, setOpen] = useState<null | "sm" | "md" | "lg" | "xl">(null);
return (
<>
<div className="modal-docs__trigger-grid">
<Button size="sm" variant="outline" onClick={() => setOpen("sm")}>Small</Button>
<Button size="sm" variant="outline" onClick={() => setOpen("md")}>Default</Button>
<Button size="sm" variant="outline" onClick={() => setOpen("lg")}>Large</Button>
<Button size="sm" variant="outline" onClick={() => setOpen("xl")}>XL</Button>
</div>
<Modal
open={open !== null}
onClose={() => setOpen(null)}
title="Sized modal"
size={open ?? "md"}
showCloseButton
footer={
<Button size="sm" variant="primary" onClick={() => setOpen(null)}>
Close
</Button>
}
>
<p>This modal demonstrates size presets (sm, md, lg, xl).</p>
</Modal>
</>
);
}
Becomes fullscreen on mobile while remaining a centered, sized modal on larger viewports. Recommended for complex configuration flows.
import { useState } from "react";
import { Modal } from "@/design-system/components/Modal";
import { Button } from "@/design-system/components/Button";
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button size="sm" variant="secondary" onClick={() => setOpen(true)}>
Open responsive fullscreen
</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
title="Fullscreen (responsive)"
fullScreenOnMobile
scrollable
size="xl"
showCloseButton
footer={
<>
<Button size="sm" variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button size="sm" variant="primary" onClick={() => setOpen(false)}>
Save changes
</Button>
</>
}
>
<div className="modal-long-content">
<p>
On smaller screens this modal becomes fullscreen (edge-to-edge) while retaining
scrollable body and stable actions.
</p>
<p>Use for configuration panels, onboarding flows, and multi-step forms.</p>
<p>More content...</p>
<p>More content...</p>
<p>More content...</p>
<p>More content...</p>
<p>More content...</p>
</div>
</Modal>
</>
);
}