@instructure/ui-checkbox 11.7.4-snapshot-121 → 11.7.4-snapshot-125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [11.7.4-snapshot-121](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-121) (2026-07-22)
6
+ ## [11.7.4-snapshot-125](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-125) (2026-07-22)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **many:** keep form field messages out of the control's accessible name ([409d077](https://github.com/instructure/instructure-ui/commit/409d077a3095b318fd7d2e150bb01bc2732c9bf5))
12
+ * **many:** only reference rendered messages via aria-describedby ([ccc9516](https://github.com/instructure/instructure-ui/commit/ccc95165ec8dae6d06e001c0a20fcebef1cf9962))
7
13
 
8
14
 
9
15
  ### Features
@@ -68,8 +68,12 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
68
68
  this.state.checked = !!props.defaultChecked;
69
69
  }
70
70
  this._defaultId = props.deterministicId();
71
+ this._messagesId = props.deterministicId('Checkbox-messages');
72
+ this._labelId = props.deterministicId('Checkbox-label');
71
73
  }
72
74
  _defaultId;
75
+ _messagesId;
76
+ _labelId;
73
77
  _input = null;
74
78
  ref = null;
75
79
  handleRef = el => {
@@ -151,6 +155,9 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
151
155
  get isError() {
152
156
  return !!this.props.messages?.find(m => m.type === 'error' || m.type === 'newError');
153
157
  }
158
+ get hasMessages() {
159
+ return !!this.props.messages && this.props.messages.length > 0;
160
+ }
154
161
  get invalid() {
155
162
  return !!this.props.messages?.find(m => m.type === 'newError' || m.type === 'error');
156
163
  }
@@ -175,8 +182,18 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
175
182
  focused
176
183
  } = this.state;
177
184
  error(!(variant === 'simple' && labelPlacement !== 'end'), `[Checkbox] The \`simple\` variant does not support the \`labelPlacement\` property. Use the \`toggle\` variant instead.`);
