Please wait...
Page is loading

Modals

BS4 theme modals is an enhancement of BS modal . Its main purpose is to change default functionality and add support for new modal variants.

Getting started

To use BS4 modal simply include bs4 theme in your bundle file

//= require vendor/bootstrap-4.4.1-iplan-theme.bundle

Default behaviour

BS4 theme modal changes the default BS modal behaviour:

  • Only when screen is xs modal will pop from the bottom of the page (with an animation) and expand into the entire width of the screen by default. It's height will grow to the page height minus a certain gap
  • Static modal will be hidden when pressing the Escape key (This is not the default BS static modal behaviour).
  • Opening a modal will hide visible popovers outside the modal.
  • You can add data-modal-dialog-class attr to the button that opens the modal to change the modal's modal-dialog class
  • When modal is open the body will be frozen to prevent scrolling outside the modal
  • Every modal will have the BS class .modal-dialog-scrollable , which means every .modal-body will have max-height and be scrollable when content is long. This means that modal can't expand beyond the page height.
click to show popover
this popover will not dismiss on blur but will be dismissed when modal is opened
.row
  .col-24.col-md-auto
    =button_tag 'modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#default-behaviour", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'static modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#default-behaviour-static", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'modal with long content', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#default-behaviour-long-content", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'modal dialog class', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': 'modal-left', 'data-target': "#default-behaviour", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'mini modal in xs screen', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': 'h-auto', 'data-target': "#default-behaviour", type: 'button'

.mt-2
  =link_to "click to show popover", 'javascript:;', class: '', 'data-toggle': "popover", 'data-trigger': "click", 'data-dismiss-on-blur': false, title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
  .small.text-muted='this popover will not dismiss on blur but will be dismissed when modal is opened'

.modal#default-behaviour{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Normal modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        .alert.alert-info
          Try resizing the window and see how the modal behaves on xs screen
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the relea
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#default-behaviour-static{tabindex: '-1', 'data-backdrop': "static"}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Normal modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        .alert.alert-info
          Try clicking with the mouse outside the modal and then try pressing Escape key
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#default-behaviour-long-content{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Normal modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        .alert.alert-info
          Note that the modal will not exceed page height even though content inside the modal body is long
        .alert.alert-info
          Try resizing the window and see how the modal behaves on xs screen.
        -10.times do
          Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Structure

BS4 theme modal structure is important and should be as follows:

  1. A .modal should have a unique ID and {tabindex: "-1"}
  2. First .modal child should be .modal-dialog
  3. First .modal-dialog child should be .modal-content
  4. Inside .modal-content you should place .modal-header, .modal-body and .modal-footer, it is important to keep the order but not all of those elements are mandatory (you can skip .modal-header for example)
  5. Inside .modal-header you can add .modal-subtitle before or after .modal-title, just note that they will have to be wrapped with an empty .text-truncate cause .modal-header is display: flex and .modal-title should be text-truncated
    Please also note that when using .modal-subtitle after title, you should manually set title to line height 1 with .lh-1

Its not a good idea to break modal structure but if needed it can be done following this guidelines:

  • rules 1, 2, 3 are mandatory
  • You can wrap .modal-header, .modal-body and .modal-footer in a wrapper but this wrapper should have height: 100%, display: flex and flex-direction: column You can use BS classes .d-flex.flex-column.h-100 to achive this
=button_tag 'modal with broken structure', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#broken-structure", type: 'button'
=button_tag 'modal with subtitle before', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-with-subtitle-before", type: 'button'
=button_tag 'modal with subtitle after', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-with-subtitle-after", type: 'button'

.modal#broken-structure{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-content-wrapper.d-flex.flex-column.h-100
        .modal-header
          %h5.modal-title='Normal modal'
          =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
            %span ×
        .modal-body
          Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
        .modal-footer
          =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#modal-with-subtitle-before{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        .text-truncate
          .modal-subtitle.small.text-muted='This is modal subtitle before title'
          %h5.modal-title='Normal modal with sutitle'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#modal-with-subtitle-after{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        .text-truncate
          %h5.modal-title.lh-1='Normal modal with sutitle'
          .modal-subtitle.small.text-muted='This is modal subtitle after title'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Height

