Modals
Codebase Alpine modals can be used for all sorts of things.
The Simplest Modal
Modal content. Click the “Close” button to dismiss.
The user can only dismiss the modal above by clicking the “Close” button.
<div x-data="{modal: false}">
<button class="btn btn-primary mb-3 scroll-lock" @click="modal = !modal" :aria-expanded="modal ? 'true' : 'false'">Modal example</button>
<div class="modal-wrapper" :class="{ 'active': modal }">
<div class="backdrop backdrop-shaded"></div>
<div class="modal-panel m-3 p-3 b-thin rounded bg-color-background" :class="{ 'active': modal }">
<p>Modal content.</p>
<button class="float-right scroll-unlock" @click="modal = false">Close</button>
</div>
</div>
</div>
Modal content. Click away to dismiss.
(Best practice is to include a “Close” button, but in this example I want you to try the click-away function.)
The simplest Alpine modal consists of five units:
- The Alpine modal control button (can be part of the same Alpine component, or in a separate component – see example 1c below)
- The
.modal-wrapper
element, containing three units:- The
.backdrop
- The
.modal-panel
element, containing:- The Alpine modal-close button
- The
The above elements have minimal styling – just enough to make the modal work. The .modal-control
and .modal-close
classes need to be applied to HTML <button>
elements. Put no other styling on the .modal-wrapper
– it is used as a hidden “pocket” for your modal backdrop and panel when not activated. Style the .modal-panel
how you like, e.g. using decoration utilities and/or layouts.
The backdrop element is there to prevent user interaction with anything elsefor the purpose of adding a “click-away” to dismiss functionality. Think of the backdrop as a massive, full-screen button behind the modal panel, that a visitor can click on to dismiss the modal.
By default, the .backdrop
has no background color assigned (it is invisible), but you can add the modifier .backdrop-shaded
to get the semi-transparent blurred black backdrop that most of these examples are using, or you can add .backdrop-black
for a fully black backdrop, as you can see in the “lightbox” examples below.
The backdrop is placed inside the modal wrapper, before the modal panel (so that it appears behind the modal panel):
<div class="backdrop backdrop-shaded scroll-unlock" x-show.transition.opacity.duration.600ms="modal" @click="modal = false"></div>
As follows:
<div x-data="{modal: false}">
<button class="btn btn-primary mb-3 scroll-lock" @click="modal = !modal" :aria-expanded="modal ? 'true' : 'false'">Modal example 1b</button> – with “click away”
<div class="modal-wrapper" :class="{ 'active': modal }">
<div class="backdrop backdrop-shaded scroll-unlock" x-show.transition.opacity.duration.600ms="modal" @click="modal = false"></div>
<div class="modal-panel p-3 b-thin rounded bg-color-background" :class="{ 'active': modal }">
<p>Modal content.</p>
<button class="float-right scroll-unlock" @click="modal = false">Close</button>
</div>
</div>
</div>
Modal Control Button as a Separate Component
Having a button in a separate Alpine component (using a communication event bus) enables you to have the button and the modal in different parts of a webage (and it enables you to have more than one button per modal).
Idea copied from: https://codewithhugo.com/alpinejs-component-communication-event-bus/
This modal was triggered from a separate button component outside.
<!-- The button component (separate) -->
<div x-data="{}" class="mb-3">
<button class="btn btn-primary scroll-lock" @click="$dispatch('modal-ex')">Modal example</button>
</div>
<!-- The modal component (without button) -->
<div x-data="{ modal: false }">
<div
class="modal-wrapper"
:class="{ 'active': modal }"
x-on:modal-ex.window="modal = !modal"
>
<div class="backdrop backdrop-shaded scroll-unlock" x-show.transition.opacity.duration.600ms="modal" @click="modal = false"></div>
<div class="modal-panel container-sm p-3 b-thin rounded bg-color-background" :class="{ 'active': modal }">
<p>Modal content.</p>
<button class="float-right scroll-unlock" @click="modal = false">Close</button>
</div>
</div>
</div>
Styling Modal Panels
Modal header
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Modal Header
Image Title
Lorem ipsum dolor sit amet, vis in blandit singulis, an unum doming facilisi vim. Facete aliquam bonorum id quo, ex labore tincidunt mel, usu no quod liberavisse. Ex sea dolorum insolens assueverit, sed ut harum latine dignissim. Vis cibo vidit ea, eu duo debet platonem explicari, pro ex graece meliore. Illum graeci inciderint mei et, ei decore nostro vim.
Header
Lorem ipsum dolor sit amet, vis in blandit singulis, an unum doming facilisi vim. Facete aliquam bonorum id quo, ex labore tincidunt mel, usu no quod liberavisse. Ex sea dolorum insolens assueverit, sed ut harum latine dignissim. Vis cibo vidit ea, eu duo debet platonem explicari, pro ex graece meliore. Illum graeci inciderint mei et, ei decore nostro vim.
Number of items:
Lorem ipsum dolor sit amet
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. |
Use .modal-panel.modal-panel-cover
for a full cover modal:
Full-Cover Modal
Department 1
Department 2
Department 3
Department 4
Department 5
Department 6
Design the modal panels any way you want. But you will want to control the width, and control the height for scrollable panels (example 3c). Use .modal-panel-cover
for full cover modals.
<!-- Example 2a -->
<div class="modal-panel w-xs" :class="{ 'active': modal }">...</div>
<!-- Example 2b -->
<div class="modal-panel w-lg flex flex-column" :class="{ 'active': modal }">...</div>
<!-- Example 2c -->
<div class="modal-panel w-xs flex flex-column" :class="{ 'active': modal }">...</div>
<!-- Example 2d -->
<div class="modal-panel modal-panel-cover" :class="{ 'active': modal }">...</div>
(In addition the the modal and layout classes in the example code above, you will need decorative utility classes for borders, box shadows, background colors, etc.)
A Modal as a Lightbox
Modals can be made into a lightbox by placing the “close” button within the backdrop instead of within the panel. In these examples I have also used .backdrop-black so that the visitor’ more is more drawn to the image. (And I have removed the click-outside.)
Codebase modals have their content width and height and constrained to fit within the viewport (with the max-height further constrained, to account for the iOS Safari browser bar). Therefore, oversized images will be scaled down if necessary.
In these examples, the close (dismiss) button is within the black backdrop’s top-right corner using flex layout.
A tall narrow image (500px × 1000px)
A short wide image (2000px × 500px)
A tall wide image (2000px × 2000px)
<div x-data="{modal: false}">
<button class="btn btn-primary mb-3 scroll-lock" @click="modal = !modal" :aria-expanded="modal ? 'true' : 'false'">Modal example</button>
<div class="modal-wrapper" :class="{ 'active': modal }">
<div class="backdrop backdrop-black scroll-unlock flex flex-top flex-end p-2">
<button class="modal-close btn btn-transparent btn-icon b-0" @click="modal = false">
×
</button>
</div>
<div class="modal-panel p-3" :class="{ 'active': modal }">
<img src="" alt="">
<p class="p-block -color-ui-text t-center">Figure legend</p>
</div>
</div>
</div>
Scroll-Lock
You can improve the user experience of modals by adding a “scroll lock” – to prevent the page scrolling behind the opened modal.
All you need for this is a tiny JavaScript that looks for some extra CSS classes that you will add to your AlpineJS modal components (.scroll-lock
on the controller button, and .scroll-unlock
on the close button and the optional “click away” click event on the backdrop.
The scroll-lock JavaScript is as follows:
const scrLock = Array.from(document.querySelectorAll('.scroll-lock'))
const scrUnlock = Array.from(document.querySelectorAll('.scroll-unlock'))
scrLock.forEach(el => {
el.addEventListener('click', () => {
document.querySelector('body').classList.add('body-scroll-lock')
});
})
scrUnlock.forEach(el => {
el.addEventListener('click', () => {
document.querySelector('body').classList.remove('body-scroll-lock')
});
})
The script above just adds or removes the CSS class .body-scroll-lock
to/from your <body>
tag.
The Codebase CSS class .body-scroll-lock
is what actually performs the scroll lock:
.body-scroll-lock {
touch-action: none;
overflow: hidden;
}