From 3317fe314cdb60b1e0a2f4aeeba5b4aee5e542a8 Mon Sep 17 00:00:00 2001 From: Alex Bajcz <15620636+BajczA475@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:36:13 -0600 Subject: [PATCH 1/2] Update modal.R Currently, the modal title is hard-coded to be an h4 element. This makes sense as a default, but ideally, it would be toggleable to respect a user's heading hierarchy for a11y. This creates an input that allows toggling to whatever level is desired. --- R/modal.R | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/R/modal.R b/R/modal.R index 9866a94766..ea0c260af7 100644 --- a/R/modal.R +++ b/R/modal.R @@ -41,6 +41,8 @@ removeModal <- function(session = getDefaultReactiveDomain()) { #' @inheritParams actionButton #' @param ... UI elements for the body of the modal dialog box. #' @param title An optional title for the dialog. +#` @param title_level An optional header level (between 1 and 6) for the title. +#' Defaults to 4 (h4 element). #' @param footer UI for footer. Use `NULL` for no footer. #' @param size One of `"s"` for small, `"m"` (the default) for medium, #' `"l"` for large, or `"xl"` for extra large. Note that `"xl"` only @@ -153,9 +155,21 @@ removeModal <- function(session = getDefaultReactiveDomain()) { #' ) #' } #' @export -modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"), +modalDialog <- function(..., title = NULL, title_level = 4, footer = modalButton("Dismiss"), size = c("m", "s", "l", "xl"), easyClose = FALSE, fade = TRUE) { +if (!is.numeric(title_level) || length(title_level) != 1 || is.na(title_level)) { + stop("`title_level` must be a single numeric value between 1 and 6.") +} + +if (title_level %% 1 != 0) { + stop("`title_level` must be an integer between 1 and 6.") +} + +if (title_level < 1 || title_level > 6) { + stop("`title_level` must be a value 1 through 6.") +} + size <- match.arg(size) backdrop <- if (!easyClose) "static" @@ -175,7 +189,8 @@ modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"), class = switch(size, s = "modal-sm", m = NULL, l = "modal-lg", xl = "modal-xl"), div(class = "modal-content", if (!is.null(title)) div(class = "modal-header", - tags$h4(class = "modal-title", title) + tag_fun = htmltools::tags[[paste0("h", title_level)]] #NOW, GRAB HEADER LEVEL ACCORDING TO USER SPEC + tag_fun(class = "modal-title", title) ), div(class = "modal-body", ...), if (!is.null(footer)) div(class = "modal-footer", footer) From d22526a46d884ea8ad700ad5acccd028daf0b859 Mon Sep 17 00:00:00 2001 From: Alex Bajcz <15620636+BajczA475@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:05:22 -0600 Subject: [PATCH 2/2] Update modal.R to not hardcode h4 This ensures that the modal title is not hard-coded to be an h4. Instead, it will be a div with an appropriate id that can be used to set an aria-labelledby attribute on the modal container parent. It also only applies the attribute when the title is specified, as a title is optional. This leaves users to specify the title as whatever kind of element they want (or plain text). --- R/modal.R | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/R/modal.R b/R/modal.R index ea0c260af7..1ac98002c1 100644 --- a/R/modal.R +++ b/R/modal.R @@ -41,8 +41,6 @@ removeModal <- function(session = getDefaultReactiveDomain()) { #' @inheritParams actionButton #' @param ... UI elements for the body of the modal dialog box. #' @param title An optional title for the dialog. -#` @param title_level An optional header level (between 1 and 6) for the title. -#' Defaults to 4 (h4 element). #' @param footer UI for footer. Use `NULL` for no footer. #' @param size One of `"s"` for small, `"m"` (the default) for medium, #' `"l"` for large, or `"xl"` for extra large. Note that `"xl"` only @@ -155,23 +153,13 @@ removeModal <- function(session = getDefaultReactiveDomain()) { #' ) #' } #' @export -modalDialog <- function(..., title = NULL, title_level = 4, footer = modalButton("Dismiss"), +modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"), size = c("m", "s", "l", "xl"), easyClose = FALSE, fade = TRUE) { -if (!is.numeric(title_level) || length(title_level) != 1 || is.na(title_level)) { - stop("`title_level` must be a single numeric value between 1 and 6.") -} - -if (title_level %% 1 != 0) { - stop("`title_level` must be an integer between 1 and 6.") -} - -if (title_level < 1 || title_level > 6) { - stop("`title_level` must be a value 1 through 6.") -} - size <- match.arg(size) + has_title <- !is.null(title) + backdrop <- if (!easyClose) "static" keyboard <- if (!easyClose) "false" div( @@ -183,14 +171,16 @@ if (title_level < 1 || title_level > 6) { `data-bs-backdrop` = backdrop, `data-keyboard` = keyboard, `data-bs-keyboard` = keyboard, + if (has_title) `aria-labelledby` = "shiny-modal-title", div( class = "modal-dialog", class = switch(size, s = "modal-sm", m = NULL, l = "modal-lg", xl = "modal-xl"), div(class = "modal-content", if (!is.null(title)) div(class = "modal-header", - tag_fun = htmltools::tags[[paste0("h", title_level)]] #NOW, GRAB HEADER LEVEL ACCORDING TO USER SPEC - tag_fun(class = "modal-title", title) + div(class = "modal-title", + id = "shiny-modal-title", + title) ), div(class = "modal-body", ...), if (!is.null(footer)) div(class = "modal-footer", footer)