{{craftSnippets}}
Home Articles Plugins Starter theme Components on Github Gists About
  • Home
  • Articles
  • Alpine JS modal component for Craft CMS
Posted on Dec 03, 2020 by Piotr Pogorzelski

Alpine JS modal component for Craft CMS

Modal component on github gists
image/svg+xml Alpine JS modal demo

Notification

Since the writing of this article, i released Modal component plugin - i recommend using it instead of the method described in this article.
Modal component plugin uses robust a11y-dialog library instead of Alpine JS.

Content-agnostic and easy to customize modal component made with Alpine JS.
Table of contents:
  • Introduction
  • Usage
  • Customizing modal
  • About Alpine JS

Introduction #

In this article, I will describe modal component created using Alpine JS and Twig templating language. It is designed to be used with Craft CMS, but with little modifications, it will work in any system that uses Twig - for example, Drupal. It can also probably be easily ported to Blade, Smarty, or other templating systems.

If you want to quickly see what component looks like, you can visit demo page. You can grab component code from github gists.

Here is how the modal component looks like - it contains only bare-bones styling and is content-agnostic. In this case, we injected picture of kitten and header into it.

Summary of modal functionality. It does pretty much what most commonly used modals do:

  • Modal is displayed and hidden using simple fade-in and fade-out animations. When it is opened, website content underneath it is covered with overlay and scrolling is blocked (thanks to setting overflow: hidden to <html> tag).
  • Modal container has specific width on desktop (40rem, set using CSS custom property), while on mobile it will just fit the screen. Modal content can be scrolled if it does not fit the screen height.
  • Modal can be closed by clicking on x icon in the upper right corner or just by clicking outside of its container. Clicking inside, while letting on mouse button outside will not close modal.
  • Modal contents are sitting within <template> element when modal is not displayed. Thanks to that, if modal has images inside, they will not get unnecessarily loaded before the user opens modal.
  • Modal contains ARIA attributes that make it accessible.

Usage #

Links to Alpine CDN are included in the component, so you don't need to attach any additional libraries to your website. Content is inserted into modal using {% embed %} Twig tag. You also need to provide a modal handle, so multiple instances of modal can be used. Handle cannot contain dashes.

{% embed 'path_to_modal_file' with {
  modalHandle: 'someModal'
} %}
{% block modalContent %}
    <div>our modal content</div>    
{% endblock %}
{% endembed %}

Thanks to usage of embed tag, modal functionality can fit seamlessly into your existing Twig templates. You don't need to put HTML content into Javasript variable or use DOM selectors, as is sometimes a case with modal libraries.

There are three Javascript functions that are available outside the modal component and are used to interact with it:

alpineModalOpen() function is used to open modal. The first param of function is a modal handle, second one is optional array of options. This array can contain onOpen option, that can be set to callback function that will run after modal opens - useful for dynamically replacing modal contents, for example with something returned by AJAX call.

alpineModalOpen('someModal');
// or: 
alpineModalOpen('someModal', {
    onOpen: function(){
        document.querySelector('[data-modal-content]').innerHTML = 'something';
    }
})

alpineModalClose() is used to programatically close modal. It also requires modal handle parameter:

alpineModalClose('someModal')

alpineModalCloseAll() can be used to close all modals, regardless of their handle.

alpineModalCloseAll()

If you want to close modal using some additional button placed inside its content, you can use Alpine @click attribute and set it to closeModal(). This is the internal method of Alpine component that closes modal - keep in mind that it will only work inside component:

{% embed 'path_to_modal_file' with {
  modalHandle: 'someModal'
} %}
{% block modalContent %}
    <div>our modal content</div>    
    <button @click="closeModal()">cancel</button>
{% endblock %}
{% endembed %} #}

Since modal contents are appended to DOM from inside <template> tag when modal is not opened, you cannot attach JS events to them with regular way. You need to use event delegation - here's jQuery example:

// will work
$('body').on('click', '.some-class-inside-modal', function(){
    // do dsomething..
})
// won't work
$('.some-class-inside-modal').on('click', function(){
    // do dsomething..
})

Customizing modal #

Modal componenent was created with customization in mind. You can easily add new blocks to it, for example block that contains header - just add it to modal-component__content element:

<div 
    class="modal-component__content"
    data-modal-content
>   
<div class="custom-modal-header">
    {% block modalContent %}{% endblock %}
</div>
    {% block modalContent %}{% endblock %}
</div> 

Modal uses just Bare-bones styling. You can easily add your own decorations, like curved borders and box-shadow. You can also easily change color of background, width or padding, using custom properties that can be overwritten:

