Skip to content

Commit c091b4d

Browse files
authored
fix(radio): correct display of read-only state (#3350)
1 parent 5d2cc5d commit c091b4d

File tree

8 files changed

+62
-41
lines changed

8 files changed

+62
-41
lines changed

.changeset/hot-timers-count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@spectrum-css/radio": patch
3+
---
4+
5+
Corrects the styles of the read-only state to show the radio inputs and allow visible focus. Also adds `aria-disabled` since `aria-readonly` isn't well supported, and story demonstrates scripting to make selection for read-only radios immutable.

components/fieldgroup/stories/fieldgroup.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,17 @@ An invalid group of radio buttons or checkboxes is signified by negative help te
9797

9898
<Canvas of={FieldGroupStories.HorizontalSideLabelCheckbox} />
9999

100-
### Read-only checkboxes
100+
### Read-only Checkbox
101101

102-
<Description of={FieldGroupStories.ReadOnly} />
102+
<Description of={FieldGroupStories.ReadOnlyCheckbox} />
103103

104-
<Canvas of={FieldGroupStories.ReadOnly} />
104+
<Canvas of={FieldGroupStories.ReadOnlyCheckbox} />
105+
106+
### Read-only Radio
107+
108+
<Description of={FieldGroupStories.ReadOnlyRadio} />
109+
110+
<Canvas of={FieldGroupStories.ReadOnlyRadio} />
105111

106112
## Properties
107113

components/fieldgroup/stories/fieldgroup.stories.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,14 @@ export default {
7575
helpText: "Select an option.",
7676
items: [
7777
{
78-
id: "apple",
7978
label: "Apples are best",
8079
customClasses: ["spectrum-FieldGroup-item"],
8180
},
8281
{
83-
id: "banana",
8482
label: "Bananas forever",
8583
customClasses: ["spectrum-FieldGroup-item"],
8684
},
8785
{
88-
id: "pear",
8986
label: "Pears or bust",
9087
customClasses: ["spectrum-FieldGroup-item"],
9188
}
@@ -126,7 +123,7 @@ export const VerticalRadio = Template.bind({});
126123
VerticalRadio.tags = ["!dev"];
127124
VerticalRadio.args = {
128125
layout: "vertical",
129-
inputType: "radio",
126+
inputType: "radio"
130127
};
131128
VerticalRadio.parameters = {
132129
chromatic: { disableSnapshot: true },
@@ -146,7 +143,7 @@ export const HorizontalRadio = Template.bind({});
146143
HorizontalRadio.tags = ["!dev"];
147144
HorizontalRadio.args = {
148145
layout: "horizontal",
149-
inputType: "radio",
146+
inputType: "radio"
150147
};
151148
HorizontalRadio.parameters = {
152149
chromatic: { disableSnapshot: true },
@@ -167,7 +164,7 @@ InvalidRadio.tags = ["!dev"];
167164
InvalidRadio.args = {
168165
layout: "horizontal",
169166
inputType: "radio",
170-
isInvalid: true,
167+
isInvalid: true
171168
};
172169
InvalidRadio.parameters = {
173170
chromatic: { disableSnapshot: true },
@@ -231,7 +228,7 @@ VerticalSideLabelRadio.tags = ["!dev"];
231228
VerticalSideLabelRadio.args = {
232229
labelPosition: "side",
233230
inputType: "radio",
234-
layout: "vertical",
231+
layout: "vertical"
235232
};
236233
VerticalSideLabelRadio.parameters = {
237234
chromatic: { disableSnapshot: true },
@@ -242,7 +239,7 @@ HorizontalSideLabelRadio.tags = ["!dev"];
242239
HorizontalSideLabelRadio.args = {
243240
labelPosition: "side",
244241
inputType: "radio",
245-
layout: "horizontal",
242+
layout: "horizontal"
246243
};
247244
HorizontalSideLabelRadio.parameters = {
248245
chromatic: { disableSnapshot: true },
@@ -275,13 +272,28 @@ HorizontalSideLabelCheckbox.parameters = {
275272
* - Read-only checkboxes are immutable, i.e. their checked state cannot be changed.
276273
* - Unlike disabled checkbox groups, the normally focusable elements of a checkbox group should remain focusable.
277274
*/
278-
export const ReadOnly = Template.bind({});
279-
ReadOnly.tags = ["!dev"];
280-
ReadOnly.args = {
275+
export const ReadOnlyCheckbox = Template.bind({});
276+
ReadOnlyCheckbox.tags = ["!dev"];
277+
ReadOnlyCheckbox.args = {
281278
isReadOnly: true,
282279
inputType: "checkbox",
283280
helpText: undefined,
284281
};
285-
ReadOnly.parameters = {
282+
ReadOnlyCheckbox.parameters = {
283+
chromatic: { disableSnapshot: true },
284+
};
285+
286+
/**
287+
* A group of read-only radio buttons.
288+
*
289+
* Review the individual story for more features of [read-only radio buttons](?path=/docs/components-radio--docs#read-only).
290+
*/
291+
export const ReadOnlyRadio = Template.bind({});
292+
ReadOnlyRadio.tags = ["!dev"];
293+
ReadOnlyRadio.args = {
294+
isReadOnly: true,
295+
inputType: "radio"
296+
};
297+
ReadOnlyRadio.parameters = {
286298
chromatic: { disableSnapshot: true },
287299
};

components/fieldgroup/stories/template.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const Template = (
1616
customClasses = [],
1717
layout = "vertical",
1818
inputType = "radio",
19+
name = getRandomId(),
1920
isReadOnly = false,
2021
isRequired = false,
2122
label,
@@ -61,13 +62,14 @@ export const Template = (
6162
})}
6263
>
6364
${inputType === "radio" ?
64-
items.map((item) =>
65+
items.map((item, i) =>
6566
Radio({
6667
...item,
6768
isReadOnly,
6869
isRequired,
69-
name: "field-group-example",
70+
name: `field-group-example-${name}`,
7071
customClasses: [`${rootClass}-item`],
72+
...(isReadOnly ? {isChecked: i === 1} : {}),
7173
}, context))
7274
: items.map((item, i) =>
7375
CheckBox({

components/radio/index.css

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,24 +239,14 @@
239239
}
240240

241241
&.is-readOnly {
242-
.spectrum-Radio-input:read-only {
243-
cursor: initial;
244-
}
245-
246-
/* hide selection indicator */
247-
& .spectrum-Radio-button {
248-
position: fixed;
249-
inset-inline-end: 100%;
250-
inset-block-end: 100%;
251-
clip: rect(1px, 1px, 1px, 1px);
252-
clip-path: inset(50%);
242+
.spectrum-Radio-input {
243+
pointer-events: none;
253244
}
254245

255246
.spectrum-Radio-label,
256247
/* ensure disabled readonly has normal text color */
257248
& .spectrum-Radio-input:disabled ~ .spectrum-Radio-label,
258249
& .spectrum-Radio-input:checked:disabled ~ .spectrum-Radio-label {
259-
margin-inline-start: 0;
260250
color: var(--highcontrast-radio-neutral-content-color, var(--mod-radio-neutral-content-color, var(--spectrum-radio-neutral-content-color)));
261251
}
262252
}

components/radio/metadata/metadata.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@
2727
".spectrum-Radio-label:lang(ja)",
2828
".spectrum-Radio-label:lang(ko)",
2929
".spectrum-Radio-label:lang(zh)",
30-
".spectrum-Radio.is-readOnly .spectrum-Radio-button",
30+
".spectrum-Radio.is-readOnly .spectrum-Radio-input",
3131
".spectrum-Radio.is-readOnly .spectrum-Radio-input:checked:disabled ~ .spectrum-Radio-label",
3232
".spectrum-Radio.is-readOnly .spectrum-Radio-input:disabled ~ .spectrum-Radio-label",
33-
".spectrum-Radio.is-readOnly .spectrum-Radio-input:read-only",
3433
".spectrum-Radio.is-readOnly .spectrum-Radio-label",
3534
".spectrum-Radio:active .spectrum-Radio-button:before",
3635
".spectrum-Radio:active .spectrum-Radio-input:checked + .spectrum-Radio-button:before",

components/radio/stories/radio.stories.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,14 @@ Disabled.parameters = {
130130
};
131131

132132
/**
133-
* A radio group has a read-only option for when it's in the disabled state but still needs to be shown.
134-
* This allows for content to be copied, but not interacted with or changed.
133+
* A radio group has a read-only option for when it's functionally disabled but still needs to be shown.
134+
* This allows for label content to be copied, but prevents the input from being interacted with or changed.
135135
*
136-
* - Read-only radio buttons are disabled, but not all disabled radio buttons are read-only.
137-
* - Read-only radio buttons do not have a focus ring, but the button should be focusable.
136+
* Read-only radio buttons:
137+
* - prevent interaction like disabled, but not all disabled radio buttons are read-only
138+
* - are immutable, i.e. their checked state cannot be changed
139+
* - are keyboard focusable and communicate state to assistive technology
140+
* - use `aria-disabled` since the `readonly` attribute [is not valid](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly#overview) and `aria-readonly` is not currently announced by the majority of screen readers.
138141
*/
139142
export const ReadOnly = BasicGroupTemplate.bind({});
140143
ReadOnly.storyName = "Read-only";

components/radio/stories/template.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export const Template = ({
3838
...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}),
3939
})}
4040
style=${styleMap(customStyles)}
41-
id=${ifDefined(id)}
4241
>
4342
<input
4443
type="radio"
@@ -47,9 +46,16 @@ export const Template = ({
4746
id=${inputId}
4847
?checked=${isChecked}
4948
?disabled=${isDisabled}
50-
@change=${function() {
51-
if (isDisabled) return;
52-
updateArgs({ isChecked: !isChecked });
49+
aria-disabled=${ifDefined(isReadOnly ? "true" : undefined)}
50+
@change=${(e) => {
51+
if (isDisabled || isReadOnly) return;
52+
updateArgs?.({ isChecked: e.target.checked });
53+
}}
54+
@click=${(e) => {
55+
if (!isReadOnly) return;
56+
57+
// Make checked value immutable for read-only.
58+
e.preventDefault();
5359
}}
5460
/>
5561
<span class="${rootClass}-button ${rootClass}-button--sizeS"></span>
@@ -74,7 +80,6 @@ export const BasicGroupTemplate = (args, context) => Container({
7480
${Template({
7581
...args,
7682
label: "Example label",
77-
id: "radio-1-" + (args?.id ?? "default"),
7883
name: "radio-example-" + (args?.name ?? "default"),
7984
}, context)}
8085
${Template({
@@ -84,7 +89,6 @@ export const BasicGroupTemplate = (args, context) => Container({
8489
customStyles: {
8590
"max-width": "220px",
8691
},
87-
id: "radio-2-" + (args?.id ?? "default"),
8892
name: "radio-example-" + (args?.name ?? "default"),
8993
}, context)}
9094
`,

0 commit comments

Comments
 (0)