diff --git a/.changeset/a11y-input-error-message-not-read.md b/.changeset/a11y-input-error-message-not-read.md
new file mode 100644
index 0000000000..d996b451d0
--- /dev/null
+++ b/.changeset/a11y-input-error-message-not-read.md
@@ -0,0 +1,5 @@
+---
+'react-magma-docs': patch
+---
+
+fix(Input): Clarification that developers should use `ref` with `Input`. Add a new example for Storybook.
diff --git a/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap b/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap
index 31bb651f6e..c50f9e4128 100644
--- a/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap
+++ b/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap
@@ -389,6 +389,10 @@ exports[`Alert Variants should render an alert with danger variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
.emotion-0 {
-webkit-align-items: stretch;
-webkit-box-align: stretch;
@@ -492,6 +496,10 @@ exports[`Alert Variants should render an alert with danger variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
-
+
Test Alert Text
@@ -634,6 +644,10 @@ exports[`Alert Variants should render an alert with info variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
.emotion-0 {
-webkit-align-items: stretch;
-webkit-box-align: stretch;
@@ -737,6 +751,10 @@ exports[`Alert Variants should render an alert with info variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
-
+
Test Alert Text
@@ -879,6 +899,10 @@ exports[`Alert Variants should render an alert with warning variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
.emotion-0 {
-webkit-align-items: stretch;
-webkit-box-align: stretch;
@@ -982,6 +1006,10 @@ exports[`Alert Variants should render an alert with warning variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
-
+
Test Alert Text
@@ -1124,6 +1154,10 @@ exports[`Alert should render an alert with default variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
.emotion-0 {
-webkit-align-items: stretch;
-webkit-box-align: stretch;
@@ -1227,6 +1261,10 @@ exports[`Alert should render an alert with default variant 1`] = `
}
}
+.emotion-8 {
+ white-space: pre-line;
+}
+
-
+
Test Alert Text
diff --git a/packages/react-magma-dom/src/components/AlertBase/index.tsx b/packages/react-magma-dom/src/components/AlertBase/index.tsx
index 109febb36d..1380bb3060 100644
--- a/packages/react-magma-dom/src/components/AlertBase/index.tsx
+++ b/packages/react-magma-dom/src/components/AlertBase/index.tsx
@@ -379,6 +379,10 @@ const DismissButton = styled(IconButton, { shouldForwardProp })<{
}
`;
+const AlertSpan = styled.span`
+ white-space: pre-line;
+`
+
function renderIcon(variant = 'info', isToast?: boolean, theme?: any) {
const Icon = VARIANT_ICON[variant];
@@ -479,7 +483,7 @@ export const AlertBase = React.forwardRef
(
isDismissible={isDismissible}
theme={theme}
>
- {children}
+ {children}
{additionalContent && (
{additionalContent}
diff --git a/packages/react-magma-dom/src/components/Form/index.tsx b/packages/react-magma-dom/src/components/Form/index.tsx
index 9544c18a49..014b140ae0 100644
--- a/packages/react-magma-dom/src/components/Form/index.tsx
+++ b/packages/react-magma-dom/src/components/Form/index.tsx
@@ -8,6 +8,7 @@ import { ThemeInterface } from '../../theme/magma';
import { InverseContext, useIsInverse } from '../../inverse';
import styled from '@emotion/styled';
import { TypographyContextVariant, TypographyVisualStyle } from '../Typography';
+import { Announce } from '../Announce';
/**
* @children required
@@ -108,7 +109,9 @@ export const Form = React.forwardRef(
{description && {description}}
{errorMessage && (
- {errorMessage}
+
+ {errorMessage}
+
)}
{children}
{actions}
diff --git a/packages/react-magma-dom/src/components/Input/Input.stories.tsx b/packages/react-magma-dom/src/components/Input/Input.stories.tsx
index 4329385726..71725aff1e 100644
--- a/packages/react-magma-dom/src/components/Input/Input.stories.tsx
+++ b/packages/react-magma-dom/src/components/Input/Input.stories.tsx
@@ -2,13 +2,14 @@ import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
import { HelpIcon, NotificationsIcon, WorkIcon } from 'react-magma-icons';
import { Input, InputProps } from '.';
-import { ButtonSize, ButtonType, ButtonVariant } from '../Button';
+import { Button, ButtonSize, ButtonType, ButtonVariant } from '../Button';
import { Card, CardBody } from '../Card';
import { IconButton } from '../IconButton';
import { InputIconPosition, InputSize, InputType } from '../InputBase';
import { LabelPosition } from '../Label';
import { Tooltip } from '../Tooltip';
import { Spacer } from '../Spacer';
+import { ButtonGroup } from '../ButtonGroup';
const Template: Story = args => (
<>
@@ -211,7 +212,11 @@ export const HelpLink = args => {
-
+
{
+ const [inputValues, setInputValues] = React.useState({
+ firstName: '',
+ lastName: '',
+ emailAddress: '',
+ });
+ const [hasErrors, setHasErrors] = React.useState({
+ firstName: false,
+ lastName: false,
+ emailAddress: false,
+ });
+
+ const firstNameInputRef = React.useRef();
+ const lastNameInputRef = React.useRef();
+ const emailAddressInputRef = React.useRef();
+
+ const submit = () => {
+ setHasErrors({
+ firstName: false,
+ lastName: false,
+ emailAddress: false,
+ });
+
+ if (!inputValues.emailAddress) {
+ setHasErrors(prev => ({ ...prev, emailAddress: true }));
+ emailAddressInputRef.current.focus();
+ }
+
+ if (!inputValues.lastName) {
+ setHasErrors(prev => ({ ...prev, lastName: true }));
+ lastNameInputRef.current.focus();
+ }
+
+ if (!inputValues.firstName) {
+ setHasErrors(prev => ({ ...prev, firstName: true }));
+ firstNameInputRef.current.focus();
+ }
+ };
+
+ const reset = () => {
+ setHasErrors({
+ firstName: false,
+ lastName: false,
+ emailAddress: false,
+ });
+ setInputValues({
+ firstName: '',
+ lastName: '',
+ emailAddress: '',
+ });
+
+ firstNameInputRef.current.focus();
+ };
+
+ return (
+ <>
+
+ setInputValues(prev => ({ ...prev, firstName: event.target.value }))
+ }
+ required
+ value={inputValues.firstName}
+ ref={firstNameInputRef}
+ />
+
+
+ setInputValues(prev => ({ ...prev, lastName: event.target.value }))
+ }
+ required
+ value={inputValues.lastName}
+ ref={lastNameInputRef}
+ />
+
+
+ setInputValues(prev => ({ ...prev, emailAddress: event.target.value }))
+ }
+ required
+ value={inputValues.emailAddress}
+ ref={emailAddressInputRef}
+ />
+
+
+
+
+
+ >
+ );
+};
diff --git a/website/react-magma-docs/src/pages/api/form.mdx b/website/react-magma-docs/src/pages/api/form.mdx
index 2d84b46d52..7a94181fe8 100644
--- a/website/react-magma-docs/src/pages/api/form.mdx
+++ b/website/react-magma-docs/src/pages/api/form.mdx
@@ -27,23 +27,135 @@ import {
} from 'react-magma-dom';
export function Example() {
+ const [state, setState] = React.useState({
+ firstName: '',
+ lastName: '',
+ email: '',
+ });
+
+ const [errors, setErrors] = React.useState({
+ firstName: false,
+ lastName: false,
+ email: false,
+ });
+
+ const resetErrors = () => {
+ setErrors({
+ firstName: false,
+ lastName: false,
+ email: false,
+ });
+ };
+
+ const onSubmit = event => {
+ event.preventDefault();
+ resetErrors();
+
+ if (!state.firstName) {
+ setErrors(prevErrors => ({ ...prevErrors, firstName: true }));
+ }
+
+ if (!state.lastName) {
+ setErrors(prevErrors => ({ ...prevErrors, lastName: true }));
+ }
+
+ if (!state.email) {
+ setErrors(prevErrors => ({ ...prevErrors, email: true }));
+ }
+ };
+
+ const cancel = () => {
+ setState({
+ firstName: '',
+ lastName: '',
+ email: '',
+ });
+ setErrors({
+ firstName: false,
+ lastName: false,
+ email: false,
+ });
+ };
+
+ const errorMessage = React.useMemo(() => {
+ let message = '';
+
+ if (Object.values(errors).some(error => error)) {
+ message += 'Please fix the following errors:\n';
+ }
+
+ for (const error in errors) {
+ if (errors[error]) {
+ switch (error) {
+ case 'firstName':
+ message += '· First Name is required\n';
+ break;
+ case 'lastName':
+ message += '· Last Name is required\n';
+ break;
+ case 'email':
+ message += '· Email is required\n';
+ break;
+ default:
+ return;
+ }
+ }
+ }
+
+ return message;
+ }, [errors]);
+
return (
diff --git a/website/react-magma-docs/src/pages/api/input.mdx b/website/react-magma-docs/src/pages/api/input.mdx
index 9a1936a8de..49240f6a1e 100644
--- a/website/react-magma-docs/src/pages/api/input.mdx
+++ b/website/react-magma-docs/src/pages/api/input.mdx
@@ -170,7 +170,9 @@ If an error message is present, it will replace the helper text. Can be a node o
The `required` prop can be used to indicate when a field is required. It is also important to indicate to the user whenever a field is required.
-While React Magma provides the error styling, it is up to the consumer app to handle the validation.
+While React Magma provides the error styling, it is up to the consumer app to handle the validation. We recommend using a `ref` on the input for accessibility.
+
+For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in Form.
```tsx
import React from 'react';
@@ -178,10 +180,12 @@ import { Input, Button, ButtonGroup, Spacer } from 'react-magma-dom';
export function Example() {
const [hasError, setHasError] = React.useState(false);
const [nameValue, setNameValue] = React.useState('');
+ const inputRef = React.useRef();
function submit() {
if (nameValue === '') {
setHasError(true);
+ inputRef.current.focus();
} else {
setHasError(false);
}
@@ -190,6 +194,7 @@ export function Example() {
function reset() {
setHasError(false);
setNameValue('');
+ inputRef.current.focus();
}
return (
@@ -201,6 +206,7 @@ export function Example() {
onChange={event => setNameValue(event.target.value)}
required
value={nameValue}
+ ref={inputRef}
/>