A polyfill for the HTMLDialogElement closedby
attribute, providing control over how modal dialogs can be dismissed.
Note: The HTML attribute is
closedby
(lowercase), while the JavaScript property isclosedBy
(camelCase).
- 🎯 Implements the
closedby
attribute for<dialog>
elements - 🔒 Three closing modes:
any
,closerequest
, andnone
- 🚀 Zero dependencies
- 📦 TypeScript support included
- 🌐 Works in all modern browsers with
<dialog>
support - ✨ Automatically detects native support
npm install dialog-closedby-polyfill
The polyfill is automatically applied when imported:
// ES Modules (auto-applies if needed)
import "dialog-closedby-polyfill";
// CommonJS (auto-applies if needed)
require("dialog-closedby-polyfill");
If you need more control over when the polyfill is applied:
import { apply, isSupported } from "dialog-closedby-polyfill";
if (!isSupported()) {
apply();
}
Or include it via CDN:
<!-- Polyfill applied automatically -->
<script
type="module"
src="https://cdn.jsdelivr.net/npm/dialog-closedby-polyfill/index.js"
></script>
<!-- polyfill manually -->
<script type="module">
import {
isSupported,
apply,
} from "https://cdn.jsdelivr.net/npm/dialog-closedby-polyfill/index.js";
if (!isSupported()) apply();
</script>
https://tak-dcxi.github.io/github-pages-demo/closedby.html
The closedby
attribute controls how a modal dialog can be dismissed:
closedby value |
ESC key | Backdrop click | close() method |
---|---|---|---|
"any" |
✅ | ✅ | ✅ |
"closerequest" |
✅ | ❌ | ✅ |
"none" |
❌ | ❌ | ✅ |
The dialog can be closed by:
- Pressing the ESC key
- Clicking the backdrop
- Calling the
close()
method
<dialog
id="dialog-any"
closedby="any"
aria-labelledby="dialog-any-title"
autofocus
>
<h1 id="dialog-any-title">any</h1>
<p>This dialog can be closed in any way</p>
<button type="button" commandfor="dialog-any" command="close">Close</button>
</dialog>
The dialog can be closed by:
- Pressing the ESC key
- Calling the
close()
method - ❌ Clicking the backdrop (disabled)
<dialog
id="dialog-closerequest"
closedby="closerequest"
aria-labelledby="dialog-closerequest-title"
autofocus
>
<h1 id="dialog-closerequest-title">closerequest</h1>
<p>This dialog cannot be closed by clicking outside</p>
<button type="button" commandfor="dialog-closerequest" command="close">
Close
</button>
</dialog>
The dialog can only be closed by:
- Calling the
close()
method - ❌ Pressing the ESC key (disabled)
- ❌ Clicking the backdrop (disabled)
<dialog
id="dialog-none"
closedby="none"
aria-labelledby="dialog-none-title"
autofocus
>
<h1 id="dialog-none-title">none</h1>
<p>This dialog can only be closed programmatically</p>
<button type="button" commandfor="dialog-none" command="close">Close</button>
</dialog>
You can also set the attribute via JavaScript:
const dialog = document.querySelector("dialog");
// Using setAttribute
dialog.setAttribute("closedby", "none");
// Using the property (when polyfill is loaded)
dialog.closedBy = "closerequest";
The closedby
attribute can be changed while the dialog is open:
const dialog = document.querySelector("dialog");
dialog.showModal();
// Change behavior while dialog is open
setTimeout(() => {
dialog.closedBy = "none"; // Now only closeable via close() method
}, 3000);
This polyfill works in all browsers that support the native <dialog>
element.
Native closedby
support:
- Chrome 134+
- Safari: Not implemented yet
- Firefox: Not implemented yet
- Edge: 134+
Dialog element support (required for polyfill):
- Chrome 37+
- Firefox 98+
- Safari 15.4+
- Edge 79+
Note: For browsers without native
closedby
support, this polyfill provides the functionality. For older browsers without<dialog>
element support, you'll also need a dialog element polyfill.
Check if the browser natively supports the closedby
attribute.
import { isSupported } from "dialog-closedby-polyfill";
if (isSupported()) {
console.log("Native closedby support available!");
}
Check if the polyfill has already been applied.
import { isPolyfilled } from "dialog-closedby-polyfill";
if (isPolyfilled()) {
console.log("Polyfill has been applied");
}
Manually apply the polyfill. This is called automatically when importing the main module.
import { apply } from "dialog-closedby-polyfill";
apply(); // Apply the polyfill
TypeScript definitions are included. The polyfill extends the HTMLDialogElement
interface:
interface HTMLDialogElement {
closedBy: "any" | "closerequest" | "none";
}
The polyfill works by:
- Extending HTMLDialogElement: Adds the
closedby
property to dialog elements - Intercepting
showModal()
: Sets up event listeners when a modal dialog is opened - Handling Events:
keydown
event for ESC key detectionclick
event on the dialog for backdrop clickscancel
event prevention based onclosedby
value
- Observing Changes: Uses MutationObserver to watch for attribute changes
- Cleanup: Removes event listeners when dialog is closed
This polyfill aims to match the native implementation as closely as possible. However, there might be minor differences in edge cases. Please report any discrepancies you find.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT License - see the LICENSE file for details
Declarative command/commandfor
attributes to provide dialog button operations with markup only.
- GitHub: keithamus/invokers-polyfill
- Use case: Simplifies dialog controls without requiring JavaScript event handlers
<!-- Works seamlessly with this polyfill -->
<button type="button" commandfor="my-dialog" command="show-modal">
Open Dialog
</button>
<dialog
id="my-dialog"
closedby="closerequest"
aria-labelledby="my-dialog-heading"
autofocus
>
<h1 id="my-dialog-heading">Heading</h1>
<p>Content</p>
<button type="button" commandfor="my-dialog" command="close">Close</button>
</dialog>
This polyfill is inspired by the native implementation and the work of the web standards community.