Project saved
Actions
Modal
A native dialog with contained focus, Escape to close and focus returned to its trigger.
| Class name | Type | Description |
|---|---|---|
modalplf:modal | Component | A native dialog |
modal-headerplf:modal-header | Part | Title row with the close button |
modal-titleplf:modal-title | Part | The dialog's heading |
modal-descriptionplf:modal-description | Part | Muted text under the title |
modal-closeplf:modal-close | Part | The close button, which draws an x while empty |
modal-actionplf:modal-action | Part | The row of actions at the end |
modal-boxplf:modal-box | Part | The content box beside a floating close button |
modal-floating-closeplf:modal-floating-close | Modifier | The close button floats on the corner |
<button class="btn btn-primary" data-plf-trigger="modal:open" data-plf-target="modal-default">Open modal</button>
<dialog class="modal" data-plf-modal id="modal-default" aria-describedby="modal-default-description">
<div class="modal-header">
<h2 class="modal-title">Edit profile</h2>
<button class="modal-close" data-plf-trigger="modal:close" aria-label="Close"></button>
</div>
<p class="modal-description" id="modal-default-description">Make changes to your profile here.</p>
<div class="field">
<label class="field-label" for="modal-default-input">Name</label>
<input class="input" id="modal-default-input" type="text" autofocus placeholder="Type something…">
</div>
<div class="modal-action">
<button class="btn btn-outline" data-plf-trigger="modal:close">Cancel</button>
<button class="btn btn-primary" data-plf-trigger="modal:close">Save</button>
</div>
</dialog><button class="plf:btn plf:btn-primary" data-plf-trigger="modal:open" data-plf-target="modal-default">Open modal</button>
<dialog class="plf:modal" data-plf-modal id="modal-default" aria-describedby="modal-default-description">
<div class="plf:modal-header">
<h2 class="plf:modal-title">Edit profile</h2>
<button class="plf:modal-close" data-plf-trigger="modal:close" aria-label="Close"></button>
</div>
<p class="plf:modal-description" id="modal-default-description">Make changes to your profile here.</p>
<div class="plf:field">
<label class="plf:field-label" for="modal-default-input">Name</label>
<input class="plf:input" id="modal-default-input" type="text" autofocus placeholder="Type something…">
</div>
<div class="plf:modal-action">
<button class="plf:btn plf:btn-outline" data-plf-trigger="modal:close">Cancel</button>
<button class="plf:btn plf:btn-primary" data-plf-trigger="modal:close">Save</button>
</div>
</dialog>'use client';
import { useState } from 'react';
import { Button, Field, Input, Modal, ModalAction, ModalClose, ModalDescription, ModalHeader, ModalTitle } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button color="primary" onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onOpenChange={setOpen}>
<ModalHeader>
<ModalTitle>Edit profile</ModalTitle>
<ModalClose />
</ModalHeader>
<ModalDescription>Make changes to your profile here.</ModalDescription>
<Field label="Name">
<Input type="text" autoFocus placeholder="Type something…" />
</Field>
<ModalAction>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="primary" onClick={() => setOpen(false)}>Save</Button>
</ModalAction>
</Modal>
</>
);
}'use client';
import { useState } from 'react';
import { Button, Field, Input, Modal, ModalAction, ModalClose, ModalDescription, ModalHeader, ModalTitle } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button color="primary" onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onOpenChange={setOpen}>
<ModalHeader>
<ModalTitle>Edit profile</ModalTitle>
<ModalClose />
</ModalHeader>
<ModalDescription>Make changes to your profile here.</ModalDescription>
<Field label="Name">
<Input type="text" autoFocus placeholder="Type something…" />
</Field>
<ModalAction>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="primary" onClick={() => setOpen(false)}>Save</Button>
</ModalAction>
</Modal>
</>
);
}The dialog’s first heading names it; a dialog without one needs aria-label.
Floating close button
Keep the close button as the dialog’s first child and wrap the rest in modal-box.
<button class="btn btn-outline" data-plf-trigger="modal:open" data-plf-target="modal-floating">Open modal</button>
<dialog class="modal modal-floating-close" data-plf-modal id="modal-floating" aria-describedby="modal-floating-description">
<button class="modal-close" data-plf-trigger="modal:close" aria-label="Close"></button>
<div class="modal-box">
<div class="modal-header">
<h2 class="modal-title">Share this page</h2>
</div>
<p class="modal-description" id="modal-floating-description">Anyone with the link can view it.</p>
<div class="field">
<label class="field-label" for="modal-floating-input">Message</label>
<input class="input" id="modal-floating-input" type="text" autofocus placeholder="Type something…">
</div>
<div class="modal-action">
<button class="btn btn-outline" data-plf-trigger="modal:close">Cancel</button>
<button class="btn btn-primary" data-plf-trigger="modal:close">Done</button>
</div>
</div>
</dialog><button class="plf:btn plf:btn-outline" data-plf-trigger="modal:open" data-plf-target="modal-floating">Open modal</button>
<dialog class="plf:modal plf:modal-floating-close" data-plf-modal id="modal-floating" aria-describedby="modal-floating-description">
<button class="plf:modal-close" data-plf-trigger="modal:close" aria-label="Close"></button>
<div class="plf:modal-box">
<div class="plf:modal-header">
<h2 class="plf:modal-title">Share this page</h2>
</div>
<p class="plf:modal-description" id="modal-floating-description">Anyone with the link can view it.</p>
<div class="plf:field">
<label class="plf:field-label" for="modal-floating-input">Message</label>
<input class="plf:input" id="modal-floating-input" type="text" autofocus placeholder="Type something…">
</div>
<div class="plf:modal-action">
<button class="plf:btn plf:btn-outline" data-plf-trigger="modal:close">Cancel</button>
<button class="plf:btn plf:btn-primary" data-plf-trigger="modal:close">Done</button>
</div>
</div>
</dialog>'use client';
import { useState } from 'react';
import { Button, Field, Input, Modal, ModalAction, ModalBox, ModalClose, ModalDescription, ModalHeader, ModalTitle } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onOpenChange={setOpen} floatingClose>
<ModalClose />
<ModalBox>
<ModalHeader>
<ModalTitle>Share this page</ModalTitle>
</ModalHeader>
<ModalDescription>Anyone with the link can view it.</ModalDescription>
<Field label="Message">
<Input type="text" autoFocus placeholder="Type something…" />
</Field>
<ModalAction>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="primary" onClick={() => setOpen(false)}>Done</Button>
</ModalAction>
</ModalBox>
</Modal>
</>
);
}'use client';
import { useState } from 'react';
import { Button, Field, Input, Modal, ModalAction, ModalBox, ModalClose, ModalDescription, ModalHeader, ModalTitle } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onOpenChange={setOpen} floatingClose>
<ModalClose />
<ModalBox>
<ModalHeader>
<ModalTitle>Share this page</ModalTitle>
</ModalHeader>
<ModalDescription>Anyone with the link can view it.</ModalDescription>
<Field label="Message">
<Input type="text" autoFocus placeholder="Type something…" />
</Field>
<ModalAction>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="primary" onClick={() => setOpen(false)}>Done</Button>
</ModalAction>
</ModalBox>
</Modal>
</>
);
}<button class="btn btn-outline" data-plf-trigger="modal:open" data-plf-target="modal-backdrop">Open modal</button>
<dialog class="modal" data-plf-modal data-plf-close-on-backdrop id="modal-backdrop">
<div class="modal-header">
<h2 class="modal-title">Click outside to close</h2>
</div>
<div class="modal-action">
<button class="btn btn-primary" data-plf-trigger="modal:close">Close</button>
</div>
</dialog><button class="plf:btn plf:btn-outline" data-plf-trigger="modal:open" data-plf-target="modal-backdrop">Open modal</button>
<dialog class="plf:modal" data-plf-modal data-plf-close-on-backdrop id="modal-backdrop">
<div class="plf:modal-header">
<h2 class="plf:modal-title">Click outside to close</h2>
</div>
<div class="plf:modal-action">
<button class="plf:btn plf:btn-primary" data-plf-trigger="modal:close">Close</button>
</div>
</dialog>'use client';
import { useState } from 'react';
import { Button, Modal, ModalAction, ModalHeader, ModalTitle } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onOpenChange={setOpen} closeOnBackdrop>
<ModalHeader>
<ModalTitle>Click outside to close</ModalTitle>
</ModalHeader>
<ModalAction>
<Button color="primary" onClick={() => setOpen(false)}>Close</Button>
</ModalAction>
</Modal>
</>
);
}'use client';
import { useState } from 'react';
import { Button, Modal, ModalAction, ModalHeader, ModalTitle } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onOpenChange={setOpen} closeOnBackdrop>
<ModalHeader>
<ModalTitle>Click outside to close</ModalTitle>
</ModalHeader>
<ModalAction>
<Button color="primary" onClick={() => setOpen(false)}>Close</Button>
</ModalAction>
</Modal>
</>
);
}Save from a dialog
Run your request from the form’s submit; when it succeeds, close the dialog with PLF.Modal.getOrCreateInstance(dialog).close() and confirm with a Toast.
<button class="btn btn-primary" data-plf-trigger="modal:open" data-plf-target="modal-save">Edit project</button>
<dialog class="modal" data-plf-modal id="modal-save">
<div class="modal-header">
<h2 class="modal-title">Edit project</h2>
<button class="modal-close" data-plf-trigger="modal:close" aria-label="Close"></button>
</div>
<form id="modal-save-form">
<div class="field">
<label class="field-label" for="modal-save-name">Project name</label>
<input class="input" id="modal-save-name" name="name" type="text" autofocus required value="Website redesign">
</div>
<div class="modal-action">
<button class="btn btn-outline" type="button" data-plf-trigger="modal:close">Cancel</button>
<button class="btn btn-primary">
<span class="btn-label">Save</span>
<span class="btn-loading">
<span class="btn-icon btn-icon-loading"></span>
Saving…
</span>
</button>
</div>
</form>
</dialog>
<section class="toast" aria-label="Project notifications">
<article class="alert shadow-lg alert-success" id="modal-save-toast" data-plf-toast hidden>
<div class="alert-content">
<p class="alert-title" data-plf-toast-title>Project saved</p>
</div>
<button class="toast-close" data-plf-trigger="toast:hide"></button>
</article>
</section>
<script>
const dialog = document.getElementById('modal-save');
const form = document.getElementById('modal-save-form');
form.addEventListener('submit', async event => {
event.preventDefault();
const response = await PLF.Button.loading(event.submitter, fetch('/projects/1', { method: 'POST', body: new FormData(form) }));
if (!response.ok) return;
PLF.Modal.getOrCreateInstance(dialog).close();
PLF.Toast.getOrCreateInstance(document.getElementById('modal-save-toast')).show();
});
</script><button class="plf:btn plf:btn-primary" data-plf-trigger="modal:open" data-plf-target="modal-save">Edit project</button>
<dialog class="plf:modal" data-plf-modal id="modal-save">
<div class="plf:modal-header">
<h2 class="plf:modal-title">Edit project</h2>
<button class="plf:modal-close" data-plf-trigger="modal:close" aria-label="Close"></button>
</div>
<form id="modal-save-form">
<div class="plf:field">
<label class="plf:field-label" for="modal-save-name">Project name</label>
<input class="plf:input" id="modal-save-name" name="name" type="text" autofocus required value="Website redesign">
</div>
<div class="plf:modal-action">
<button class="plf:btn plf:btn-outline" type="button" data-plf-trigger="modal:close">Cancel</button>
<button class="plf:btn plf:btn-primary">
<span class="plf:btn-label">Save</span>
<span class="plf:btn-loading">
<span class="plf:btn-icon plf:btn-icon-loading"></span>
Saving…
</span>
</button>
</div>
</form>
</dialog>
<section class="plf:toast" aria-label="Project notifications">
<article class="plf:alert plf:shadow-lg plf:alert-success" id="modal-save-toast" data-plf-toast hidden>
<div class="plf:alert-content">
<p class="plf:alert-title" data-plf-toast-title>Project saved</p>
</div>
<button class="plf:toast-close" data-plf-trigger="toast:hide"></button>
</article>
</section>
<script>
const dialog = document.getElementById('modal-save');
const form = document.getElementById('modal-save-form');
form.addEventListener('submit', async event => {
event.preventDefault();
const response = await PLF.Button.loading(event.submitter, fetch('/projects/1', { method: 'POST', body: new FormData(form) }));
if (!response.ok) return;
PLF.Modal.getOrCreateInstance(dialog).close();
PLF.Toast.getOrCreateInstance(document.getElementById('modal-save-toast')).show();
});
</script>'use client';
import { useState, type FormEvent } from 'react';
import { Button, Field, Input, Modal, ModalAction, ModalClose, ModalHeader, ModalTitle, toast, Toaster } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
async function save(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setSaving(true);
try {
const response = await fetch('/projects/1', { method: 'POST', body: new FormData(event.currentTarget) });
if (!response.ok) return;
setOpen(false);
toast.success('Project saved');
} finally {
setSaving(false);
}
}
return (
<>
<Button color="primary" onClick={() => setOpen(true)}>Edit project</Button>
<Modal open={open} onOpenChange={setOpen}>
<ModalHeader>
<ModalTitle>Edit project</ModalTitle>
<ModalClose />
</ModalHeader>
<form onSubmit={save}>
<Field label="Project name">
<Input name="name" type="text" autoFocus required defaultValue="Website redesign" />
</Field>
<ModalAction>
<Button variant="outline" type="button" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="primary" loading={saving} loadingText="Saving…">Save</Button>
</ModalAction>
</form>
</Modal>
<Toaster />
</>
);
}'use client';
import { useState, type FormEvent } from 'react';
import { Button, Field, Input, Modal, ModalAction, ModalClose, ModalHeader, ModalTitle, toast, Toaster } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
async function save(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setSaving(true);
try {
const response = await fetch('/projects/1', { method: 'POST', body: new FormData(event.currentTarget) });
if (!response.ok) return;
setOpen(false);
toast.success('Project saved');
} finally {
setSaving(false);
}
}
return (
<>
<Button color="primary" onClick={() => setOpen(true)}>Edit project</Button>
<Modal open={open} onOpenChange={setOpen}>
<ModalHeader>
<ModalTitle>Edit project</ModalTitle>
<ModalClose />
</ModalHeader>
<form onSubmit={save}>
<Field label="Project name">
<Input name="name" type="text" autoFocus required defaultValue="Website redesign" />
</Field>
<ModalAction>
<Button variant="outline" type="button" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="primary" loading={saving} loadingText="Saving…">Save</Button>
</ModalAction>
</form>
</Modal>
<Toaster />
</>
);
}JavaScript
Modal needs JavaScript. The Full script (pelatform-ui.min.js) starts every component on page load, with no code of your own; with ESM imports, call init() yourself, as in Installation. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="components/form.min.css">
<link rel="stylesheet" href="components/alert.min.css">
<link rel="stylesheet" href="components/modal.min.css">
<link rel="stylesheet" href="components/toast.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/modal.min.js" defer></script>
<script src="components/toast.min.js" defer></script>
<script src="components/button.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<link rel="stylesheet" href="components/alert.prefixed.min.css">
<link rel="stylesheet" href="components/modal.prefixed.min.css">
<link rel="stylesheet" href="components/toast.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/modal.min.js" defer></script>
<script src="components/toast.min.js" defer></script>
<script src="components/button.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>| Attribute | Purpose |
|---|---|
data-plf-modal |
Marks the <dialog> |
data-plf-trigger="modal: |
Opens the dialog named by data-plf-target; its aria-keyshortcuts, such as Control+K Meta+K, also opens it outside text fields |
data-plf-target |
The dialog’s id, without #, in the same root |
data-plf-trigger="modal: |
Inside the dialog, closes it; so does a submitted <form method="dialog"> |
data-plf-close-on-backdrop |
Closes on a click outside the dialog |
| Method | Behavior |
|---|---|
PLF. |
Returns the instance, creating it for valid markup |
PLF. |
Returns the instance or null |
instance. |
Opens; focus returns to the optional trigger. Returns false when canceled or another modal is open |
instance. |
Closes; returns false when canceled |
instance. |
Closes without events and removes listeners |
PLF., fire |
Opens a confirmation or a result with SweetAlert’s options, on dialogs it creates in the root, and returns a promise for the outcome (Dialogs from JavaScript) |
PLF. |
Returns a Dialog whose calls start from these options, such as labels in your language |
| Event | When |
|---|---|
plf: |
Before opening; cancelable |
plf: |
After opening |
plf: |
Before closing; cancelable |
plf: |
After closing |
Each event’s detail:
| Property | Events | Value |
|---|---|---|
instance |
All | The Modal instance |
reason |
All | api, trigger, keyboard or backdrop |
trigger |
All | The open or close button, or the trigger passed to open; otherwise null |
action |
before-close, close |
Only on an Alert Dialog: confirm when the confirm button closes it, otherwise cancel |
| Key | Action |
|---|---|
| Tab, Shift+Tab | Move between controls inside the dialog |
| Escape | Closes the dialog, through plf: |
One modal is open at a time; stacked modals are not supported.
React
Modal from @pelatform/react renders this dialog: the application opens it with open and closes it in onOpenChange, and each part is a component of its own.
| Prop | Values | Default |
|---|---|---|
open |
true while the dialog is open |
Required |
onOpenChange |
(open, reason), called when the dialog asks to close: trigger (its close button), keyboard (Escape) or backdrop; keep open to stay open |
None |
closeOnBackdrop |
true closes on a click outside the dialog |
false |
floatingClose |
true floats the close button on the corner: put ModalClose first and the rest in ModalBox |
false |
The parts:
| Part | Element |
|---|---|
ModalHeader |
A div with modal-header |
ModalTitle |
An h2 with modal-title, or another heading with level, which names the dialog |
ModalDescription |
A p with modal-description, which describes the dialog |
ModalClose |
A button with modal-close: the drawn x and the name Close unless given others |
ModalAction |
A div with modal-action |
ModalBox |
A div with modal-box |
The content mounts when the dialog opens, so autoFocus takes the first focus and a form starts fresh each time.
| Variable | Default |
|---|---|
--plf-modal-padding |
Space 6 (24px) |
--plf-modal-radius |
Extra large radius (12px) |
--plf-modal-bg, --plf-modal-color |
Surface and text |
--plf-modal-border |
Subtle border |
--plf-modal-shadow |
Large shadow |
--plf-modal-backdrop-bg |
--plf-color-backdrop |