BS4 theme modal changes the default BS modal behaviour:

  • By default BS4 Modal has a minimum height of 20rem, but in XS screen it will grow to fill the entire screen (leaving a small gap from the top)
  • You can use h-auto on .modal-dialog (height: auto) to make the modal shrink to fit the content (check "mini modal on xs screen" button below)
  • When using a remote modal that shrinks to take less space, in XS screen you should take into account that the animation will grow to fit the loading modal state and when content arrives the height will change immediately (no animation), This will look and feel buggy. So when using a remote modal that shrink you should give it a specific fixed height (for example you can use h-40 or give it a specific height like 20rem)
.row
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'h-auto modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': 'h-auto', 'data-target': "#h-auto-modal", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'Remote modal with fixed height in xs (40%)', class: 'btn btn-light', 'data-toggle': "remote-modal", 'data-target': '#fixed-height-remote-modal-in-xs', 'data-modal-dialog-class': 'h-only-xs-40', 'data-remote-modal-url': components_remote_modal_path(height: true), type: 'button'


.modal#h-auto-modal{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Normal modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        .alert.alert-info
          Resize the window, see how the modal behaves on xs screen
        Min height on screens from sm is 20rem, in xs modal will shrink to fit content
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Full screen modal

On top of the default modal sizes in BS (.modal-lg, .modal-sm) BS4 theme modal adds:

  • .modal-fs - expands across the entire screen
  • .modal-fs-only-* - expands across the entire screen only for a certain screen size
  • .modal-fs-* - expands across the entire screen from a certain screen size
Full screen on xs screen
You can set .modal-fs-only-xs but most of the time it's not required. If you don't setup .modal-fs-only-xs the modal still expands in it's width to the entire screen and will allow the modal height to cover almost the entire screen.
.row
  .col-24.col-sm-auto
    =button_tag 'full screen modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-size-variants", 'data-modal-dialog-class': "modal-fs", type: 'button'
  .col-24.col-sm-auto.mt-2.mt-sm-0
    =button_tag 'full screen modal only on xs screen', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-size-variants", 'data-modal-dialog-class': "modal-fs-only-xs", type: 'button'
  .col-24.col-sm-auto.mt-2.mt-sm-0
    =button_tag 'full screen modal from md screen', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-size-variants", 'data-modal-dialog-class': "modal-fs-md", type: 'button'

.modal#modal-size-variants{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Full screen modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Side Modal

BS4 theme Modal adds an option to open the modal from the side (left or right).

  • Side modal will always take the entire height of the screen and the .modal-body will expand accordingly
  • Side modal will pop with an animation from left or right
  • Side modal width will be like regular modal, the width will be the same and can be mixed with .modal-lg / .modal-sm / .modal-fs (mainly useful with .modal-fs-only-xs to get full screen modal on mobile), it can also be static or not etc... The only caveat is that side modal width will take 80% of the screen on small screens
  • The variant class - .modal-left or .modal-right should be places on the modal dialog. you can also use data-modal-dialog-class to set it.
.row
  .col-24.col-md-auto
    =button_tag 'left modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-left", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'right modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-right", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'static left modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-left", 'data-target': "#modal-side-variants-static", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'static right modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-right", 'data-target': "#modal-side-variants-static", type: 'button'
.row.mt-2
  .col-24.col-md-auto
    =button_tag 'left modal lg', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-left modal-lg", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'right modal xl', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-right modal-xl", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'right modal fs', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-right modal-fs", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'right modal fs only xs', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-right modal-fs-only-xs", 'data-target': "#modal-side-variants", type: 'button'



.modal#modal-side-variants{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Side modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#modal-side-variants-static{tabindex: '-1', 'data-backdrop': "static"}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Static Side modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Corner Modal

BS4 theme Modal adds an option to open the modal from the corner (top left, bottom left or top right, bottom right).

  • Corner modal will always shrink to the height of the content
  • Corner modal will pop with an animation from bottom or top
  • Corner modal width will be like regular modal, the width will be the same and can be mixed with .modal-lg / .modal-sm / .modal-fs (mainly useful with .modal-fs-only-xs to get full screen modal on mobile), it can also be static or not etc... The only caveat is that Corner modal width will take 80% of the screen on small screens
  • The variant class - .modal-corner-left-bottom or .modal-corner-right-bottom or .modal-corner-left-top or .modal-corner-right-top should be places on the modal dialog. you can also use data-modal-dialog-class to set it.
.row
  .col-24.col-md-auto.mb-2
    =button_tag 'corner bottom left modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-bottom-left", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mb-2
    =button_tag 'corner bottom right modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-bottom-right", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mb-2
    =button_tag 'corner top left modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-top-left", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mb-2
    =button_tag 'corner top right modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-top-right", 'data-target': "#modal-side-variants", type: 'button'

