Codebase

Documentation

Responsive Layouts
Utilities
Components
AlpineJS Components

Modals

Codebase Alpine modals can be used for all sorts of things.

The Simplest Modal

– without “click-away”

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>
– with “click away”

(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 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>
– with a button in a separate Alpine 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/

<!-- 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

– panel is dressed as a card, with “click-away”
– with a grid system, with “click-away”
– with scrollable content, and no “click away”

Use .modal-panel.modal-panel-cover for a full cover modal:

– with full cover content, no need for a backdrop, and no “click-away”

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.

– modal with a tall narrow image
– modal with a short wide image
– modal with a gigantic image
<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;
}