Table of contents
September 26, 2026 ยท View on GitHub
shinytip
๐ฌ Simple flexible tooltips for Shiny apps
Demo
ยท
by Dean Attali
{shinytip} lets you add tooltips to any Shiny input, output, tag, or even plain text. Just wrap any UI with tip() and you're done -- no setup needed, no JavaScript involved.
Tooltips can be customized in a variety of ways, such as position, width, colours, font size, etc. They also work in all the places where other tooltips sometimes break, such as dynamic UI, modals, and Rmd/qmd document. Shiny inputs can even emit a different tooltip based on whether the input is enabled or disabled. See the demo Shiny app online to play around with all the options.
Need Shiny help? I'm available for consulting.
If you find {shinytip} useful, please consider supporting my work! โค
This package is part of a larger ecosystem of packages with a shared vision: solving common Shiny issues and improving Shiny apps with minimal effort, minimal code changes, and clear documentation. Other packages for your Shiny apps:
| Package | Description | Demo |
|---|---|---|
| shinyjs | ๐ก Easily improve the user experience of your Shiny apps in seconds | ๐ |
| shinyalert | ๐ฏ๏ธ Easily create pretty popup messages (modals) in Shiny | ๐ |
| shinyscreenshot | ๐ท Capture screenshots of entire pages or parts of pages in Shiny apps | ๐ |
| timevis | ๐ Create interactive timeline visualizations in R | ๐ |
| shinycssloaders | โ Add loading animations to a Shiny output while it's recalculating | ๐ |
| colourpicker | ๐จ A colour picker tool for Shiny and for selecting colours in plots | ๐ |
| shinybrowser | ๐ Find out information about a user's web browser in Shiny apps | ๐ |
| shinydisconnect | ๐ Show a nice message when a Shiny app disconnects or errors | ๐ |
| shinymixpanel | ๐ Track user interactions with Mixpanel in Shiny apps or R scripts | WIP |
| shinyforms | ๐ Easily create questionnaire-type forms with Shiny | WIP |
Table of contents
- How to use
- Sponsors ๐
- Overview of functions
- Installation
- Customizing the tooltip
- Tooltip theme
- Setting defaults for all tooltips
- Tooltips on disabled inputs
- Updating a tooltip
- Where {shinytip} works
- Limitations
- Similar packages
How to use
Wrap any Shiny tag, input, output, or plain text with tip() and give it the tooltip text.
library(shiny)
library(shinytip)
ui <- fluidPage(
h2("shinytip"),
tip(actionButton("btn", "Hover me"), "Hello! ๐")
)
shinyApp(ui, function(input, output) {})
That's it!
Sometimes you don't want the entire UI element to trigger the tooltip, so there are two more functions:
tip_icon()creates a question-mark icon that carries the tooltip.tip_input()attaches that icon to the end of a Shiny input's label.
library(shiny)
library(shinytip)
ui <- fluidPage(
h2("shinytip"),
# tooltip on a button
tip(actionButton("btn", "Hover me"), "Hello!"),
# tooltip on plain text
tip("some important text", "Here's why it's important", position = "right"),
# a question-mark icon beside a heading
h3("Results", tip_icon("Explanation")),
# a question-mark icon at the end of an input's label
tip_input(textInput("name", "Name"), "Your full name")
)
shinyApp(ui, function(input, output) {})
You can also use R's |> operator for cleaner syntax:
actionButton("btn", "Hover me") |> tip("Hello! ๐")
The tooltip text can include emojis, and you can use \n to force a line break, but it cannot contain HTML.
tip()doesn't replace the element it's given, it only adds a few attributes to it, so adding a tooltip won't affect your app's layout. The exceptions are images, icons, and plain text, which get wrapped in a<div>/<span>because they need a container that can hold the tooltip.
Sponsors ๐
There are no sponsors yet
Become the first sponsor for {shinytip}!
Overview of functions
| Function | Description |
|---|---|
tip() | Add a tooltip to any Shiny tag, input, output, or plain text. |
tip_icon() | Create a question-mark icon that shows a tooltip. |
tip_input() | Add a question-mark icon with a tooltip to the end of an input's label. |
tip_theme() | Bundle a set of appearance options (colours, sizes, etc.) so that they can be defined once and reused. |
tip_update() | Change the text of a tooltip from the server. |
Check out the demo app to see all of these in action and to generate your own tooltips.
Installation
For most users: To install the stable CRAN version:
install.packages("shinytip")
For advanced users: To install the latest development version from GitHub:
install.packages("remotes")
remotes::install_github("daattali/shinytip")
Customizing the tooltip
Each tooltip can also be customized with the following parameters:
-
position: Where the tooltip appears in relation to the element. One of"top"(default),"bottom","left","right","top-left","top-right","bottom-left","bottom-right". -
width: How wide the tooltip should be."line"(default) keeps the entire tooltip on a single line,"fit"makes it the same width as the element, and"s"/"m"/"l"/"xl"are fixed widths. -
theme: Atip_theme()object that controls the tooltip's appearance (see the next section). -
solid(not accepted bytip()): Usesolid = TRUEto get a question-mark icon with a solid background. -
wrap_tag(only accepted bytip()): Usewrap_tag = TRUEto wrap the element in a<div>. This can fix a variety of issues, most often a disabled input (see Limitations). It's opt-in because the extra<div>can affect the UI layout.
Tooltip theme
Everything that affects how a tooltip looks lives in a tip_theme() object: bg (background colour), fg (text colour), fontsize, radius (how rounded the corners are), animate (whether the tooltip fades and slides in and out), move (how far it slides while animating), and pointer (whether the cursor changes on hover).
Since it's a standalone object, you can define a theme once and use it in as many tooltips as you want:
warning_theme <- tip_theme(bg = "#B00020", fg = "white", fontsize = 14)
tip(actionButton("delete", "Delete"), "This cannot be undone!", theme = warning_theme)
tip("Danger zone", "Be careful here", theme = warning_theme)
Setting defaults for all tooltips
If you want all the tooltips in your app to look the same, there's no need to pass the same arguments over and over. Any parameter can be set globally with an R option named after the parameter, prefixed by shinytip.:
options(shinytip.position = "right", shinytip.bg = "#0275D8", shinytip.fontsize = 14)
Every parameter can be set this way, except for tag, content, and content_disabled.
Tooltips on disabled inputs
A question that comes up a lot in Shiny is how to tell the user why a button is disabled. That's what the content_disabled parameter is for: it's the text to show while the input is disabled.
library(shiny)
ui <- fluidPage(
shinyjs::useShinyjs(),
checkboxInput("terms", "I accept the terms"),
tip(
actionButton("submit", "Submit"),
"Submit the form",
content_disabled = "You must accept the terms",
position = "right"
)
)
server <- function(input, output, session) {
observe({
shinyjs::toggleState("submit", condition = input$terms)
})
}
shinyApp(ui, server)
If you only provide content_disabled, the tooltip is shown only while the input is disabled. If you provide both content and content_disabled, the tooltip swaps its text depending on the input's state. Inputs that are disabled with shinyjs::disable() are detected automatically.
Updating a tooltip
To change a tooltip's text while the app is running, give it a tip_id and use tip_update() in the server.
library(shiny)
ui <- fluidPage(
actionButton("btn", "Click me") |>
tip("You haven't clicked yet", tip_id = "btn_tip", position = "right")
)
server <- function(input, output, session) {
observeEvent(input$btn, {
tip_update("btn_tip", paste("You clicked", input$btn, "times"))
})
}
shinyApp(ui, server)
Both content and content_disabled can be changed, added, or removed: leave an argument out to keep it, or use NA to remove it. For example, tip_update("btn_tip", content_disabled = "Not allowed right now") starts showing that text while the input is disabled, and tip_update("btn_tip", content_disabled = NA) goes back to a regular tooltip.
Where {shinytip} works
Tooltips have a habit of working nicely in a simple example and then breaking in a real app. {shinytip} was tested in every place I could think of, and it works:
- On inputs, outputs, tags, images, icons, and plain text
- In UI that's generated dynamically with
renderUI()orinsertUI() - Inside Shiny modules
- Inside modals, both
modalDialog()andshinyalert::shinyalert() - Inside Rmd and qmd documents
- Alongside other packages such as {shinycssloaders}
- With emojis in the tooltip text
Limitations
-
The best position for a tooltip cannot be detected automatically, so a tooltip near the edge of the page can run off-screen and you'll need to choose a different
positionyourself. -
The tooltip text cannot contain HTML.
-
On mobile (and other touch devices), tooltips are shown on click rather than hover.
-
Tooltips are drawn using CSS pseudo-elements, so if you add a tooltip to an element that already makes use of pseudo-elements, the two will conflict and you may get unexpected results. This can be fixed by using
wrap_tag = TRUE. -
If an element becomes partially transparent when disabled (such as buttons), then the tooltip will also have the same transparency when the element is disabled. This can be fixed by using
wrap_tag = TRUE. -
Bootstrap 5 (used by
{bslib}) prevents disabled buttons from having tooltips on hover. This can be fixed by usingwrap_tag = TRUE.
Similar packages
There are a few other packages that can add tooltips to Shiny apps. I wrote {shinytip} because none of them worked in all the situations I needed, and none of them supported all the features and customizations I wanted. If {shinytip} doesn't do what you're after, these are worth a look:
-
{bslib}: Has a powerful and flexible
tooltip()function, which uses JavaScript rather than a lightweight pure-CSS solution. {bslib} is a heavy dependency if your app isn't already using it, and tooltips only work if your Shiny app is using Bootstrap 5. It does not support tooltips on disabled elements or easily styling tooltips. However, being a JavaScript solution allows it features that {shinytip} doesn't have. This is an excellent choice for Bootstrap 5 apps that already use {bslib}. -
{tippy}: A JavaScript-based tooltip package. It has a different set of options and methods, some of which {shinytip} doesn't have, so it's worth checking out if {shinytip} is missing something you need. I couldn't get it to work in several of the scenarios I tried, and its documentation has carried a warning about the API being unstable for several years.
-
{spsComps}: Another JavaScript-powered package that contains tooltips. Tooltips on disabled inputs are not supported, and an entire JavaScript tag gets inserted into the UI for every tooltip, which makes this too heavy for my liking. Nonetheless, these tooltips support many features and can be a great option.
-
{prompter}: Requires calling
use_prompt()in the UI for setup, and doesn't work on plain text. It is an alternative CSS-only framework worth checking out if you want lightweight. -
{bsplus}: Another Shiny-addon package that supports tooltips using JavaScript. It requires setup via calling
use_bs_tooltip()in the UI and doesn't work on plain text or on disabled inputs.
Credits
{shinytip} is powered by the balloon.css project by Claudio Holanda.