.row.mb-n2
  .col-24.col-md-auto.mb-2
    =button_tag 'STATIC corner bottom left modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-bottom-left", 'data-target': "#modal-corner-variants-static", type: 'button'
  .col-24.col-md-auto.mb-2
    =button_tag 'corner bottom left modal lg', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-bottom-left modal-lg", 'data-target': "#modal-side-variants", type: 'button'
  .col-24.col-md-auto.mb-2
    =button_tag 'corner bottom left modal > xs, h-auto ', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-corner-bottom-left-sm h-auto ", 'data-target': "#modal-side-variants", type: 'button'

.modal#modal-corner-variants{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Corner modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#modal-corner-variants-static{tabindex: '-1', 'data-backdrop': "static"}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Static Side modal'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

A form can be placed inside the .modal-body but many times we need to place the submit button inside the modal footer, so instead of placing the form inside .modal-body we make the from be form.modal-content, this way we don't break the modal structure

=button_tag 'modal with form', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-with-form", type: 'button'

.modal#modal-with-form{tabindex: '-1'}
  .modal-dialog
    %form.modal-content{onsubmit: 'alert("form was submitted!")', 'data-remote': 'true', 'data-spin-on-ajax': 'false'}
      .modal-header
        %h5.modal-title='Modal with form'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =text_field_tag 'modal-with-form-text-field'
      .modal-footer
        .row.flex-fill
          .col
            =button_tag 'submit', class: 'btn-block btn btn-primary'
          .col
            =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Animation & Page scroll

When modal is opened we freeze the body to prevent scrolling outside the modal. "Freezing" the body means setting it's position to fixed, this normally will break page scroll position, but BS4 theme modal deals with this.

