Skip to content

Commit 28f1d6f

Browse files
committed
Docs for tooltip / popover accessibility; rstudio/bslib#825
1 parent 52a7da2 commit 28f1d6f

File tree

2 files changed

+128
-1
lines changed

2 files changed

+128
-1
lines changed

shiny/ui/_popover.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,56 @@ def popover(
5959
In addition to clicking the `close_button`, popovers can be closed by pressing the
6060
Esc/Space key when the popover (and/or its trigger) is focused.
6161
62+
Accessibility of Popover Triggers
63+
---------------------------------
64+
65+
Because the user needs to interact with the `trigger` element to see the `popover`,
66+
it's best practice to use an element that is typically accessible via keyboard
67+
interactions, like a button or a link.
68+
69+
If you use a non-interactive element, like a `<span>` or text, `popover()` will
70+
automatically add the `tabindex="0"` attribute to the trigger element to make sure
71+
that users can reach the element with the keyboard. This means that in most cases
72+
you can use any element you want as the trigger.
73+
74+
One place where it's important to consider the accessibility of the trigger is when
75+
using an icon without any accompanying text. In these cases, many icon elements are
76+
created with the assumption that the icon is decorative, which will make it
77+
inaccessible to users of assistive technologies.
78+
79+
When using an icon as the primary trigger, ensure that the icon does not have
80+
`aria-hidden="true"` or `role="presentation"` attributes. Icon packages typically
81+
provide a way to specify a title for the icon, as well as a way to specify that the
82+
icon is not decorative. The title should be a short description of the purpose of
83+
the trigger, rather than a description of the icon itself.
84+
85+
For example:
86+
87+
```python
88+
icon_title = "Settings"
89+
def bs_gear_icon(title: str):
90+
# Enhanced from https://rstudio.github.io/bsicons/ via `bsicons::bs_icon("gear", title = icon_title)`
91+
return ui.HTML(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-gear " style="height:1em;width:1em;fill:currentColor;" aria-hidden="true" role="img" ><title>{title}</title><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"></path><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"></path></svg>')
92+
93+
ui.popover(
94+
bs_gear_icon(icon_title),
95+
title = icon_title,
96+
ui.input_slider("n", "Number of points", 1, 100, 50)
97+
)
98+
```
99+
100+
```python
101+
icon_title = "Settings"
102+
def fa_gear_icon(title: str):
103+
# Enhanced from https://rstudio.github.io/fontawesome/ via `fontawesome::fa("gear", a11y = "sem", title = icon_title)`
104+
return ui.HTML(f'<svg aria-label="{title}" role="img" viewBox="0 0 512 512" style="height:1em;width:1em;vertical-align:-0.125em;margin-left:auto;margin-right:auto;font-size:inherit;fill:currentColor;overflow:visible;position:relative;"><title>{title}</title><path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>')
105+
ui.popover(
106+
fa_gear_icon(icon_title),
107+
title = icon_title,
108+
ui.input_slider("n", "Number of points", 1, 100, 50)
109+
)
110+
```
111+
62112
See Also
63113
--------
64114
* <https://getbootstrap.com/docs/5.2/components/popovers/>
@@ -67,6 +117,9 @@ def popover(
67117
* :func:`~shiny.ui.tooltip`
68118
"""
69119

120+
# * If you're using [bsicons::bs_icon()], provide a `title`.
121+
# * If you're using [fontawesome::fa()], set `a11y = "sem"` and provide a `title`.
122+
70123
# Theming/Styling
71124
# ---------------
72125
#
@@ -79,7 +132,7 @@ def popover(
79132
# ```
80133
# popover(
81134
# "Trigger", "Popover message",
82-
# options = list(customClass = "my-pop")
135+
# options = {"customClass": "my-pop"}
83136
# )
84137
# ```
85138
#

shiny/ui/_tooltip.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,85 @@ def tooltip(
5353
those elements, wrap the object in a :func:`~shiny.ui.tags.div` or
5454
:func:`~shiny.ui.tags.span`.
5555
56+
Accessibility of Tooltip Triggers
57+
---------------------------------
58+
59+
Because the user needs to interact with the `trigger` element to see the `tooltip`,
60+
it's best practice to use an element that is typically accessible via keyboard
61+
interactions, like a button or a link.
62+
63+
If you use a non-interactive element, like a `<span>` or text, `tooltip()` will
64+
automatically add the `tabindex="0"` attribute to the trigger element to make sure
65+
that users can reach the element with the keyboard. This means that in most cases
66+
you can use any element you want as the trigger.
67+
68+
One place where it's important to consider the accessibility of the trigger is when
69+
using an icon without any accompanying text. In these cases, many icon elements are
70+
created with the assumption that the icon is decorative, which will make it
71+
inaccessible to users of assistive technologies.
72+
73+
When using an icon as the primary trigger, ensure that the icon does not have
74+
`aria-hidden="true"` or `role="presentation"` attributes. Icon packages typically
75+
provide a way to specify a title for the icon, as well as a way to specify that the
76+
icon is not decorative. The title should be a short description of the purpose of
77+
the trigger, rather than a description of the icon itself.
78+
79+
For example:
80+
81+
```python
82+
icon_title = "About tooltips"
83+
def bs_info_icon(title: str):
84+
# Enhanced from https://rstudio.github.io/bsicons/ via `bsicons::bs_icon("info-circle", title = icon_title)`
85+
return ui.HTML(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-info-circle " style="height:1em;width:1em;fill:currentColor;" aria-hidden="true" role="img" ><title>{title}</title><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path><path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path></svg>')
86+
87+
ui.tooltip(
88+
bs_info_icon(icon_title),
89+
"Text shown in the tooltip."
90+
)
91+
```
92+
93+
```python
94+
icon_title = "About tooltips"
95+
def fa_info_circle(title: str):
96+
# Enhanced from https://rstudio.github.io/fontawesome/ via `fontawesome::fa("info-circle", a11y = "sem", title = icon_title)`
97+
return ui.HTML(f'<svg aria-hidden="true" role="img" viewBox="0 0 512 512" style="height:1em;width:1em;vertical-align:-0.125em;margin-left:auto;margin-right:auto;font-size:inherit;fill:currentColor;overflow:visible;position:relative;"><title>{title}</title><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>')
98+
ui.tooltip(
99+
fa_info_circle(icon_title),
100+
"Text shown in the tooltip."
101+
)
102+
```
103+
56104
See Also
57105
--------
58106
59107
* [Bootstrap tooltips documentation](https://getbootstrap.com/docs/5.2/components/tooltips/)
60108
"""
109+
110+
# * If you're using [bsicons::bs_icon()], provide a `title`.
111+
# * If you're using [fontawesome::fa()], set `a11y = "sem"` and provide a `title`.
112+
113+
# Theming/Styling
114+
# ---------------
115+
#
116+
# Like other bslib components, tooltips can be themed by supplying [relevant theming
117+
# variables](https://rstudio.github.io/bslib/articles/bs5-variables.html#tooltip-bg)
118+
# to [bs_theme()], which effects styling of every tooltip on the page. To style a
119+
# _specific_ tooltip differently from other tooltips, utilize the `customClass`
120+
# option:
121+
#
122+
# ```
123+
# tooltip(
124+
# "Trigger", "Tooltip message",
125+
# options = {"customClass": "my-pop"}
126+
# )
127+
# ```
128+
#
129+
# And then add relevant rules to [bs_theme()] via [bs_add_rules()]:
130+
#
131+
# ```
132+
# bs_theme() |> bs_add_rules(".my-pop { max-width: none; }")
133+
# ```
134+
61135
attrs, children = consolidate_attrs(*args, **kwargs)
62136

63137
if len(children) == 0:

0 commit comments

Comments
 (0)