-
Notifications
You must be signed in to change notification settings - Fork 922
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
USWDS - Character count: Enhance visual cue when limit is exceeded #5908
base: develop
Are you sure you want to change the base?
Conversation
…sts; add no label and no form group templates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to the unit test updates, there are a handful of changed files with this PR. Added some comments to help add clarity to each of the changes:
const formGroupEl = characterCountEl.querySelector(FORM_GROUP); | ||
|
||
if (!formGroupEl) { | ||
throw new Error(`${CHARACTER_COUNT} is missing inner ${FORM_GROUP}`); | ||
} | ||
|
||
const labelEl = characterCountEl.querySelector(LABEL); | ||
|
||
if (!labelEl) { | ||
throw new Error(`${CHARACTER_COUNT} is missing inner ${LABEL}`); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
Added checks for form group and label elements. These elements are necessary for the overall structure of the component and will result in JS errors for toggleErrorState
if they are missing.
/** | ||
* Updates character count form elements to match error status of input. | ||
* | ||
* @description On focusout, it will update the input label and form group to match the current state of the input element. | ||
* Called on focusout instead of input to prevent content shift while typing. | ||
* @param {HTMLInputElement|HTMLTextAreaElement} inputEl The character count input element | ||
*/ | ||
const toggleErrorState = (inputEl) => { | ||
const { labelEl, formGroupEl } = getCharacterCountElements(inputEl); | ||
const errorState = inputEl.classList.contains(INPUT_ERROR_CLASS); | ||
|
||
labelEl.classList.toggle(LABEL_ERROR_CLASS, errorState); | ||
formGroupEl.classList.toggle(FORM_GROUP_ERROR_CLASS, errorState); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
toggleErrorState
adds/removes form-group
and usa-label
with relevant --error
classes. Called on focusout
const FORM_GROUP_CLASS = `${PREFIX}-form-group`; | ||
const FORM_GROUP_ERROR_CLASS = `${FORM_GROUP_CLASS}--error`; | ||
const FORM_GROUP = `.${FORM_GROUP_CLASS}`; | ||
const LABEL_CLASS = `${PREFIX}-label`; | ||
const LABEL_ERROR_CLASS = `${LABEL_CLASS}--error`; | ||
const LABEL = `.${LABEL_CLASS}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
Added classes and element used by toggleErrorState
. Organized by when they appear in markup/DOM
FORM_GROUP_ERROR_CLASS, | ||
LABEL_ERROR_CLASS, | ||
INPUT_ERROR_CLASS, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
Exported for use in new unit tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated unit test templates
I added usa-form-group
and usa-label
's to all character count unit tests for consistency and to prevent JS errors now that the component checks for these elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
New unit test to check for the existence of form group element
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
New unit test to check for the existence of the label element
it("should add form group error classes on focusout when the input has error class", () => { | ||
input.classList.add(INPUT_ERROR_CLASS); | ||
|
||
EVENTS.focusout(input); | ||
|
||
assert.strictEqual( | ||
formGroup.classList.contains(FORM_GROUP_ERROR_CLASS), | ||
true | ||
); | ||
assert.strictEqual(label.classList.contains(LABEL_ERROR_CLASS), true); | ||
}); | ||
|
||
it("should remove form group error classes on focusout when the input does not have error class", () => { | ||
assert.strictEqual(input.classList.contains(INPUT_ERROR_CLASS), false); | ||
formGroup.classList.add(FORM_GROUP_ERROR_CLASS); | ||
label.classList.add(LABEL_ERROR_CLASS); | ||
|
||
EVENTS.focusout(input); | ||
|
||
assert.strictEqual( | ||
formGroup.classList.contains(FORM_GROUP_ERROR_CLASS), | ||
false | ||
); | ||
assert.strictEqual(label.classList.contains(LABEL_ERROR_CLASS), false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
New testing requirements to check for the addition / removal of error states on input and focusout
focusout: { | ||
[INPUT]() { | ||
toggleErrorState(this); | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add form group error classes on focusout
This idea came from a discussion between @jaclinec and I. We wanted to add the full error state classes but didn't want to have the content shift while the user was typing. She suggested we add these classes only after the user leaves the field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this approach makes sense. One thing I noticed is that it didn't quite reverse the way I expected when when I returned to the input and resolved the error. (See screenshot below).
To replicate:
- Turn on the input error state by entering too many characters
- Move focus out of the input, which activates the form group error styles
- Return focus to the input
- Remove enough characters so that it no longer exceeds maxlength
- Keep focus on the input and notice it still shows an error state, even though there is no error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amyleadem this is the intentional behavior based on @jaclinec's recommendation to not cause content shift while the user is typing (see comment above and testing instructions)
If the content shift on removal is acceptable, I can make it so that the error state is immediately removed rather than waiting for focusout
.
Here's a quick example of adding/removing the classes on input
. I think this would be particularly jarring for screen magnification users
Kapture.2024-05-23.at.10.02.35.webm
@@ -150,9 +169,25 @@ const updateCountMessage = (inputEl) => { | |||
inputEl.setCustomValidity(""); | |||
} | |||
|
|||
inputEl.classList.toggle(INPUT_ERROR_CLASS, isOverLimit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
Adds / removes input error classes to match invalid status message class
@mahoneycm we think there might be an issue with the blue focus indicator and the red indicator not having enough contrast with one another. Is there a way to add a small space between the two (maybe a few pixels) to make a distinction there are two states happening (one focus, one error)? https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html |
@amycole501 Here's what it looks like it with an offset of 2px Kapture.2024-05-03.at.15.59.41.webmThe input shrinks a bit but this is actually a result of the error border being added and not the outline offset. The same difference in input size occurs with or without the offset. What do we think about something like this? This issue might be a bit out of scope for this specific issue but definitely something we should consider with #5884 |
I like it! Would like to know what @alex-hull thinks but the separation makes it much easier to see the two borders. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mahoneycm
I started testing the interactions on this and generally it looks good! I had one question below about what happens after you fix an error (shown in the comment below). Let me know if you want to discuss!
I'll return to the code review once we figure that out.
focusout: { | ||
[INPUT]() { | ||
toggleErrorState(this); | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this approach makes sense. One thing I noticed is that it didn't quite reverse the way I expected when when I returned to the input and resolved the error. (See screenshot below).
To replicate:
- Turn on the input error state by entering too many characters
- Move focus out of the input, which activates the form group error styles
- Return focus to the input
- Remove enough characters so that it no longer exceeds maxlength
- Keep focus on the input and notice it still shows an error state, even though there is no error
Summary
Enhanced visual cue when
maxlength
is exceeded. Now, the visual state of the character count will reflect its validation state.Breaking change
This is not a breaking change.
Related issue
Closes #5851
Related pull requests
Changelog →
Preview link
Character count →
Problem statement
Testing results show that users expect additional indication that they have exceeded the limit. Additionally, screen magnification users may be unable to see the character count status message beneath the textarea input.
Solution
Add additional visual cues to let users know they have exceeded the character count limit.
Major changes
usa-input--error
class is added to the form inputusa-input--error
andusa-form-group--error
are added to their relevant elements.Testing and review
Character count behaviors
focusout
each componentfocusout
each componentTesting checklist
npm run test
runs without errorusa-input--error
classes are applied on character input over character limitusa-input--error
classes are removed on character input under character limitusa-form-group--error
classes are applied afterfocusout
of over limit inputusa-form-group--error
classes are removed afterfocusout
of under limit inputusa-label--error
classes are applied afterfocusout
of over limit inputusa-label--error
classes are removed afterfocusout
of under limit input