Every modal has a backdrop with fade effect that covers the page. and will appear on the screen with an animation effect

  • Fade and slide down - all modals on screens bigger than xs (except side modals)
  • Slide from bottom to top - all modals on xs screen (except side modals)
  • Slide from left / right - side modals (screen size doesn't matter)
=button_tag 'modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#default-behaviour", type: 'button'
=button_tag 'right modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-modal-dialog-class': "modal-right", 'data-target': "#modal-side-variants", type: 'button'

History state

  • When a modal opens it adds its ID to the history state and the URL.
  • When a modal is closed it removes its ID from the history state and the URL.
  • When a modal is open and we press the browser back button the modal will close instead of refreshing the page.
  • When a modal is open and we refresh the page the modals ID will be removed from the URL (if the modal is still in the page)
the modal id is #default-behaviour

Stacked modals

You can stack modals on top of each other, just open a modal when another modal is open. You can even mix modal sizes and variants (e.g .modal-right, .modal-lg etc...)

  • Modal stacking is supported up to 4 modals
  • Each modal will have data-modal-stack='x' that will represent it's place in the stack
  • First modal will have data-modal-stack='0'
  • The body will have data-modal-stack='x' that will represent the current active modal stack
  • Non side modals will shrink in height and have more margin from the top as the stack grows, this will create make the modals appear as if they are stacked
=button_tag 'stacked modal 0', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#stacked-modal-0", type: 'button'

.modal#stacked-modal-0{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Stacked modal - 0'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =button_tag 'stacked modal 1', class: 'btn btn-light btn-block mb-2', data: {toggle: 'modal', target: '#stacked-modal-1', stack: 'modal'}, type: 'button'
        =button_tag 'modal 1', class: 'btn btn-light btn-block mb-2', 'data-toggle': "modal", 'data-target': "#stacked-modal-1", type: 'button'
        -10.times do
          Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#stacked-modal-1{tabindex: '-1'}
  .modal-dialog.modal-lg
    .modal-content
      .modal-header
        %h5.modal-title='Stacked modal - 1'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =button_tag 'stacked modal 2', class: 'btn btn-light btn-block mb-2', data: {toggle: 'modal', target: '#stacked-modal-2', stack: 'modal'}, type: 'button'
        =button_tag 'modal 2', class: 'btn btn-light btn-block mb-2', 'data-toggle': "modal", 'data-target': "#stacked-modal-2", type: 'button'
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#stacked-modal-2{tabindex: '-1', 'data-backdrop': "static"}
  .modal-dialog.modal-right
    .modal-content
      .modal-header
        %h5.modal-title='Stacked modal - 2'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =button_tag 'stacked modal 3', class: 'btn btn-light btn-block mb-2', data: {toggle: 'modal', target: '#stacked-modal-3', stack: 'modal'}, type: 'button'
        =button_tag 'modal 3', class: 'btn btn-light btn-block mb-2', 'data-toggle': "modal", 'data-target': "#stacked-modal-3", type: 'button'
        -10.times do
          Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#stacked-modal-3{tabindex: '-1', 'data-backdrop': "static"}
  .modal-dialog.modal-sm
    .modal-content
      .modal-header
        %h5.modal-title='Stacked modal - 3'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Remote modals

Remote modal behaves just like a modal and using it is the same. The modal itself is created dynamically when the link/button is pressed and will have .remote-modal class. You can open a modal manually using $.modalManager.remoteManager.open(...) but the common way is via link/button. To open a remote modal create a link/button with the following:

  • data-toggle: 'remote-modal' (instead of just 'modal')
  • data-target: by default unique ID will be generated automatically, but you can pass '#xxx' (a unique ID that doesn't exist in the page)
  • data-remote-modal-url: 'xxx' (the url to load)

Couple of things to note

  • Remote modal are static by default
  • You shouldn't use auto height (to achieve mini modal in mobile) on modal-dialog when modal is remote cause it's height is unknown and will be given only when content is loaded
  • Remote modal can be stacked with {data: {stack: modal}}
  • Modal dialog class can be set from the server with data-modal-dialog-class attribute on modal content. Note that this is a bad practice cause the modal size will change only when response is received from the server.
.row
  .col-24.col-md-auto
    =button_tag 'Stacked', class: 'btn btn-light', 'data-toggle': "remote-modal", 'data-target': '#stacked-remote-modal-0', 'data-remote-modal-url': components_remote_modal_path(stack: 0), type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'Right', class: 'btn btn-light', 'data-toggle': "remote-modal", 'data-target': '#stacked-remote-modal-0', 'data-remote-modal-url': components_remote_modal_path(stack: 3), type: 'button', 'data-modal-dialog-class': 'modal-right'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'Dialog class from server', class: 'btn btn-light', 'data-toggle': "remote-modal", 'data-target': '#stacked-remote-modal-0', 'data-remote-modal-url': components_remote_modal_path(stack: 3, data_modal_dialog_class: 'modal-lg'), type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'Error', class: 'btn btn-light', 'data-toggle': "remote-modal", 'data-method': 'POST', 'data-remote-modal-url': components_modal_path(result: 500), type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'Manual', class: 'btn btn-light', onclick: "$.modalManager.remoteManager.open('#manual-remote-modal', '#{components_remote_modal_path(stack: 0)}');", type: 'button'

Referrer modal

If a modal was opened from another modal (or when another modal was open) in the same stack, the referrer modal will be saved in the history stack. The new modal will have the class .has-referrer-modal and you can add a link in the header with data: {back: 'modal'} that will revert to the previous modal. This works for inline modals and remote modals.

=button_tag 'Referrer inline modal - 0', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#referrer-inline-modal-0", type: 'button'

.modal#referrer-inline-modal-0{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Referrer inline modal - 0'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =button_tag 'Inline referrer modal 1', class: 'btn btn-light btn-block mb-2', data: {toggle: "modal", target: "#referrer-inline-modal-1"}, type: 'button'
        =button_tag 'Inline referrer modal 1 stacked', class: 'btn btn-light btn-block mb-2', data: {toggle: 'modal', target: '#referrer-inline-modal-1', stack: 'modal'}, type: 'button'
        =button_tag 'Remote modal 1', class: 'btn btn-light btn-block mb-2', 'data-toggle': "remote-modal", 'data-remote-modal-url': components_remote_modal_path(stack: 1), type: 'button'
        =button_tag 'Remote modal stacked 1', class: 'btn btn-light btn-block mb-2', 'data-toggle': "remote-modal", 'data-stack': 'modal', 'data-remote-modal-url': components_remote_modal_path(stack: 1), type: 'button'

      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#referrer-inline-modal-1{tabindex: '-1'}
  .modal-dialog.modal-lg
    .modal-content
      .modal-header
        =link_to 'javascript:;', class: 'modal-header-back', data: {back: 'modal'} do
          %i.far.fa-arrow-left
          =t('application.back')
        %h5.modal-title='Referrer inline modal - 1'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =button_tag 'Inline referrer modal 2', class: 'btn btn-light btn-block mb-2', 'data-toggle': "modal", 'data-target': "#referrer-inline-modal-2", type: 'button'
        =button_tag 'Inline referrer modal 2 stacked', class: 'btn btn-light btn-block mb-2', data: {toggle: 'modal', target: '#referrer-inline-modal-2', stack: 'modal'}, type: 'button'
        =button_tag 'Remote modal 2', class: 'btn btn-light btn-block mb-2', 'data-toggle': "remote-modal", 'data-target': '#stacked-remote-modal-2', 'data-remote-modal-url': components_remote_modal_path(stack: 2), type: 'button'
        =button_tag 'Remote modal stacked 2', class: 'btn btn-light btn-block mb-2', 'data-toggle': "remote-modal", 'data-target': '#stacked-remote-modal-2', 'data-remote-modal-url': components_remote_modal_path(stack: 2), type: 'button'

      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

.modal#referrer-inline-modal-2{tabindex: '-1'}
  .modal-dialog.modal-lg
    .modal-content
      .modal-header
        =link_to 'javascript:;', class: 'modal-header-back', data: {back: 'modal'} do
          %i.far.fa-arrow-left
          =t('application.back')
        %h5.modal-title='Referrer inline modal - 2'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        asd

      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

Nested modals

Some times we have no choice but to nest a modal inside a modal (mainly in remote modal). Since parent modal is positioned "fixed" the nested modal will not be able to expand to the entire screen and it's z-index will inherit the parent z-index.

  • Bs4 modal deals with that issue by repositioning the nested modal outside the modal (right after it).
  • In a remote modal, when parent modal is hidden and then removed the repositioned child modal will also be removed
Modal with nested modal in it
.small.text-muted.mb-2 Modal with nested modal in it
=button_tag 'Modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-with-nested-modal", type: 'button'
=button_tag 'Remote modal', class: 'btn btn-light', 'data-toggle': "remote-modal", 'data-target': '#remote-modal-with-nested-modal', 'data-remote-modal-url': components_remote_modal_path(nested: true), type: 'button'

.modal#modal-with-nested-modal{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Modal with a modal nested in it'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        =button_tag 'Nested modal', class: 'btn btn-light btn-block', 'data-toggle': "modal", 'data-target': "#nested-modal-in-modal", type: 'button'
        =button_tag 'Nested stacked modal', class: 'btn btn-light btn-block', 'data-stack': 'modal', 'data-toggle': "modal", 'data-target': "#nested-modal-in-modal", type: 'button'
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

      .modal#nested-modal-in-modal{tabindex: '-1'}
        .modal-dialog.modal-lg
          .modal-content
            .modal-header
              %h5.modal-title='Nested modal'
              =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
                %span ×
            .modal-body
              this modal is nested
            .modal-footer
              =button_tag 'close', type: 'button', class: 'btn-block btn btn-secondary', 'data-dismiss': "modal"

asddas

  • asdasd
=button_tag 'Modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#modal-states", type: 'button'

.modal#modal-states{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Modal that should be processed'
        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×
      .modal-body
        %form{action: components_modal_path(result: 200), method: 'POST', data:{remote: 'true', submit: 'modal'}}
          =button_tag 'form submit', type: 'submit', class: 'btn btn-primary'
          =link_to 'data submit', components_modal_path(result: 200), class: 'btn btn-primary', data:{remote: 'true', submit: 'modal', method: 'POST'}
        %form.my-2{action: components_modal_path(result: 500), method: 'POST', data:{remote: 'true', submit: 'modal'}}
          =button_tag 'form error', type: 'submit', class: 'btn btn-primary'
      .modal-footer
        =button_tag 'close', type: 'button', class: 'btn btn-secondary', 'data-dismiss': "modal"

RTL

BS4 theme modal fully supports RTL, not extra work needed!
Change the page to RTL and check it out.

.modal-left and .modal-right
Keep in mind that .modal-left will pop from the left on LTR page but on RTL page it will pop from the right. The same logic goes for .modal-right

Z INDEX

BS4 header has z-index:2, while body, footer and data grid will have z-index: 1. This will result in popovers in header will be on top of body, header and data grid, popovers in body will be inside body, and popovers in footer will be above body but not above header.

Super select, Super date field etc...
Components like super select and date field will attach their dropdowns to the modal dom element and not will not be inside the modal content so they will appear on top of header, body, footer, and data grid
-ofs = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
.row
  .col-24.col-md-auto
    =button_tag 'modal', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#z-index-modal", type: 'button'
  .col-24.col-md-auto.mt-2.mt-md-0
    =button_tag 'modal with data grid', class: 'btn btn-light', 'data-toggle': "modal", 'data-target': "#z-index-data-grid-modal", type: 'button'

.modal#z-index-modal{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Z INDEX modal'
        =button_tag "popover", type: 'button', class: 'btn btn-light ml-1', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
        .form-group.ml-1.mb-0=select_tag 'header_select_zindex', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'SuperSelect'}, class: 'form-control'

        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×

      .modal-body
        .d-flex
          =button_tag "popover", type: 'button', class: 'btn btn-light ml-1', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
          .form-group.ml-1.mb-0=select_tag 'body-top_select_zindex', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'Please select'}, class: 'form-control'

      .modal-footer
        =button_tag "popover", type: 'button', class: 'btn btn-light', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
        .form-group.mb-0=select_tag 'header_select_footer', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'SuperSelect'}, class: 'form-control'
        .form-group.mb-0=select_tag 'header_select_footer_up', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'SuperSelect up', 'dropdown-position': 'above'}, class: 'form-control'

        =button_tag 'close', type: 'button', class: 'btn btn-secondary ml-auto', 'data-dismiss': "modal"