--modal-background-outside: rgba(0,0,0,0.8);
--modal-background-inside: white;
--modal-width: 40rem;
--modal-padding: 1rem;

If you have multiple modals, each of them must have a unique handle. You can give these modal differing styles - modal element will have CSS class based on its handle, like modal-component--someHandle.

About Alpine JS #

Alpine JS is a library designed to add bits of interactivity to server-generated templates. Its syntax is based on Vue JS, but it does not offer any kind of component functionality - it just adds reactivity to markup using HTML attributes. That's why we need to rely on Twig templating language to create Alpine components and include them in our code. And that's why it is a perfect match for Craft CMS.

Teaching Alpine is beyond scope of this article - so I suggest anyone interested to head to one of these resources:

  • https://github.com/alpinejs/alpine
  • https://www.smashingmagazine.com/2020/03/introduction-alpinejs-javascript-framework/
  • https://codewithhugo.com/tags/alpinejs/

But why build modal in Alpine JS at all, instead of using some modal library? Thanks to Alpine JS, we can easily customize how our modal behaves.

For example, there's one thing that really annoyed me about most modal libraries - how they handled closing modal when the user clicked outside of it. Or not really outside - when the user clicked inside and let of mouse button outside, it was still registered as outside click and triggered the closing of modal. Now imagine that modal contains contact form. Users, (me included) often click inside specific input to type there, and before they start typing, they let of the mouse button - often while the cursor already moved a bit. If cursor moved outside of modal border, modal closes. And all form contents are lost.

Building modal functionality using Alpine allowed me to easily remedy such problems and customize its functionality on a level usually not achievable with most modal libraries.


If you want to get latest updates on Craft CMS tutorials and components, follow me on Twitter or subscribe to RSS feed.
Quick links for this article:
Modal component on github gists
image/svg+xml Alpine JS modal demo
Articles on blog:
  • User account management with Craft CMS
  • Frontend testing for Craft CMS websites with Codeception and Cypress
  • Building reactive Craft Commerce product page with Sprig plugin
  • Dynamically generated PDF attachments for Freeform submissions
  • Using template hooks in Craft CMS
  • Alpine JS modal component for Craft CMS
  • Using template UI elements to extend Craft CMS control panel
  • Matrix within a Matrix - possible solutions for Craft CMS
  • Universal email template for Craft CMS
  • Creating attributes table from entry fields in Craft CMS
  • Namespacing forms in Craft CMS
  • Creating map-based navigation for Craft CMS
  • Placeholder image macro for Craft CMS
  • Building AJAX contact form with Craft CMS
  • Using incognito field plugin for Craft CMS
  • Email footer creator made with Craft CMS
  • Infinite scrolling and lazy loading with Craft CMS
  • Using Javascript in Twig templates with Craft CMS
  • Twig templating tips and tricks for Craft CMS
  • Basic SEO functionality for Craft CMS
  • Working with dates in Craft CMS templates
  • Working with SVG images in Craft CMS templates
  • Responsive and lazy-loaded youtube videos with Craft CMS
  • Debugging and inspecting Twig templates in Craft CMS
  • Creating article excerpts with Twig component in Craft CMS
  • Adding favicons to Craft CMS website
  • Truncating text with Twig macros in Craft CMS
  • Universal language switcher for Craft CMS
  • Read time macro for Craft CMS
  • Using attr() function to render HTML attributes in Craft CMS
  • Building dynamic, AJAX based pagination for Craft CMS
  • How to add Disqus comments to Craft CMS website
  • Ellipsis pagination component for Craft CMS
  • Converting email addresses into links using Twig macro
  • Breadcrumb created from URL for Craft CMS
  • Best developer-oriented Craft CMS plugins
  • Search autocomplete component for Craft CMS
  • RSS feed - template component for Craft CMS
  • Testing emails sent by Craft CMS using Mailtrap
  • Quick edit link - Twig component for Craft CMS
  • Filtering entries in control panel using Searchit plugin
  • Fetching routes into Twig templates in Craft CMS


User account management with Craft CMS

Frontend testing for Craft CMS websites with Codeception and Cypress

Building reactive Craft Commerce product page with Sprig plugin

Dynamically generated PDF attachments for Freeform submissions

Using template hooks in Craft CMS

Using template UI elements to extend Craft CMS control panel

Matrix within a Matrix - possible solutions for Craft CMS

Universal email template for Craft CMS

Copyright ©2023 Piotr Pogorzelski