Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
374f757
Initial toolbar container
elnelson575 Nov 3, 2025
ed1a07e
Updates
elnelson575 Nov 3, 2025
04bd68b
Minor updates
elnelson575 Nov 3, 2025
8f6d6f1
Added tests
elnelson575 Nov 3, 2025
6389307
Updated news
elnelson575 Nov 3, 2025
c7d70e6
Removing example:
elnelson575 Nov 3, 2025
3f8b630
Adding snaps
elnelson575 Nov 5, 2025
1abed04
Adding a few comments
elnelson575 Nov 5, 2025
5fd9254
Update to toolbar testing
elnelson575 Nov 5, 2025
c2289a3
Fix dots in toolbar function
elnelson575 Nov 5, 2025
b23fc17
updates to dots
elnelson575 Nov 5, 2025
d2eb240
Merge branch 'feat/toolbar-container' of https://github.com/rstudio/b…
elnelson575 Nov 5, 2025
0836374
Update R/toolbar.R
elnelson575 Nov 5, 2025
a55d03f
Update inst/components/scss/toolbar.scss
elnelson575 Nov 5, 2025
0afabf4
Update inst/components/scss/toolbar.scss
elnelson575 Nov 5, 2025
e6e9d88
Update inst/components/scss/toolbar.scss
elnelson575 Nov 5, 2025
626af55
Adding input buttons
elnelson575 Nov 12, 2025
180319a
Added disabled option
elnelson575 Nov 12, 2025
478f5e1
Merge remote-tracking branch 'origin/main' into feat/toolbar-container
elnelson575 Nov 13, 2025
0340d21
Merge remote-tracking branch 'origin/main' into feat/toolbar-container
elnelson575 Nov 13, 2025
f898670
Updated code
elnelson575 Nov 14, 2025
899b644
minor formatting changes
elnelson575 Nov 14, 2025
6a3ccfe
Adding footer back
elnelson575 Nov 14, 2025
88f00d8
Docs changes
elnelson575 Nov 14, 2025
df309bf
Updating footer, updating tests
elnelson575 Nov 14, 2025
d69e3e8
Merge branch 'feat/toolbar-container' into feature/add-buttons
elnelson575 Nov 14, 2025
ef468d1
Updating button spacing
elnelson575 Nov 14, 2025
78372c1
Reformat spacing
elnelson575 Nov 14, 2025
652a061
Merging in toolbar/epic
elnelson575 Nov 18, 2025
0c0f895
Fixed button sizing, changing to also incorperate label+icon
elnelson575 Nov 19, 2025
3ce0b20
Updated card header and font sizing
elnelson575 Nov 19, 2025
cf585de
Updated footer, updated gap
elnelson575 Nov 20, 2025
a6250a6
Updating tests
elnelson575 Nov 20, 2025
8f3ab2d
Updating tests
elnelson575 Nov 20, 2025
fa866ba
Update headings in bs-theme-preset.md
elnelson575 Nov 20, 2025
06177d6
Update header format in bs-theme-preset-builtin.md
elnelson575 Nov 20, 2025
b49322b
Add back deleted file
elnelson575 Nov 20, 2025
c5f3dd6
Merge branch 'feature/add-buttons' of https://github.com/rstudio/bsli…
elnelson575 Nov 20, 2025
cd2a746
removing comment
elnelson575 Nov 20, 2025
cc601fb
Fix tests
elnelson575 Nov 20, 2025
25c6027
Update card.scss
elnelson575 Nov 20, 2025
e6fac9a
Fixing diff
elnelson575 Nov 20, 2025
c5d9e38
Slim down icon button formatting
elnelson575 Nov 20, 2025
37b9855
Clean up scss
elnelson575 Nov 20, 2025
16acb76
Fixing diff
elnelson575 Nov 20, 2025
5ed4321
Removing accidental legacy addition
elnelson575 Nov 20, 2025
85bad5e
Merge branch 'feature/add-buttons' of https://github.com/rstudio/bsli…
elnelson575 Nov 20, 2025
5531572
Apply suggestions from code review
elnelson575 Nov 21, 2025
b8d66d5
Updating with edits based on comments
elnelson575 Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ Imports:
memoise (>= 2.0.1),
mime,
rlang,
sass (>= 0.4.9)
sass (>= 0.4.9),
shiny (>= 1.11.1.9000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, shiny should stay under Suggests. You can just move this line back down to be between rmarkdown and testthat.

Suggests:
bsicons,
curl,
Expand All @@ -52,7 +53,6 @@ Suggests:
magrittr,
rappdirs,
rmarkdown (>= 2.7),
shiny (>= 1.11.1),
testthat,
thematic,
tools,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export(toggle_sidebar)
export(toggle_switch)
export(toggle_tooltip)
export(toolbar)
export(toolbar_input_button)
export(tooltip)
export(update_popover)
export(update_submit_textarea)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Added toast notifications based on [Bootstrap's Toast component](https://getbootstrap.com/docs/5.3/components/toasts/): Use `toast()` to create customizable toast objects, `show_toast()` to display a toast message, `hide_toast()` for manual dismissal, and `toast_header()` for structured headers with icons and status indicators. (#1246)

* Added a new `toolbar()` component for creating Bootstrap toolbars that can contain buttons, text, and other elements. (#1247)
* Added `toolbar_input_button()` for easily creating buttons to include in a `toolbar()`. (#1248)

## Improvements and bug fixes

Expand Down
91 changes: 90 additions & 1 deletion R/toolbar.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,114 @@
#' A toolbar which can contain buttons, inputs, and other UI elements in a small
#' form suitable for inclusion in card headers, footers, and other small places.
#'
#' @examplesIf rlang::is_interactive()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be @examples. We use this pattern when you wouldn't want to run the example unless you're in an interactive session -- like when you create a full Shiny app in the example. Showing a little snippet of HTML is generally safe (IIRC)

#' toolbar(
#' align = "right",
#' toolbar_input_button(id = "see", icon = icon("eye")),
#' toolbar_input_button(id = "save", icon = icon("save")),
#' toolbar_input_button(id = "edit", icon = icon("pencil"))
Comment on lines +10 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this example reminded me that I the current API doesn't do a great job of guiding people to fall into the pit of accessibility success.

It might be better to force people to provide either label or tooltip and then have a separate option like icon_only = TRUE. Then, if label is provided, we'd add an appropriate ARIA attribute. We should also evaluate the accessibility implications of using a tooltip to communicate meaning, maybe we'll end up always requiring label so we can set up the right ARIA attributes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this would also impact our determination of btn_type in toolbar_input_button()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So right now based on the Mac native voiceover functionality, here's what happens:
If label: reads label
If icon: reads the aria-label which is "icon_name icon" (ex. "calendar icon")
If tooltip: Reads the tooltip, then whatever case of the above applies.

Based on this, I actually think our implementation of tooltip might be more accessible than the non-tooltipped version.

Two potential options I'm thinking of:

  1. Require label always, set a show_label option to True or False which controls visibility. Label gets set as the aria attribute no matter what.
  2. Require a tooltip or a label, that gets set as the aria attribute.

Thoughts?

#' )
#'
#' @param ... UI elements for the toolbar.
#' @param align Determines if toolbar should be aligned to the `"right"` or
#' `"left"`.
#' @param gap A CSS length unit defining the gap (i.e., spacing) between
#' elements in the toolbar. Defaults to `0` (no gap).
#' @return Returns a toolbar element.
#'
#' @family Toolbar components
#' @export
toolbar <- function(
...,
align = c("right", "left")
align = c("right", "left"),
gap = NULL
) {
align <- rlang::arg_match(align)
gap <- validateCssUnit(gap)

tag <- div(
class = "bslib-toolbar bslib-gap-spacing",
"data-align" = align,
style = css(gap = gap),
...,
component_dependencies()
)

tag_require(tag, version = 5, caller = "toolbar()")
as_fragment(tag)
}

#' Add toolbar button input
#'
#' @description
#' A button designed to fit well in small places such as in a [toolbar()].
#'
#' @examplesIf rlang::is_interactive()
#' toolbar(
#' align = "right",
#' toolbar_input_button(id = "see", icon = icon("eye")),
#' toolbar_input_button(id = "save", label = "Save")),
#' toolbar_input_button(id = "edit", icon = icon("pencil"), label="Edit")
#' )
#'
#' @param id The input ID.
#' @param icon An icon to display in the button. (One of icon or label must be
#' supplied.)
#' @param label The label to display in the button. (One of icon or label must
#' be supplied.)
#' @param tooltip An optional [tooltip()] to display when hovering over the
#' button.
#' @param disabled If `TRUE`, the button will not be clickable. Use
#' [shiny::updateActionButton()] to dynamically enable/disable the button.
#' @param border Whether to show a border around the button.
#' @param ... UI elements for the button.
#'
#' @return Returns a button suitable for use in a toolbar.
#'
#' @family Toolbar components
#' @export

toolbar_input_button <- function(
id,
icon = NULL,
label = NULL,
tooltip = NULL,
...,
disabled = FALSE,
border = FALSE
) {
if (is.null(icon) && is.null(label)) {
stop(
"At least one of 'icon' or 'label' must be provided.",
call. = TRUE
)
}
has_icon <- !is.null(icon)
has_label <- !is.null(label)

btn_type <-
if (has_icon && !has_label) {
"icon"
} else if (has_label && !has_icon) {
"label"
} else {
# Can't both be missing (checked above)
"both"
}

button <- shiny::actionButton(
id,
label = label,
icon = icon,
disabled = disabled,
class = "bslib-toolbar-input-button btn-sm",
class = if (!border) "border-0" else "border-1",
"data-type" = btn_type,
...
)

if (!is.null(tooltip)) {
button <- tooltip(button, tooltip, placement = "bottom")
}
button
}
2 changes: 2 additions & 0 deletions inst/components/scss/card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
flex-direction: row;
align-items: center;
align-self: stretch;
min-height: 2.5rem;
padding-block: 4px;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely the right final value, but I think we should see if we can hook this into a Bootstrap CSS variable

gap: 0.25rem;

// Give the nav flex: 1 so that if the card header contains a nav, it will take all the available space
Expand Down
37 changes: 29 additions & 8 deletions inst/components/scss/toolbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,56 @@
.bslib-toolbar {
display: flex;
align-items: center;
gap: 0;

/* ---- Toolbar options ---- */

&[data-align="left"] {
margin-right: auto;
justify-content: start;
}

&[data-align="right"] {
margin-left: auto;
justify-content: end;
}

/* ---- Toolbar Input Buttons ---- */

// Keep labels and icons centered in the button
.bslib-toolbar-input-button {
align-items: center;
justify-content: center;
line-height: 1; // Override Bootstrap's line-height to avoid too much vertical space
}

// Square icon-only buttons
.bslib-toolbar-input-button[data-type="icon"] {
height: var(--_icon-size, 2rem);
aspect-ratio: 1;
line-height: 1 !important; // Ensure no line-height interference

// Ensure icon is centered
>.action-icon {
display: flex;
align-items: center;
justify-content: center;
line-height: 1; // Remove excess line-height
margin: 0; // Remove any default margins
}
}

/* ---- Adjustments to other elements ---- */

// Ensures uniformity of font sizing in elements and sub-elements (e.g., input select)
&,
& * {
font-size: 0.8rem;
font-size: 0.9rem;
}

&>* {
margin-bottom: 0 !important;
width: auto;
align-self: center;
}

// Card header is flex by default, but card footer is not and must be in order for
// toolbar alignment to work
.card-footer:has(> &) {
display: flex;
align-items: center;
}
}
5 changes: 4 additions & 1 deletion man/toolbar.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions man/toolbar_input_button.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 98 additions & 0 deletions tests/testthat/_snaps/toolbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
Item 2
</div>

---

Code
show_raw_html(toolbar("Item 1", "Item 2", gap = "10px"))
Output
<div class="bslib-toolbar bslib-gap-spacing" data-align="right" style="gap:10px;">
Item 1
Item 2
</div>

# toolbar() aligns correctly

Code
Expand All @@ -28,3 +38,91 @@
Item 2
</div>

# toolbar_input_button() has correct attributes

Code
show_raw_html(toolbar_input_button(id = "label_only", label = "Click me"))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="label" id="label_only" type="button">
<span class="action-label">Click me</span>
</button>

---

Code
show_raw_html(toolbar_input_button(id = "icon_only", icon = shiny::icon("star")))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="icon" id="icon_only" type="button">
<span class="action-icon">
<i class="far fa-star" role="presentation" aria-label="star icon"></i>
</span>
</button>

---

Code
show_raw_html(toolbar_input_button(id = "both", label = "Save", icon = shiny::icon(
"save")))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="both" id="both" type="button">
<span class="action-icon">
<i class="far fa-floppy-disk" role="presentation" aria-label="floppy-disk icon"></i>
</span>
<span class="action-label">Save</span>
</button>

# toolbar_input_button() disabled parameter

Code
show_raw_html(toolbar_input_button(id = "disabled_btn", label = "Disabled",
disabled = TRUE))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="label" disabled id="disabled_btn" type="button">
<span class="action-label">Disabled</span>
</button>

---

Code
show_raw_html(toolbar_input_button(id = "enabled_btn", label = "Enabled",
disabled = FALSE))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="label" id="enabled_btn" type="button">
<span class="action-label">Enabled</span>
</button>

# toolbar_input_button() border parameter

Code
show_raw_html(toolbar_input_button(id = "no_border", label = "No Border",
border = FALSE))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="label" id="no_border" type="button">
<span class="action-label">No Border</span>
</button>

---

Code
show_raw_html(toolbar_input_button(id = "with_border", label = "With Border",
border = TRUE))
Output
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-1" data-type="label" id="with_border" type="button">
<span class="action-label">With Border</span>
</button>

# toolbar_input_button() tooltip parameter

Code
show_raw_html(toolbar_input_button(id = "tooltip_icon", icon = shiny::icon(
"question"), tooltip = "Help"))
Output
<bslib-tooltip placement="bottom" bsOptions="[]" data-require-bs-version="5" data-require-bs-caller="tooltip()">
<template>Help</template>
<button class="btn btn-default action-button bslib-toolbar-input-button btn-sm border-0" data-type="icon" id="tooltip_icon" type="button">
<span class="action-icon">
<i class="fas fa-question" role="presentation" aria-label="question icon"></i>
</span>
</button>
</bslib-tooltip>

Loading