.modal#z-index-data-grid-modal{tabindex: '-1'}
  .modal-dialog
    .modal-content
      .modal-header
        %h5.modal-title='Z INDEX modal'
        =button_tag "popover", type: 'button', class: 'btn btn-light ml-1', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
        .form-group.ml-1.mb-0=select_tag 'dg_header_select_zindex', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'SuperSelect'}, class: 'form-control'

        =button_tag class: 'close', 'data-dismiss': 'modal', type: 'button' do
          %span ×

      .modal-body.p-0
        .border-bottom.d-flex.p-3
          =button_tag "popover", type: 'button', class: 'btn btn-light ml-1', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
          .form-group.ml-1.mb-0=select_tag 'dg_body-top_select_zindex', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'Please select'}, class: 'form-control'

        =render 'components/data_grid/data_grid/variants/data_grid',
                       data_grid_id: 'data-grid-list-in-modal',
                       data_grid_content: 'list',
                       data_grid_class: ''


        .border-bottom.d-flex.p-3
          =button_tag "popover", type: 'button', class: 'btn btn-light ml-1', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
          .form-group.ml-1.mb-0=select_tag 'dg_body_bottom_select_zindex', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'Please select'}, class: 'form-control'


      .modal-footer
        =button_tag "popover", type: 'button', class: 'btn btn-light', 'data-toggle': "popover", 'data-trigger': "click", title: "Popover title", 'data-content': "And here's some amazing content. It's very engaging. Right?"
        .form-group.mb-0=select_tag 'dg_header_select_footer', options_for_select(ofs), include_blank: true, data: {toggle: 'super-select', placeholder: 'SuperSelect up', 'dropdown-position': 'above'}, class: 'form-control'

        =button_tag 'close', type: 'button', class: 'btn btn-secondary ml-auto', 'data-dismiss': "modal"

