diff --git a/packages/react/src/components/ComboBox/ComboBox.js b/packages/react/src/components/ComboBox/ComboBox.js index 9beab9534e89..f95f91e7b1bc 100644 --- a/packages/react/src/components/ComboBox/ComboBox.js +++ b/packages/react/src/components/ComboBox/ComboBox.js @@ -334,6 +334,11 @@ const ComboBox = React.forwardRef((props, ref) => { {...inputProps} {...rest} ref={mergeRefs(textInput, ref)} + aria-describedby={ + helperText && !invalid && !warn + ? comboBoxHelperId + : undefined + } /> {invalid && ( { @@ -178,6 +186,7 @@ export default class DatePickerInput extends Component { placeholder, type, pattern, + ['aria-describedby']: helperText ? datePickerInputHelperId : undefined, }; return ( @@ -245,7 +254,9 @@ export default class DatePickerInput extends Component { ) : null; const helper = helperText ? ( -
{helperText}
+
+ {helperText} +
) : null; let error = null; diff --git a/packages/react/src/components/Dropdown/Dropdown.js b/packages/react/src/components/Dropdown/Dropdown.js index b4c18490afb6..ea621d806615 100644 --- a/packages/react/src/components/Dropdown/Dropdown.js +++ b/packages/react/src/components/Dropdown/Dropdown.js @@ -20,6 +20,9 @@ import mergeRefs from '../../tools/mergeRefs'; import deprecate from '../../prop-types/deprecate'; import { useFeatureFlag } from '../FeatureFlags'; import { usePrefix } from '../../internal/usePrefix'; +import setupGetInstanceId from '../../tools/setupGetInstanceId'; + +const getInstanceId = setupGetInstanceId(); const defaultItemToString = (item) => { if (typeof item === 'string') { @@ -68,6 +71,11 @@ const Dropdown = React.forwardRef(function Dropdown( initialSelectedItem, onSelectedItemChange, }); + const { current: dropdownInstanceId } = useRef(getInstanceId()); + + const helperId = !helperText + ? undefined + : `dropdown-helper-text-${dropdownInstanceId}`; // only set selectedItem if the prop is defined. Setting if it is undefined // will overwrite default selected items from useSelect @@ -129,7 +137,9 @@ const Dropdown = React.forwardRef(function Dropdown( const ItemToElement = itemToElement; const toggleButtonProps = getToggleButtonProps(); const helper = helperText ? ( -
{helperText}
+
+ {helperText} +
) : null; function onSelectedItemChange({ selectedItem }) { @@ -171,6 +181,9 @@ const Dropdown = React.forwardRef(function Dropdown( className={`${prefix}--list-box__field`} disabled={disabled} aria-disabled={disabled} + aria-describedby={ + !inline && !invalid && !warn && helper ? helperId : undefined + } title={selectedItem ? itemToString(selectedItem) : label} {...toggleButtonProps} ref={mergeRefs(toggleButtonProps.ref, ref)}> diff --git a/packages/react/src/components/MultiSelect/MultiSelect.js b/packages/react/src/components/MultiSelect/MultiSelect.js index 8e860a492521..b365d3344f1b 100644 --- a/packages/react/src/components/MultiSelect/MultiSelect.js +++ b/packages/react/src/components/MultiSelect/MultiSelect.js @@ -287,6 +287,9 @@ const MultiSelect = React.forwardRef(function MultiSelect( className={`${prefix}--list-box__field`} disabled={disabled} aria-disabled={disabled} + aria-describedby={ + !inline && !invalid && !warn && helperText ? helperId : undefined + } {...toggleButtonProps} ref={mergeRefs(toggleButtonProps.ref, ref)} onKeyDown={onKeyDown}> diff --git a/packages/react/src/components/NumberInput/NumberInput.js b/packages/react/src/components/NumberInput/NumberInput.js index caeb46a6316c..ffaa9f9988ba 100644 --- a/packages/react/src/components/NumberInput/NumberInput.js +++ b/packages/react/src/components/NumberInput/NumberInput.js @@ -435,7 +435,9 @@ class NumberInput extends Component { }); const helper = helperText ? ( -
{helperText}
+
+ {helperText} +
) : null; const labelClasses = classNames(`${prefix}--label`, { @@ -475,6 +477,9 @@ class NumberInput extends Component { if (normalizedProps.warn) { ariaDescribedBy = normalizedProps.warnId; } + if (!normalizedProps.validation) { + ariaDescribedBy = helperText ? normalizedProps.helperId : undefined; + } return (
{helperText}
+
+ {helperText} +
) : null; const ariaProps = {}; if (invalid) { ariaProps['aria-describedby'] = errorId; + } else if (!inline) { + ariaProps['aria-describedby'] = helper ? helperId : undefined; } const input = (() => { return ( diff --git a/packages/react/src/components/TextArea/TextArea.js b/packages/react/src/components/TextArea/TextArea.js index d13e2654ec17..a9b00a8f7fdd 100644 --- a/packages/react/src/components/TextArea/TextArea.js +++ b/packages/react/src/components/TextArea/TextArea.js @@ -6,11 +6,14 @@ */ import PropTypes from 'prop-types'; -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import classNames from 'classnames'; import { WarningFilled16 } from '@carbon/icons-react'; import { useFeatureFlag } from '../FeatureFlags'; import { usePrefix } from '../../internal/usePrefix'; +import setupGetInstanceId from '../../tools/setupGetInstanceId'; + +const getInstanceId = setupGetInstanceId(); const TextArea = React.forwardRef(function TextArea( { @@ -38,6 +41,8 @@ const TextArea = React.forwardRef(function TextArea( defaultValue?.length || value?.length || 0 ); + const { current: textAreaInstanceId } = useRef(getInstanceId()); + const textareaProps = { id, onChange: (evt) => { @@ -82,8 +87,14 @@ const TextArea = React.forwardRef(function TextArea( [`${prefix}--form__helper-text--disabled`]: other.disabled, }); + const helperId = !helperText + ? undefined + : `text-area-helper-text-${textAreaInstanceId}`; + const helper = helperText ? ( -
{helperText}
+
+ {helperText} +
) : null; const errorId = id + '-error-msg'; @@ -103,6 +114,14 @@ const TextArea = React.forwardRef(function TextArea( } ); + let ariaDescribedBy; + + if (invalid) { + ariaDescribedBy = errorId; + } else if (!invalid && helperText) { + ariaDescribedBy = helperId; + } + const input = (