I have a custom modal / full screen react component that uses position: fixed
to ensure that it always take up the full screen.
However, this component will break if one of its ancestor components has a css property that breaks the position: fixed
. For example a transform
, etc.
How do I build modal / full screen component that is guaranteed to always be position: fixed
relative to the viewport regardless of any ancestor component?
The specification CSS Positioned Layout Module Level 3 states for position: fixed
:
[The] box is positioned and sized relative to a fixed positioning containing block ... .
If the box has
position: fixed
:The containing block is established by the nearest ancestor box that establishes an fixed positioning containing block, ... .
Meaning: For "pure" fixed positioning, you cannot simply ignore the nearest fixed positioning containg block.
But you can add elements to the top layer of the document. These render "as if they were siblings of the root element".
You should check browser support for the individual features.
You can use the Popover API:
popover
attribute.HTMLElement.showPopover()
JavaScript method, or via the activation behaviour through adding the popovertarget
HTML attribute.By default, the popover element is relative to the viewport (see overlay
property). Each top-level element has its own ::backdrop
pseudo-element.
Example:
#my-popover::backdrop {
background-color: rgba(0,0,0, .2);
}
<button popovertarget="my-popover">Open popover</button>
<div id="my-popover" popover>
<p>Some popover paragraph.</p>
</div>
Note: Popovers are not modals; they do not obstruct interaction with the underlying document.
<dialog>
elementYou can also use the <dialog>
element. It is more complex compared to the Popover API, but allows for native modals.
Note: Use this element for building dialogs or modals, but do not simply abuse it for its functionality.
Modals are dialogs that...
Similar to popover elements, a dialog is relative to the viewport and—only as a modal— has its own ::backdrop
pseudo-element.
Important: A dialog should always have an explicit closing button.
Example:
const dialog = document.querySelector("dialog");
// Add close-by-JS functionality
dialog.querySelector("#close-js")
.addEventListener("click", () => dialog.close());
// Add open-as-(non-)modal functionalities
document.getElementById("show-dialog")
.addEventListener("click", () => dialog.show());
document.getElementById("show-modal")
.addEventListener("click", () => !dialog.open && dialog.showModal());
dialog::backdrop {
background-color: rgba(255,0,0, .2);
}
<button id="show-dialog">Show dialog</button>
<button id="show-modal">Show dialog as modal</button>
<dialog>
<!--Add close-by-form functionality-->
<form method="dialog">
<button id="close-form">Close by form</button>
</form>
<button id="close-js">Close by JavaScript</button>
<p>Some dialog paragraph.</p>
</dialog>
Note: A dialog is not a simple popover: It should not (and therefore cannot) be dismissed, e.g. by clicking outside or pressing Escape. However, should you desire so regardless: (Discouraged example)
const dialog = document.querySelector("dialog");
dialog.querySelector("#close-js")
.addEventListener("click", () => dialog.close());
document.getElementById("show-dialog")
.addEventListener("click", evt => dialog.show());
document.getElementById("show-modal") // Now closes dialog before this event-handler fires
.addEventListener("click", evt => dialog.showModal());
// Add close-by-clicking-outside functionality
document.addEventListener(
"click",
evt => {
if (dialog.open && !dialog.contains(evt.target)) {
dialog.close();
}
},
/* Capture event to close dialog, before event propagates
* to any other event-handlers.
*/
{ capture: true }
);
// Add close-by-escape functionality
document.addEventListener(
"keydown",
evt => evt.code === "Escape" && dialog.close()
);
::backdrop {
/* Prevent ::backdrop from receiving pointer events;
* allows close-by-clicking-outside functionality for modal dialogs.
*/
pointer-events: none;
}
<button id="show-dialog">Show dialog</button>
<button id="show-modal">Show dialog as modal</button>
<dialog>
<form method="dialog">
<button id="close-form">Close by form</button>
</form>
<button id="close-js">Close by JavaScript</button>
<p>Some dialog paragraph.</p>
</dialog>