Basic Modal
A titled dialog with a focus trap, Escape to close and focus returned to the trigger.
6 फ्रेमवर्कमध्यम
A modal sheet that slides up from the bottom edge, with the full dialog contract.
<!--
A bottom sheet is a modal dialog that happens to arrive from below - it is not
a lesser thing because it does not cover the screen. Same role="dialog", same
aria-modal, same trap, same Escape, same restore.
The grabber bar is aria-hidden: it is a 36px affordance that says "this came
from the bottom edge", and announcing "image" or an empty div to a screen
reader adds nothing the dialog's name does not already carry.
The slide is a transform, not a height animation - height cannot be composited
and janks on the exact low-end phones this pattern targets. It is skipped
entirely under prefers-reduced-motion, where a sheet flying up the viewport is
precisely the vestibular trigger the setting exists to switch off.
-->
<button class="sheet-trigger" type="button" data-sheet-open>Choose a plan</button>
<div class="sheet" id="bottom-sheet" hidden>
<div class="sheet__backdrop" data-sheet-close></div>
<div class="sheet__panel" role="dialog" aria-modal="true" aria-labelledby="bottom-sheet-title">
<div class="sheet__grabber" aria-hidden="true"></div>
<h2 class="sheet__title" id="bottom-sheet-title">Choose a plan</h2>
<p class="sheet__body">Switch at any time. Changes take effect on the next invoice.</p>
<div class="sheet__actions">
<button class="sheet__btn sheet__btn--primary" type="button" data-sheet-close>Continue</button>
<button class="sheet__btn" type="button" data-sheet-close>Not now</button>
</div>
</div>
</div>
<script>
(function () {
var FOCUSABLE =
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
var root = document.getElementById('bottom-sheet');
var trigger = document.querySelector('[data-sheet-open]');
var panel = root.querySelector('.sheet__panel');
var restore = null;
function focusables() {
return Array.prototype.slice.call(panel.querySelectorAll(FOCUSABLE));
}
function open() {
restore = document.activeElement;
root.hidden = false;
document.body.style.overflow = 'hidden';
// Next frame, so the element is laid out at translateY(100%) before the
// class flips it to 0 - set both in one frame and there is no transition,
// just a jump.
window.requestAnimationFrame(function () {
root.classList.add('sheet--in');
});
var first = focusables()[0];
if (first) first.focus();
}
function close() {
root.classList.remove('sheet--in');
root.hidden = true;
document.body.style.overflow = '';
if (restore && typeof restore.focus === 'function') restore.focus();
}
trigger.addEventListener('click', open);
Array.prototype.forEach.call(root.querySelectorAll('[data-sheet-close]'), function (el) {
el.addEventListener('click', close);
});
root.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
close();
return;
}
if (event.key !== 'Tab') return;
var items = focusables();
if (!items.length) return;
var first = items[0];
var last = items[items.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
});
})();
</script>| Prop | टाइप | डिफ़ॉल्ट | विवरण |
|---|---|---|---|
openआवश्यक | boolean | - | क्या घटक अभी दिखाया जा रहा है। |
titleआवश्यक | string | - | कार्ड की हेडिंग टेक्स्ट। |
childrenआवश्यक | ReactNode | - | कंपोनेंट के अंदर रेंडर होने वाला कंटेंट। |
dismissLabel | string | 'Close' | बंद करने वाले बटन का एक्सेसिबल नाम। |
onDismissआवश्यक | () => void | - | जब यूज़र नोटिफ़िकेशन बंद करता है तब चलता है। |
className | string | - | रूट एलिमेंट पर मर्ज होने वाली अतिरिक्त क्लासेज़। |
A bottom sheet is a modal dialog that happens to arrive from below - same `role="dialog"`, same trap, same Escape, same restore. `items-end` on the overlay is the only line that makes it a sheet. The slide is a `transform`, never a height animation: height cannot be composited and janks on exactly the low-end phones this pattern targets. It needs two renders - paint at `translate-y-full`, then flip - or the browser has nothing to transition from and the sheet just appears. `motion-reduce` drops the travel entirely; a panel flying up the viewport is precisely the vestibular trigger that setting exists to switch off. Only the top corners are rounded, or you get two bright slivers of backdrop under the sheet.