API

The following is the public API for handling modals from js. For simple operations like show, and event binding the underlying bs4 API can be used. To create via js, convient hide helpers, or accesing with relation to the modal stack see below.

=button_tag  'js Modal', class: 'btn btn-light', data: {action: 'js-modal'}, type: 'button'
=button_tag  'Hide top modal in 5 seconds', class: 'btn btn-light', data: {action: 'top-modal'}, type: 'button'
:javascript
  $('[data-action="js-modal"]').on('click', (e)=> {
    const html = '#{escape_javascript render(template: "components/remote_modal/show.fbox", locals: {params: {'stack' => 0}})}';
    $.modalManager.openModal(html);
  });

  $('[data-action="top-modal"]').on('click', (e)=> {
    setTimeout(() => $.modalManager.topModal.hide(), 5000);
  });

$.modalManager

openModal - Opening a new modal from js

  • html - Required, content of the modal, as in remoteModals everything will be nested under a .modal-dialog div
  • id - Optional, will be set as the modals html id attribute, if non is passed a random uuid will be assigned.

topModal - Accesing the current modal

topModal is a getter for the modal that is on the top-most stack. If no modal is available the NullObject pattern is used (like in jQuery) - a SuperModal object with no backing element is returned so methods can be called on it with neither effects nor errors.

SuperModal - TODO

modalDialog

replaceContent

hide

tickAndHide

On this page