Modal
A modal displays content in a layer above the web page, requiring user interaction.
These components require the button plugin.
Base modal
<div
x-data="{ ...modal() }"
x-init="toggle()"
class="p-4 font-sans"
x-on:keydown.window.tab="handleForwardTab"
x-on:keydown.window.shift.tab="handleBackwardTab"
x-on:keydown.window.escape="handleEscape"
>
<button
class="btn text-gray-800 border-black bg-opacity-60 hover:bg-gray-200"
x-on:click="toggle"
>
Show Example Modal
</button>
<div
role="dialog"
aria-labelledby="dialog-title"
x-ref="dialog"
x-show="open"
class="h-screen w-full fixed bottom-0 inset-x-0 z-50 sm:inset-0 sm:flex"
>
<div
x-show.transition.opacity.duration.300ms="open"
tabindex="-1"
>
<div class="absolute inset-0 bg-black bg-opacity-60"></div>
</div>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90"
class="relative w-full h-full flex items-center justify-center p-4 transition-all"
>
<div
style="max-height: 85vh"
class="max-w-3xl w-full bg-white rounded-sm p-4 shadow-xl overflow-y-auto transition-all sm:p-8"
x-on:click.away="toggle"
>
<!-- If the first content element inside the modal is not a link, button, or form input, add tabindex="0" on the element so we can programmatically focus the element for screen readers. -->
<h3 id="dialog-title" class="text-xl sm:text-2xl md:text-3xl md:font-light lg:text-4xl" tabindex="0">Modal 1</h3>
<p class="mt-6">Nulla vitae elit libero, a pharetra augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur blandit tempus porttitor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vestibulum id ligula porta felis euismod semper. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
<p class="mt-4">Vestibulum id ligula porta felis euismod semper. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Nulla vitae elit libero, a pharetra augue.</p>
<p class="mt-4">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit. Nulla vitae elit libero, a pharetra augue.</p>
</div>
</div>
<button
aria-label="Close dialog"
title="Close dialog"
type="button"
class="hidden absolute top-0 right-0 m-4 text-gray-200 sm:inline-block hover:text-gray-300 focus:outline-none focus:ring focus:ring-blue-500"
x-on:click.stop="toggle"
>
<svg class="w-6 h-6" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
<script>
const modal = () => {
return {
open: false,
previouslyFocusedElement: null,
firstFocusableElement: null,
lastFocusableEl: null,
removedTabIndexFromFirstFocusableElement: false,
toggle() {
this.open = !this.open;
if (this.open) {
document.body.classList.add('overflow-hidden');
this.focusDialog();
return
}
document.body.classList.remove('overflow-hidden');
this.previouslyFocusedElement.focus();
if (this.removedTabIndexFromFirstFocusableElement) {
this.firstFocusableElement.setAttribute('tabindex', 0);
}
},
focusDialog() {
this.previouslyFocusedElement = document.activeElement;
setTimeout(() => {
const focusableElements = this.$refs.dialog.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex=\'0\']');
if (focusableElements.length === 0) {
return
}
this.firstFocusableElement = focusableElements[0];
this.lastFocusableElement = focusableElements[focusableElements.length - 1];
this.firstFocusableElement.focus();
if (this.firstFocusableElement.tabIndex == 0) {
this.firstFocusableElement.removeAttribute('tabindex');
this.removedTabIndexFromFirstFocusableElement = true;
} else {
this.removedTabIndexFromFirstFocusableElement = false;
}
}, 200);
},
handleBackwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.firstFocusableElement) {
e.preventDefault();
}
},
handleForwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.lastFocusableElement) {
e.preventDefault();
this.firstFocusableElement.focus();
}
},
handleEscape() {
if (this.open) {
this.toggle();
}
},
}
}
</script>
Fixed support modal
A modal for help documentation or information that stays fixed in the bottom right of a user's screen. Click the question mark button to show the modal.
<div>
<p class="p-4">Click the circle icon in the bottom right to toggle the modal.</p>
<div
x-data="{ ...modal() }"
class="relative font-sans"
x-on:keydown.window.tab="handleForwardTab"
x-on:keydown.window.shift.tab="handleBackwardTab"
x-on:keydown.window.escape="handleEscape"
>
<button
aria-label="Toggle question menu"
title="Toggle question menu"
class="fixed bottom-0 right-0 z-30 m-4 inline-flex items-center justify-center w-16 h-16 text-4xl text-white leading-none bg-black rounded-full shadow-lg hover:bg-gray-800 focus:outline-none focus:ring focus:ring-blue-500"
x-on:click.prevent="toggle"
>
<span x-show="! open">?</span>
<span x-show="open">
<i data-feather="x" class="w-8 h-8"></i>
</span>
</button>
<div
role="dialog"
aria-labelledby="dialog-title"
x-ref="dialog"
x-show="open"
x-cloak
class="h-screen w-full fixed inset-0 z-20"
>
<div
x-show.transition.opacity.duration.300ms="open"
class="fixed inset-0"
tabindex="-1"
>
<div
class="absolute inset-0 bg-black bg-opacity-60"
x-on:click.self="toggle"
></div>
</div>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90"
class="absolute bottom-0 right-0 mt-8 mb-24 mx-4 origin-bottom-right transition-all sm:m-20"
>
<div
style="max-height: 85vh"
class="max-w-lg w-full bg-white rounded-sm p-4 border-t-3 border-red-600 shadow-xl overflow-y-auto transition-all sm:p-8"
>
<!-- If the first content element inside the modal is not a link, button, or form input, add tabindex="0" on the element so we can programmatically focus the element for screen readers. -->
<h3 id="dialog-title" class="text-xl md:text-2xl" tabindex="0">Interested? Have Questions?</h3>
<p class="mt-4 text-sm text-gray-700">Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente error, veritatis in, doloribus expedita dolore, non nam eaque maiores harum facere. Velit atque molestiae unde, quasi vero reiciendis! Quasi, facilis.</p>
<ul class="mt-4 divide-y-2">
<li>
<a href="#" class="py-2 inline-flex items-center text-sm text-blue-700 hover:text-blue-800">
<i data-feather="mail" class="mr-2 w-4 h-4 text-gray-500"></i>
<span>example@northeastern.edu</span>
</a>
</li>
<li>
<a href="#" class="py-2 inline-flex items-center text-sm text-blue-700 hover:text-blue-800">
<i data-feather="phone" class="mr-2 w-4 h-4 text-gray-500"></i>
<span>555.555.5555</span>
</a>
</li>
<li>
<a href="#" class="py-2 inline-flex items-center text-sm text-blue-700 hover:text-blue-800">
<i data-feather="users" class="mr-2 w-4 h-4 text-gray-500"></i>
<span>Contact our staff</span>
</a>
</li>
<li>
<a href="#" class="py-2 inline-flex items-center text-sm text-blue-700 hover:text-blue-800">
<i data-feather="help-circle" class="mr-2 w-4 h-4 text-gray-500"></i>
<span>Frequently Asked Questions</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
const modal = () => {
return {
open: false,
previouslyFocusedElement: null,
firstFocusableElement: null,
lastFocusableEl: null,
removedTabIndexFromFirstFocusableElement: false,
toggle() {
this.open = !this.open;
if (this.open) {
document.body.classList.add('overflow-hidden');
this.focusDialog();
return
}
document.body.classList.remove('overflow-hidden');
this.previouslyFocusedElement.focus();
if (this.removedTabIndexFromFirstFocusableElement) {
this.firstFocusableElement.setAttribute('tabindex', 0);
}
},
focusDialog() {
this.previouslyFocusedElement = document.activeElement;
setTimeout(() => {
const focusableElements = this.$refs.dialog.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex=\'0\']');
if (focusableElements.length === 0) {
return
}
this.firstFocusableElement = focusableElements[0];
this.lastFocusableElement = focusableElements[focusableElements.length - 1];
this.firstFocusableElement.focus();
if (this.firstFocusableElement.tabIndex == 0) {
this.firstFocusableElement.removeAttribute('tabindex');
this.removedTabIndexFromFirstFocusableElement = true;
} else {
this.removedTabIndexFromFirstFocusableElement = false;
}
}, 200);
},
handleBackwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.firstFocusableElement) {
e.preventDefault();
}
},
handleForwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.lastFocusableElement) {
e.preventDefault();
this.firstFocusableElement.focus();
}
},
handleEscape() {
if (this.open) {
this.toggle();
}
},
}
}
</script>
Search modal
<div
x-data="{ ...modal() }"
x-init="toggle()"
class="p-4 font-sans"
x-on:keydown.window.tab="handleForwardTab"
x-on:keydown.window.shift.tab="handleBackwardTab"
x-on:keydown.window.escape="handleEscape"
>
<button
class="btn text-gray-800 border-black bg-opacity-60 hover:bg-gray-200"
x-on:click="toggle"
>
Show Example Modal
</button>
<div
role="dialog"
aria-labelledby="dialog-title"
x-ref="dialog"
x-show="open"
class="h-screen w-full fixed bottom-0 inset-x-0 z-50 sm:inset-0 sm:flex"
>
<div
x-show.transition.opacity.duration.300ms="open"
tabindex="-1"
>
<div class="absolute inset-0 bg-black bg-opacity-80"></div>
</div>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90"
class="relative w-full h-full flex items-center justify-center p-4 transition-all"
>
<div
style="max-height: 85vh"
class="max-w-3xl w-full"
x-on:click.away="toggle"
>
<!-- If the first content element inside the modal is not a link, button, or form input, add tabindex="0" on the element so we can programmatically focus the element for screen readers. -->
<div class="relative">
<input type="text" class="block w-full h-full py-3 px-1 text-white text-xl bg-transparent border-0 border-b border-white placeholder-gray-200 md:text-2xl focus:ring-0 focus:border-blue-700" placeholder="Search by keywords">
<button class="btn-sm py-0 px-3 absolute inset-y-0 right-0 my-1 text-white border-white md:my-3 hover:text-gray-800 hover:bg-white">GO</button>
</div>
</div>
</div>
<button
aria-label="Close dialog"
title="Close dialog"
type="button"
class="hidden absolute top-0 right-0 m-4 text-gray-200 sm:inline-block hover:text-gray-300 focus:outline-none focus:ring"
x-on:click.stop="toggle"
>
<svg class="w-6 h-6" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
<script>
const modal = () => {
return {
open: false,
previouslyFocusedElement: null,
firstFocusableElement: null,
lastFocusableEl: null,
removedTabIndexFromFirstFocusableElement: false,
toggle() {
this.open = !this.open;
if (this.open) {
document.body.classList.add('overflow-hidden');
this.focusDialog();
return
}
document.body.classList.remove('overflow-hidden');
this.previouslyFocusedElement.focus();
if (this.removedTabIndexFromFirstFocusableElement) {
this.firstFocusableElement.setAttribute('tabindex', 0);
}
},
focusDialog() {
this.previouslyFocusedElement = document.activeElement;
setTimeout(() => {
const focusableElements = this.$refs.dialog.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex=\'0\']');
if (focusableElements.length === 0) {
return
}
this.firstFocusableElement = focusableElements[0];
this.lastFocusableElement = focusableElements[focusableElements.length - 1];
this.firstFocusableElement.focus();
if (this.firstFocusableElement.tabIndex == 0) {
this.firstFocusableElement.removeAttribute('tabindex');
this.removedTabIndexFromFirstFocusableElement = true;
} else {
this.removedTabIndexFromFirstFocusableElement = false;
}
}, 200);
},
handleBackwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.firstFocusableElement) {
e.preventDefault();
}
},
handleForwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.lastFocusableElement) {
e.preventDefault();
this.firstFocusableElement.focus();
}
},
handleEscape() {
if (this.open) {
this.toggle();
}
},
}
}
</script>
Modal with complex content
<div
x-data="{ ...modal() }"
x-init="toggle()"
class="p-4 font-sans"
x-on:keydown.window.tab="handleForwardTab"
x-on:keydown.window.shift.tab="handleBackwardTab"
x-on:keydown.window.escape="handleEscape"
>
<button
class="btn text-gray-800 border-black bg-opacity-40 hover:bg-gray-200"
x-on:click="toggle"
>
Show Example Modal
</button>
<div
role="dialog"
aria-labelledby="dialog-title"
x-ref="dialog"
x-show="open"
class="h-screen w-full fixed bottom-0 inset-x-0 z-50 sm:inset-0 sm:flex"
>
<div
x-show.transition.opacity.duration.300ms="open"
tabindex="-1"
>
<div class="absolute inset-0 bg-black bg-opacity-50"></div>
</div>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90"
class="relative w-full h-full flex items-center justify-center p-4 transition-all"
>
<div
style="max-height: 85vh"
class="max-w-3xl w0-full bg-white rounded-sm p-4 shadow-xl overflow-y-auto transition-all sm:p-8"
x-on:click.away="toggle"
>
<div>
<div class="-mx-4 flex flex-col flex-wrap sm:flex-row">
<div class="flex-1 px-4">
<!-- If the first content element inside the modal is not a link, button, or form input, add tabindex="0" on the element so we can programmatically focus the element for screen readers. -->
<h3 id="dialog-title" class="text-xl font-bold sm:text-2xl md:text-3xl lg:text-4xl" tabindex="0">Modal 1</h3>
<p class="text-lg">Etiam porta sem malesuada magna mollis euismod.</p>
<p class="mt-6">
<strong>Email:</strong>
<a href="#" class="text-blue-700 hover:underline">example@northeastern.edu</a>
</p>
<p class="mt-6">Nulla vitae elit libero, a pharetra augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur blandit tempus porttitor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vestibulum id ligula porta felis euismod semper. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
</div>
<div class="mt-6 mx-auto w-64 px-4 sm:mt-0">
<img src="https://images.unsplash.com/photo-1561677843-39dee7a319ca?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=6&w=400&h=500&q=60" alt="Man's portait">
</div>
</div>
<p class="mt-4">Vestibulum id ligula porta felis euismod semper. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Nulla vitae elit libero, a pharetra augue.</p>
<p class="mt-4">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="mt-6">
<button
type="button"
class="btn text-gray-800 border-black border-opacity-40 hover:bg-gray-200"
x-on:click="toggle"
>
Dismiss
</button>
</div>
</div>
</div>
<button
aria-label="Close dialog"
title="Close dialog"
type="button"
class="hidden absolute top-0 right-0 m-4 text-gray-200 sm:inline-block hover:text-gray-300 focus:outline-none focus:ring focus:ring-blue-500"
x-on:click.stop="toggle"
>
<svg class="w-6 h-6" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
<script>
const modal = () => {
return {
open: false,
previouslyFocusedElement: null,
firstFocusableElement: null,
lastFocusableEl: null,
removedTabIndexFromFirstFocusableElement: false,
toggle() {
this.open = !this.open;
if (this.open) {
document.body.classList.add('overflow-hidden');
this.focusDialog();
return
}
document.body.classList.remove('overflow-hidden');
this.previouslyFocusedElement.focus();
if (this.removedTabIndexFromFirstFocusableElement) {
this.firstFocusableElement.setAttribute('tabindex', 0);
}
},
focusDialog() {
this.previouslyFocusedElement = document.activeElement;
setTimeout(() => {
const focusableElements = this.$refs.dialog.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex=\'0\']');
if (focusableElements.length === 0) {
return
}
this.firstFocusableElement = focusableElements[0];
this.lastFocusableElement = focusableElements[focusableElements.length - 1];
this.firstFocusableElement.focus();
if (this.firstFocusableElement.tabIndex == 0) {
this.firstFocusableElement.removeAttribute('tabindex');
this.removedTabIndexFromFirstFocusableElement = true;
} else {
this.removedTabIndexFromFirstFocusableElement = false;
}
}, 200);
},
handleBackwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.firstFocusableElement) {
e.preventDefault();
}
},
handleForwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.lastFocusableElement) {
e.preventDefault();
this.firstFocusableElement.focus();
}
},
handleEscape() {
if (this.open) {
this.toggle();
}
},
}
}
</script>
Modal with form
<div
x-data="{ ...modal() }"
x-init="toggle()"
class="p-4 font-sans"
x-on:keydown.window.tab="handleForwardTab"
x-on:keydown.window.shift.tab="handleBackwardTab"
x-on:keydown.window.escape="handleEscape"
>
<button
class="btn text-gray-800 border-black border-opacity-40 hover:bg-gray-200"
x-on:click="toggle"
>
Show Example Modal
</button>
<div
role="dialog"
aria-labelledby="dialog-title"
x-ref="dialog"
x-show="open"
class="h-screen w-full fixed bottom-0 inset-x-0 z-50 sm:inset-0 sm:flex"
>
<div
x-show.transition.opacity.duration.300ms="open"
tabindex="-1"
>
<div class="absolute inset-0 bg-black bg-opacity-50"></div>
</div>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="relative transform w-full h-full flex items-center justify-center p-4"
>
<div
style="max-height: 95vh"
class="relative max-w-3xl w-full bg-white rounded-sm p-4 shadow-xl overflow-y-auto transition-all sm:p-8"
x-on:click.away="toggle"
>
<!-- If the first content element inside the modal is not a link, button, or form input, add tabindex="0" on the element so we can programmatically focus the element for screen readers. -->
<h3 id="dialog-title" class="text-xl sm:text-2xl md:text-3xl md:font-light lg:text-4xl" tabindex="0">Modal for email</h3>
<p class="mt-2">Cras mattis consectetur purus sit amet fermentum. Nulla vitae elit libero augue.</p>
<form method="POST" action="#" class="mt-6">
<div class="-m-2 md:flex md:items-start">
<div class="p-2 flex-1 space-y-1">
<label for="name-input" class="inline-block text-gray-600 text-sm">Name</label>
<input id="name-input" name="name" type="text" class="w-full" placeholder="John Doe">
</div>
<div class="p-2 flex-1 space-y-1">
<label for="email-input" class="inline-block text-gray-600 text-sm">Email</label>
<input id="email-input" name="email" type="text" class="w-full" placeholder="example@northeastern.edu">
</div>
</div>
<div class="mt-6 -mx-1 flex items-center">
<div class="px-1">
<button
type="submit"
class="btn text-white bg-black border-black hover:bg-gray-800"
>
Send Email
</button>
</div>
<div class="px-1">
<button
type="button"
class="btn text-gray-800 border-black border-opacity-40 hover:bg-gray-200"
x-on:click.prevent="toggle"
>
Cancel
</button>
</div>
</div>
</form>
<button
aria-label="Close dialog"
title="Close dialog"
type="button"
class="inline-block absolute top-0 right-0 m-4 text-gray-600 hover:text-gray-800 focus:outline-none focus:ring focus:ring-blue-500"
x-on:click.stop="toggle"
>
<svg class="w-6 h-6" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
</div>
</div>
<script>
const modal = () => {
return {
open: false,
previouslyFocusedElement: null,
firstFocusableElement: null,
lastFocusableEl: null,
removedTabIndexFromFirstFocusableElement: false,
toggle() {
this.open = !this.open;
if (this.open) {
document.body.classList.add('overflow-hidden');
this.focusDialog();
return
}
document.body.classList.remove('overflow-hidden');
this.previouslyFocusedElement.focus();
if (this.removedTabIndexFromFirstFocusableElement) {
this.firstFocusableElement.setAttribute('tabindex', 0);
}
},
focusDialog() {
this.previouslyFocusedElement = document.activeElement;
setTimeout(() => {
const focusableElements = this.$refs.dialog.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex=\'0\']');
if (focusableElements.length === 0) {
return
}
this.firstFocusableElement = focusableElements[0];
this.lastFocusableElement = focusableElements[focusableElements.length - 1];
this.firstFocusableElement.focus();
if (this.firstFocusableElement.tabIndex == 0) {
this.firstFocusableElement.removeAttribute('tabindex');
this.removedTabIndexFromFirstFocusableElement = true;
} else {
this.removedTabIndexFromFirstFocusableElement = false;
}
}, 200);
},
handleBackwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.firstFocusableElement) {
e.preventDefault();
}
},
handleForwardTab(e) {
if (! this.open) {
return;
}
if (document.activeElement === this.lastFocusableElement) {
e.preventDefault();
this.firstFocusableElement.focus();
}
},
handleEscape() {
if (this.open) {
this.toggle();
}
},
}
}
</script>
These components require the button plugin.
Usage
Use modals to present a short-term task to be initiated by the user. This can be used for critical or warning information where a response is required but they’re also intended to support efficient task completion without losing the context of the underlying page.
Modals are very invasive and should be used sparingly. Try to limit the number of interactions in a modal dialog and simplify by removing unnecessary elements that does not support the task. Avoid multiple steps that require navigation within the modal dialog and avoid complex decision making that requires additional sources of information unavailable in the modal. Best practices is to support multiple methods for dismissal, but the use of both Close/Cancel and × is not required.
Use Cases
- When displaying information designed to get a user’s attention (COVID-19 announcements, school closures, events)
- When user needs to acknowledge something (cookies, content warnings, inactivity warnings)
- When prompting the user to input some information (log ins, subscribe, sign up, other forms)
- When displaying complex content after clicking a preview (spotlights, articles)
- When using a search function
- When clicking a button (support, chatbots)
These components require the button plugin.
Accessibility Requirements
After the modal opens, initial focus should be set on the first focusable element in the modal. After a modal closes, the focus should retain the user’s point of regard and return to the element that invoked the modal. Focus must not move outside the modal until it is closed. It is strongly recommended that the tab sequence of modal dialogs include a visible element that closes the dialog. Only use the Alert modal for special cases that interrupt user’s workflow to communicate a brief, important message and require a user’s response.
Modal dialogs will have a role="dialog"
or role="alertdialog"
on their container div
element. Use dialog for form-like content and use alertdialog for brief, all text modals.
Be wary of using modals on mobile devices as the modal can overtake the screen and the user may think they have gone to another page. Only use modals in mobile design if it meets one of the scenarios above and there is a clear exit strategy.
Keyboard Guidelines
The return
button and space bar
should open a modal if trigger is focused. The escape button should close an open modal and return user back to main content.