Skip to content

Commit ff12a43

Browse files
authored
FIX accessibility: make copy-to-clipboard icons "keyboard-operable" in HTML displays (scikit-learn#34429)
1 parent 7583343 commit ff12a43

7 files changed

Lines changed: 41 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- The copy-to-clipboard icons in the HTML representation of estimators are
2+
now rendered as ``<button>`` elements with an ``aria-label`` and a visible
3+
focus outline, instead of non-interactive ``<i>`` elements, so they are
4+
reachable and usable with a keyboard and screen readers.
5+
By :user:`Dea María Léon <deamarialeon>`.

sklearn/utils/_repr_html/features.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ def _features_html(features, is_fitted_css_class=""):
2222
<summary>
2323
<div class="arrow"></div>
2424
<div>{total_features_line}</div>
25-
<div class="image-container" title="Copy output features (max 100)">
26-
<i class="copy-paste-icon"
25+
<div class="image-container">
26+
<button type="button" class="copy-paste-icon"
27+
title="{copy_features_label}"
28+
aria-label="{copy_features_label}"
2729
onclick="
2830
event.stopPropagation();
2931
event.preventDefault();
3032
copyFeatureNamesToClipboard(this);
3133
"
3234
>
33-
</i>
35+
</button>
3436
</div>
3537
</summary>
3638
<div class="features-container">
@@ -50,6 +52,7 @@ def _features_html(features, is_fitted_css_class=""):
5052
</tr>
5153
5254
"""
55+
copy_features_label = f"Copy output features (max {_MAX_DISPLAY_FEATURES})"
5356
total_features = len(features)
5457
display_features = features[:_MAX_DISPLAY_FEATURES]
5558

@@ -68,5 +71,6 @@ def _features_html(features, is_fitted_css_class=""):
6871
return FEATURES_TABLE_TEMPLATE.format(
6972
total_features_line=total_features_line,
7073
is_fitted_css_class=html.escape(is_fitted_css_class),
74+
copy_features_label=copy_features_label,
7175
rows="".join(rows),
7276
)

sklearn/utils/_repr_html/params.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,12 @@ a.param-doc-link::before {
149149
width: 14px;
150150
height: 14px;
151151
cursor: pointer;
152+
border: none;
153+
padding: 0;
154+
background-color: transparent;
155+
}
156+
157+
.copy-paste-icon:focus-visible {
158+
outline: 1px solid currentColor;
159+
outline-offset: 1px;
152160
}

sklearn/utils/_repr_html/params.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ def _params_html_repr(params):
5050

5151
PARAM_ROW_TEMPLATE = """
5252
<tr class="{param_type}">
53-
<td><i class="copy-paste-icon"
53+
<td><button type="button" class="copy-paste-icon"
54+
aria-label="Copy {param_name} to clipboard"
5455
onclick="copyToClipboard('{param_name}',
5556
this.parentElement.nextElementSibling)"
56-
></i></td>
57+
></button></td>
5758
<td class="param">{param_display}</td>
5859
<td class="value">{param_value}</td>
5960
</tr>

sklearn/utils/_repr_html/tests/test_features.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def test_features_html_structure():
230230
assert '<table class="features-table">' in html
231231
assert "<tbody>" in html
232232
assert "</tbody>" in html
233-
assert '<i class="copy-paste-icon"' in html
233+
assert '<button type="button" class="copy-paste-icon"' in html
234234
assert "copyFeatureNamesToClipboard" in html
235235

236236

sklearn/utils/_repr_html/tests/test_js.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,11 @@ def test_force_theme(page, local_server, color, expected_theme):
141141
<div class="features">
142142
<details>
143143
<summary>
144-
<div class="image-container"
145-
title="Copy all output features">
146-
<i class="copy-paste-icon"></i>
144+
<div class="image-container">
145+
<button type="button" class="copy-paste-icon"
146+
title="Copy output features (max 100)"
147+
aria-label="Copy output features (max 100)">
148+
</button>
147149
</div>
148150
</summary>
149151
<div class="features-container">

sklearn/utils/_repr_html/tests/test_params.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ def test_params_html_repr():
7777
assert "estimator-table" in _params_html_repr(params)
7878

7979

80+
def test_params_html_repr_copy_button():
81+
"""Copy control renders as an accessible <button> with an aria-label."""
82+
params = ParamsDict(params={"alpha": 1}, estimator_class="")
83+
html_output = _params_html_repr(params)
84+
85+
copy_button = (
86+
r'<button type="button" class="copy-paste-icon"'
87+
r'\s*aria-label="Copy alpha to clipboard"'
88+
)
89+
assert re.search(copy_button, html_output, flags=re.DOTALL)
90+
91+
8092
def test_params_html_repr_with_doc_links():
8193
"""Test `_params_html_repr` with valid and invalid doc links."""
8294

0 commit comments

Comments
 (0)