Modal

THE BUTTER

  • .modal-overlay
  • .modal
  • .modal-dialog
  • .modal-content
  • .modal-header
  • .modal-title
  • .modal-close
  • .modal-body
  • .modal-footer
  • .modal-center .modal-bottom-sheet .modal-full-height .modal-full-screen .modal-half-screen .reverse
  • .animate .fromtop .fromleft .fromright .frombottom

A modal window from a CSS point of view is very simple, what is left is to connect the behavior to javascript button clicks to show and hide. Also, no real stying is added in framework, allowing for full custom styling.

The window

The dialog HTML is as follows, in its basic shape (javascript is needed to make it interactive).

<div class="modal-overlay">
    <div class="modal">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h6 class="f6 modal-title">Title of dialog</h6>
                    <button type="button" class="modal-close"></button>
                </div>
                <div class="modal-body">
                    Content of body
                </div>
                <div class="modal-footer">
                    footer content
                </div>
            </div>
        </div>
    </div>
</div>

Flavors

To use any of the following styles of the modal, wrap the modal window or apply the classname to the root node.

<div class="modal-overlay modal-center">
    <div class="modal">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h6 class="f6 modal-title">Title of dialog</h6>
                    <button type="button" class="modal-close"></button>
                </div>
                <div class="modal-body">
                    Content of body
                </div>
                <div class="modal-footer">
                    footer content
                </div>
            </div>
        </div>
    </div>
</div>

or wrap it
<div class="modal-center">
    <div class="modal-overlay">
        <div class="modal">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h6 class="f6 modal-title dr-title">Title of dialog</h6>
                        <button type="button" class="modal-close"></button>
                    </div>
                    <div class="modal-body">
                        Content of body
                    </div>
                    <div class="modal-footer">
                        footer content
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
.modal-center
.modal-bottom-sheet
.modal-full-height
.modal-full-screen
.modal-half-screen
90vw of width in case of mobile screens.
.modal-half-screen.reverse

Animate

Animation through css is the best way to animate. Following are basic class names you can add to the wrapper, or root node. When binding behavior, apply the rule to show or hide to the .modal-overlay element, to get results. The animation rules apply to all modals, but below are the best suitable combination for each animation.

.animate.fromtop
.animate.fromleft.modal-half-screen
.animate.fromright.modal-half-screen.reverse
RTL: The framework already contains enough definitions to mirror.
.animate.frombottom.modal-bottom-sheet

LESS variables

Following is the list of LESS variables that affect the dialog style.

@modal-bg
The modal overlay background color.
Default: fade(@black, 60)
@modal-zindex
The reference z-index of the modal window, the overlay itself is @modal-zindex - 10
Default: 1040
@modal-margin
The margin of the dialog window away from edges in small screens. Once width of browser reaches md minimum width, the margins are set to auto
Default: @space
@modal-padding
The padding of the header, body, and footer elements.
Default: @halfspace
@modal-md-width
The maximum default width of dialog when browser reaches md minimum width.
Default: 700px
@modal-scrollbar-width
The width of the scrollbar when applicable on body.
Default: 5px
@trans-func
The animation transition function.
Default: cubic-bezier(.4,0,.2,1);

Example customization

You can pass any wrapper style to redefine the look and feel, here are some examples.

// define a close icon for the modal-close
.modal-close {
    .getIcon(@icon-close);
}

// add style for footer
.modal-footer {
    background-color: @grey-light;
}

// yellow info dialog
.modal-info {
    .modal-dialog {
        max-width: 30vw;
        width: 300px;
    }
    .modal-content {
        background-color: @yellow-light;
    }
    .modal-footer {
        background-color: @yellow;
    }
}

Bonus: mobile dynamic height

If you designed interfaces for mobile devices, you probably ran into this problem, where the browser address bar appears, or disappears according to user swiping up, or down. This affects the available height of the content without changing vh value. To fix that, with a little of javascript, here is what you can do.

 function setValue() {
    // dynamically set the --vh css variable
    const vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);
}

// set on load of page, and also watch window resize
setValue();
window.addEventListener('resize', setValue);

In your css (less):

.modal-content {
    max-height: calc((var(--vh, 1vh) * 100));
}
.modal-half-screen .modal-content {
    min-height: calc((var(--vh, 1vh) * 100));
    max-height: calc((var(--vh, 1vh) * 100));
}    

Bonus: Javascript

Following is the least amount of javascript needed to get modals to work.


    class Modal {

        config = {
            closeSelector: '.modal-close',
            modalContentSelector: '.modal-content',
            triggerAttr: 'data-trigger'
        };
    
        constructor(element) {
            this.modalElement = element;
            this.trigger = element.getAttribute(this.config.triggerAttr);
    
            this.modalElement.addEventListener('click', event => {
                const target = event.target;
    
                if (!target.closest(this.config.modalContentSelector)) {
                    this.hide();
                }
                // if target is close, hide
                if (target.matches(this.config.closeSelector)) {
                    this.hide();
                }
            });
    
            window.document.addEventListener('click', event => {
                const target = event.target;
    
                if (this.trigger) {
                    if (target.matches(this.trigger) || target.closest(this.trigger)) {
                        // itself or its parent
                        this.show();
                    }
                }
            });
        }
        show = () => {
    
             this.modalElement.style.display = 'block';
        };
    
        hide = () => {
            this.modalElement.style.display = 'none';
            
        };
    
    
    }
    
    function setVh() {
        const vh = window.innerHeight * 0.01;
        document.documentElement.style.setProperty('--vh', `${vh}px`);
    }
    window.addEventListener('DOMContentLoaded', (event) => {
    
        // set --vh
        setVh();
    
        window.addEventListener('resize', setVh);
    
        // find dialogs and attach behaviors
        const modals = document.querySelectorAll('.modal-overlay');
    
        modals.forEach(n => {
    
            new Modal(n);
    
        });
    
    });