185
+
186
+ // Give the label its own id so messages stay out of the accessible name.
187
+ const labelContent = _jsxs("span", {
188
+ id: this._labelId,
189
+ children: [label, isRequired && label && _jsxs("span", {
190
+ css: this.invalid ? styles?.requiredInvalid : {},
191
+ "aria-hidden": true,
192
+ children: [' ', "*"]
193
+ })]
194
+ });
178
195
  if (variant === 'toggle') {
179
- return _jsxs(ToggleFacade, {
196
+ return _jsx(ToggleFacade, {
180
197
  disabled: disabled,
181
198
  size: size,
182
199
  hovered: hovered,
@@ -189,14 +206,10 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
189
206
  ,
190
207
  themeOverride: themeOverride,
191
208
  invalid: this.invalid,
192
- children: [label, isRequired && label && _jsxs("span", {
193
- css: this.invalid ? styles?.requiredInvalid : {},
194
- "aria-hidden": true,
195
- children: [' ', "*"]
196
- })]
209
+ children: labelContent
197
210
  });
198
211
  } else {
199
- return _jsxs(CheckboxFacade, {
212
+ return _jsx(CheckboxFacade, {
200
213
  disabled: disabled,
201
214
  size: size,
202
215
  hovered: hovered,
@@ -206,11 +219,7 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
206
219
  indeterminate: indeterminate,
207
220
  themeOverride: themeOverride,
208
221
  invalid: this.invalid,
209
- children: [label, isRequired && label && _jsxs("span", {
210
- css: this.invalid ? styles?.requiredInvalid : {},
211
- "aria-hidden": true,
212
- children: [' ', "*"]
213
- })]
222
+ children: labelContent
214
223
  });
215
224
  }
216
225
  }
@@ -225,6 +234,7 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
225
234
  margin: "small 0 0",
226
235
  css: this.isError && (variant === 'toggle' ? styles?.indentedToggleError : styles?.indentedError),
227
236
  children: _jsx(FormFieldMessages, {
237
+ id: this._messagesId,
228
238
  messages: messages
229
239
  })
230
240
  }) : null;
@@ -268,7 +278,11 @@ let Checkbox = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
268
278
  readOnly: readOnly,
269
279
  "aria-readonly": readOnly ? true : undefined,
270
280
  "aria-checked": indeterminate ? 'mixed' : undefined,
271
- "aria-invalid": this.invalid ? 'true' : undefined,
281
+ "aria-invalid": this.invalid ? 'true' : undefined
282
+ // Keep messages in the description so the accessible name contains only the label.
283
+ ,
284
+ "aria-labelledby": this.hasMessages ? props['aria-labelledby'] || this._labelId : props['aria-labelledby'],
285
+ "aria-describedby": [props['aria-describedby'], this.hasMessages ? this._messagesId : null].filter(Boolean).join(' ') || undefined,
272
286
  css: styles?.input,
273
287
  onClickCapture: e => {
274
288
  if (readOnly) {
@@ -88,8 +88,12 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
88
88
  this.state.checked = !!props.defaultChecked;
89
89
  }
90
90
  this._defaultId = props.deterministicId();
91
+ this._messagesId = props.deterministicId('Checkbox-messages');
92
+ this._labelId = props.deterministicId('Checkbox-label');
91
93
  }
92
94
  _defaultId;
95
+ _messagesId;
96
+ _labelId;
93
97
  _input = null;
94
98
  ref = null;
95
99
  handleRef = el => {
@@ -171,6 +175,9 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
171
175
  get isError() {
172
176
  return !!this.props.messages?.find(m => m.type === 'error' || m.type === 'newError');
173
177
  }
178
+ get hasMessages() {
179
+ return !!this.props.messages && this.props.messages.length > 0;
180
+ }
174
181
  get invalid() {
175
182
  return !!this.props.messages?.find(m => m.type === 'newError' || m.type === 'error');
176
183
  }
@@ -195,8 +202,18 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
195
202
  focused
196
203
  } = this.state;
197
204
  (0, _console.logError)(!(variant === 'simple' && labelPlacement !== 'end'), `[Checkbox] The \`simple\` variant does not support the \`labelPlacement\` property. Use the \`toggle\` variant instead.`);
205
+
206
+ // Give the label its own id so messages stay out of the accessible name.
207
+ const labelContent = (0, _jsxRuntime.jsxs)("span", {
208
+ id: this._labelId,
209
+ children: [label, isRequired && label && (0, _jsxRuntime.jsxs)("span", {
210
+ css: this.invalid ? styles?.requiredInvalid : {},
211
+ "aria-hidden": true,
212
+ children: [' ', "*"]
213
+ })]
214
+ });
198
215
  if (variant === 'toggle') {
199
- return (0, _jsxRuntime.jsxs)(_index2.ToggleFacade, {
216
+ return (0, _jsxRuntime.jsx)(_index2.ToggleFacade, {
200
217
  disabled: disabled,
201
218
  size: size,
202
219
  hovered: hovered,
@@ -209,14 +226,10 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
209
226
  ,
210
227
  themeOverride: themeOverride,
211
228
  invalid: this.invalid,
212
- children: [label, isRequired && label && (0, _jsxRuntime.jsxs)("span", {
213
- css: this.invalid ? styles?.requiredInvalid : {},
214
- "aria-hidden": true,
215
- children: [' ', "*"]
216
- })]
229
+ children: labelContent
217
230
  });
218
231
  } else {
219
- return (0, _jsxRuntime.jsxs)(_index.CheckboxFacade, {
232
+ return (0, _jsxRuntime.jsx)(_index.CheckboxFacade, {
220
233
  disabled: disabled,
221
234
  size: size,
222
235
  hovered: hovered,
@@ -226,11 +239,7 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
226
239
  indeterminate: indeterminate,
227
240
  themeOverride: themeOverride,
228
241
  invalid: this.invalid,
229
- children: [label, isRequired && label && (0, _jsxRuntime.jsxs)("span", {
230
- css: this.invalid ? styles?.requiredInvalid : {},
231
- "aria-hidden": true,
232
- children: [' ', "*"]
233
- })]
242
+ children: labelContent
234
243
  });
235
244
  }
236
245
  }
@@ -245,6 +254,7 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
245
254
  margin: "small 0 0",
246
255
  css: this.isError && (variant === 'toggle' ? styles?.indentedToggleError : styles?.indentedError),
247
256
  children: (0, _jsxRuntime.jsx)(_latest.FormFieldMessages, {
257
+ id: this._messagesId,
248
258
  messages: messages
249
259
  })
250
260
  }) : null;
@@ -288,7 +298,11 @@ let Checkbox = exports.Checkbox = (_dec = (0, _withDeterministicId.withDetermini
288
298
  readOnly: readOnly,
289
299
  "aria-readonly": readOnly ? true : undefined,
290
300
  "aria-checked": indeterminate ? 'mixed' : undefined,
291
- "aria-invalid": this.invalid ? 'true' : undefined,
301
+ "aria-invalid": this.invalid ? 'true' : undefined
302
+ // Keep messages in the description so the accessible name contains only the label.
303
+ ,
304
+ "aria-labelledby": this.hasMessages ? props['aria-labelledby'] || this._labelId : props['aria-labelledby'],
305
+ "aria-describedby": [props['aria-describedby'], this.hasMessages ? this._messagesId : null].filter(Boolean).join(' ') || undefined,
292
306
  css: styles?.input,
293
307
  onClickCapture: e => {
294
308
  if (readOnly) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-checkbox",
3
- "version": "11.7.4-snapshot-121",
3
+ "version": "11.7.4-snapshot-125",
4
4
  "description": " styled HTML input type='checkbox' component.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -16,27 +16,27 @@
16
16
  "dependencies": {
17
17
  "@babel/runtime": "^7.29.7",
18
18
  "keycode": "^2",
19
- "@instructure/console": "11.7.4-snapshot-121",
20
- "@instructure/emotion": "11.7.4-snapshot-121",
21
- "@instructure/shared-types": "11.7.4-snapshot-121",
22
- "@instructure/ui-form-field": "11.7.4-snapshot-121",
23
- "@instructure/ui-dom-utils": "11.7.4-snapshot-121",
24
- "@instructure/ui-icons": "11.7.4-snapshot-121",
25
- "@instructure/ui-react-utils": "11.7.4-snapshot-121",
26
- "@instructure/ui-themes": "11.7.4-snapshot-121",
27
- "@instructure/ui-utils": "11.7.4-snapshot-121",
28
- "@instructure/ui-view": "11.7.4-snapshot-121",
29
- "@instructure/uid": "11.7.4-snapshot-121",
30
- "@instructure/ui-svg-images": "11.7.4-snapshot-121"
19
+ "@instructure/console": "11.7.4-snapshot-125",
20
+ "@instructure/emotion": "11.7.4-snapshot-125",
21
+ "@instructure/shared-types": "11.7.4-snapshot-125",
22
+ "@instructure/ui-dom-utils": "11.7.4-snapshot-125",
23
+ "@instructure/ui-form-field": "11.7.4-snapshot-125",
24
+ "@instructure/ui-icons": "11.7.4-snapshot-125",
25
+ "@instructure/ui-react-utils": "11.7.4-snapshot-125",
26
+ "@instructure/ui-svg-images": "11.7.4-snapshot-125",
27
+ "@instructure/ui-themes": "11.7.4-snapshot-125",
28
+ "@instructure/ui-view": "11.7.4-snapshot-125",
29
+ "@instructure/ui-utils": "11.7.4-snapshot-125",
30
+ "@instructure/uid": "11.7.4-snapshot-125"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@testing-library/jest-dom": "^6.9.1",
34
34
  "@testing-library/react": "16.3.2",
35
35
  "@testing-library/user-event": "^14.6.1",
36
36
  "vitest": "^4.1.9",
37
- "@instructure/ui-axe-check": "11.7.4-snapshot-121",
38
- "@instructure/ui-color-utils": "11.7.4-snapshot-121",
39
- "@instructure/ui-babel-preset": "11.7.4-snapshot-121"
37
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-125",
38
+ "@instructure/ui-color-utils": "11.7.4-snapshot-125",
39
+ "@instructure/ui-axe-check": "11.7.4-snapshot-125"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": ">=18 <=19"
@@ -84,8 +84,12 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
84
84
  }
85
85
 
86
86
  this._defaultId = props.deterministicId!()
87
+ this._messagesId = props.deterministicId!('Checkbox-messages')
88
+ this._labelId = props.deterministicId!('Checkbox-label')
87
89
  }
88
90
  private readonly _defaultId: string
91
+ private readonly _messagesId: string
92
+ private readonly _labelId: string
89
93
  private _input: HTMLInputElement | null = null
90
94
 
91
95
  ref: Element | null = null
@@ -186,6 +190,10 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
186
190
  )
187
191
  }
188
192
 
193
+ get hasMessages() {
194
+ return !!this.props.messages && this.props.messages.length > 0
195
+ }
196
+
189
197
  get invalid() {
190
198
  return !!this.props.messages?.find(
191
199
  (m) => m.type === 'newError' || m.type === 'error'
@@ -217,6 +225,22 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
217
225
  `[Checkbox] The \`simple\` variant does not support the \`labelPlacement\` property. Use the \`toggle\` variant instead.`
218
226
  )
219
227
 
228
+ // Give the label its own id so messages stay out of the accessible name.
229
+ const labelContent = (
230
+ <span id={this._labelId}>
231
+ {label}
232
+ {isRequired && label && (
233
+ <span
234
+ css={this.invalid ? styles?.requiredInvalid : {}}
235
+ aria-hidden={true}
236
+ >
237
+ {' '}
238
+ *
239
+ </span>
240
+ )}
241
+ </span>
242
+ )
243
+
220
244
  if (variant === 'toggle') {
221
245
  return (
222
246
  <ToggleFacade
@@ -232,16 +256,7 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
232
256
  themeOverride={themeOverride}
233
257
  invalid={this.invalid}
234
258
  >
235
- {label}
236
- {isRequired && label && (
237
- <span
238
- css={this.invalid ? styles?.requiredInvalid : {}}
239
- aria-hidden={true}
240
- >
241
- {' '}
242
- *
243
- </span>
244
- )}
259
+ {labelContent}
245
260
  </ToggleFacade>
246
261
  )
247
262
  } else {
@@ -257,16 +272,7 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
257
272
  themeOverride={themeOverride}
258
273
  invalid={this.invalid}
259
274
  >
260
- {label}
261
- {isRequired && label && (
262
- <span
263
- css={this.invalid ? styles?.requiredInvalid : {}}
264
- aria-hidden={true}
265
- >
266
- {' '}
267
- *
268
- </span>
269
- )}
275
+ {labelContent}
270
276
  </CheckboxFacade>
271
277
  )
272
278
  }
@@ -286,7 +292,7 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
286
292
  : styles?.indentedError)
287
293
  }
288
294
  >
289
- <FormFieldMessages messages={messages} />
295
+ <FormFieldMessages id={this._messagesId} messages={messages} />
290
296
  </View>
291
297
  ) : null
292
298
  }
@@ -338,6 +344,20 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
338
344
  aria-readonly={readOnly ? true : undefined}
339
345
  aria-checked={indeterminate ? 'mixed' : undefined}
340
346
  aria-invalid={this.invalid ? 'true' : undefined}
347
+ // Keep messages in the description so the accessible name contains only the label.
348
+ aria-labelledby={
349
+ this.hasMessages
350
+ ? props['aria-labelledby'] || this._labelId
351
+ : props['aria-labelledby']
352
+ }
353
+ aria-describedby={
354
+ [
355
+ props['aria-describedby'],
356
+ this.hasMessages ? this._messagesId : null
357
+ ]
358
+ .filter(Boolean)
359
+ .join(' ') || undefined
360
+ }
341
361
  css={styles?.input}
342
362
  onClickCapture={(e) => {
343
363
  if (readOnly) {