Introduction to micromodal

Getting started

The goal of micromodal is to create simple and elegant modal dialogs which are WAI-ARIA guidelines compliant with minimal configuration.

Things to keep in mind:

  1. The modal’s trigger (usually a button or a link) must have the attribute data-micromodal-trigger with a value of the modal’s id. So if the modal has the id “modal-1”, then you will have:
`data-micromodal-trigger` = "modal-1"
  1. If you need to add something to dismiss the modal (eg. button or link), give it the attribute data-micromodal-close. In {shiny} this is the equivalent of:
`data-micromodal-close` = NA

Examples

See live demo here.

Simple usage

Though a bit long, this example clearly depicts all there is to know about using {micromodal}.

library(shiny)
library(micromodal)

server <- \(input, output, session) {
  
}

ui <- bslib::page(
  title = "Micromodal",
  theme = bslib::bs_theme(version = 5),
  # inform shiny to use {micromodal}:
  use_micromodal(),
  # your normal UI code:
  tags$div(
    class = "container",
    tags$div(
      class = "container my-5",
      tags$h1("Micromodal.js in Shiny", class = "mb-5"),
      # trigger for "modal-1"
      actionButton(
        inputId = "show_modal_1",
        label = "Exhibit 1",
        class = "btn-outline-primary px-3",
        `data-micromodal-trigger` = "modal-1"
      ),
      # trigger for "modal-2"
      actionButton(
        inputId = "show_modal_2",
        label = "Exhibit 2",
        class = "btn-outline-primary px-3",
        `data-micromodal-trigger` = "modal-2"
      )
    ),
    # modal-1:
    micromodal(
      id = "modal-1",
      title = "Login",
      content = tagList(
        textInput(
          inputId = "name",
          label = "Name",
          width = "400px"
        ),
        passwordInput(
          inputId = "password",
          label = tags$div(
            tags$span("Password"),
            tags$span("(required)", class = "text-muted fw-light")
          ),
          width = "400px"
        ) |> tagAppendAttributes(class = "mb-0"),
        tags$div(
          "Must be atleast 6 characters long.",
          class = "text-muted fw-light"
        )
      ),
      footer = tagList(
        tags$button(
          class = "modal__btn modal__btn-primary",
          "Continue"
        ),
        tags$a(
          href = "#",
          class = "ms-3",
          `data-micromodal-close` = NA,
          `aria-label` = "Close this dialog window",
          "Cancel"
        )
      )
    ),
    # modal-2:
    micromodal(
      id = "modal-2",
      title = "Micromodal",
      content = tagList(
        tags$p("This is a completely accessible modal."),
        tags$p(
          "Try hitting the",
          tags$code("tab"),
          "key* and notice how the focus stays",
          "within the modal itself. To close modal hit the",
          tags$code("esc"),
          "button, click on the overlay or just click the close button."
        ),
        tags$p("*", tags$code("alt + tab"), "in safari")
      ),
      footer = tagList(
        tags$button(
          class = "modal__btn modal__btn-primary",
          "Continue"
        ),
        tags$button(
          class = "modal__btn ms-2",
          `data-micromodal-close` = NA,
          `aria-label` = "Close this dialog window",
          "Close"
        )
      )
    )
  )
)

shinyApp(ui, server)

With uiOutput + renderUI

If need be, you can render the modal and it’s trigger dynamically.

I. Render modal

library(shiny)
library(micromodal)

ui <- fluidPage(
  use_micromodal(),
  selectInput(
    inputId = "selector",
    label = "Pick a letter",
    choices = letters
  ),
  actionButton(
    inputId = "trigger",
    label = "Trigger modal",
    `data-micromodal-trigger` = "modal-1"
  ),
  uiOutput(outputId = "mymodal")
)

server <- \(input, output, session) {
  output$mymodal <- renderUI({
    tagList(
      micromodal(
        id = "modal-1",
        title = "Rendered serverside",
        content = tagList(
          tags$p(
            "You selected the letter '",
            input$selector,
            "'"
          )
        )
      )
    )
  })
}

shinyApp(ui, server)

II. Render modal + trigger

library(shiny)
library(micromodal)

ui <- fluidPage(
  use_micromodal(),
  selectInput(
    inputId = "selector",
    label = "Pick a letter",
    choices = letters
  ),
  uiOutput(outputId = "mymodal")
)

server <- \(input, output, session) {
  output$mymodal <- renderUI({
    tagList(
      actionButton(
        inputId = "trigger",
        label = "Trigger modal",
        `data-micromodal-trigger` = "modal-1"
      ),
      micromodal(
        id = "modal-1",
        title = "Rendered serverside",
        content = tagList(
          tags$p(
            "You selected the letter '",
            input$selector,
            "'"
          )
        )
      )
    )
  })
}

shinyApp(ui, server)

NB: micromodal might not work with other *Output + render* eg. plotOutput()

Inside modules

As far as usage of {micromodal} in modules is concerned, I only need to remind you of one thing:

It’s always a missing call to NS()

Thank me later.

library(shiny)
library(micromodal)

# module UI:
sayhi_ui <- \(id) {
  ns <- NS(id)
  tagList(
    tags$h1("Hello"),
    actionLink(
      inputId = ns("sayhi"),
      label = "Say Hi",
      `data-micromodal-trigger` = ns("greetings_modal")
    ),
    micromodal(
      id = ns("greetings_modal"),
      title = "Greetings folks!",
      content = tags$p(
        "I'd like to thank you for joining us here today."
      )
    )
  )
}

# module server is not necessary in our case, just putting it here 
# for formality:
sayhi_server <- \(id) {
  moduleServer(
    id = id,
    module = \(input, output, session) {
      
    }
  )
}

ui <- bslib::page(
  title = "In Modules",
  theme = bslib::bs_theme(version = 5),
  use_micromodal(),
  tags$div(
    class = "container",
    sayhi_ui("hi")
  )
)

server <- \(input, output, session) {}

shinyApp(ui, server)