Skip to content

Commit e8cc084

Browse files
authored
Merge pull request #1747 from aehrc/issue/1741
Enhance accessibility for mandatory fields
2 parents a97a80b + 5737133 commit e8cc084

File tree

8 files changed

+71
-34
lines changed

8 files changed

+71
-34
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ For changelogs of other libraries, please refer to their respective repositories
1010

1111
Changelog only includes changes from version 0.36.0 onwards.
1212

13+
## [1.2.8] - 2025-10-28
14+
### Fixed
15+
- Required field asterisks accessibility enhancements also applies to group headings and tab buttons.
16+
17+
## [1.2.7] - 2025-10-28
18+
### Fixed
19+
- Required field asterisks are now properly announced as "Mandatory field" by screen readers. Refer to issue [#1741](https://github.com/aehrc/smart-forms/issues/1741)
20+
1321
## [1.2.6] - 2025-10-28
1422
### Fixed
1523
- Remove `openOnFocus` prop from autocomplete fields to allow screen readers to read out label, placeholder text and control's role. Refer to issue [#1733](https://github.com/aehrc/smart-forms/issues/1733)

apps/smart-forms-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@aehrc/sdc-assemble": "^2.0.2",
2929
"@aehrc/sdc-populate": "^4.6.2",
3030
"@aehrc/sdc-template-extract": "^1.0.14",
31-
"@aehrc/smart-forms-renderer": "^1.2.6",
31+
"@aehrc/smart-forms-renderer": "^1.2.8",
3232
"@dnd-kit/core": "^6.3.1",
3333
"@emotion/react": "^11.14.0",
3434
"@emotion/styled": "^11.14.1",

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/smart-forms-renderer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aehrc/smart-forms-renderer",
3-
"version": "1.2.6",
3+
"version": "1.2.8",
44
"description": "FHIR Structured Data Captured (SDC) rendering engine for Smart Forms",
55
"main": "lib/index.js",
66
"scripts": {

packages/smart-forms-renderer/src/components/FormComponents/GroupItem/GroupHeading.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,6 @@ const GroupHeading = memo(function GroupHeading(props: GroupHeadingProps) {
6464
<>
6565
<Box display="flex" alignItems="center" width="100%">
6666
<Box position="relative" display="flex" flexGrow={1} alignItems="center">
67-
{/* Required asterisk position is in front of text */}
68-
{required && requiredIndicatorPosition === 'start' ? (
69-
<RequiredAsterisk
70-
sx={{ position: 'absolute', top: 0, left: -8 }} // Adjust top and left values as needed
71-
>
72-
*
73-
</RequiredAsterisk>
74-
) : null}
75-
7667
{/* Group Heading typography */}
7768
{/* flexGrow: 1 is important if xhtml and markdown rendering has width: 100% */}
7869
<Typography
@@ -82,6 +73,14 @@ const GroupHeading = memo(function GroupHeading(props: GroupHeadingProps) {
8273
display="flex"
8374
alignItems="center"
8475
sx={{ flexGrow: 1, ...(parentStyles || {}) }}>
76+
{/* Required asterisk position is in front of text */}
77+
{required && requiredIndicatorPosition === 'start' ? (
78+
<RequiredAsterisk
79+
sx={{ position: 'absolute', top: 0, left: -8 }} // Adjust top and left values as needed
80+
>
81+
*
82+
</RequiredAsterisk>
83+
) : null}
8584
<ItemTextSwitcher qItem={qItem} />
8685

8786
{/* Required asterisk position is behind text */}

packages/smart-forms-renderer/src/components/FormComponents/ItemParts/ItemLabel.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,6 @@ const ItemLabel = memo(function ItemLabel(props: ItemLabelProps) {
6060
return (
6161
<Box display="flex" alignItems="center" justifyContent="space-between">
6262
<Box position="relative" display="flex" flexGrow={1} alignItems="center">
63-
{/* Required asterisk position is in front of text */}
64-
{required && requiredIndicatorPosition === 'start' ? (
65-
<RequiredAsterisk
66-
readOnly={readOnly}
67-
variant={variant}
68-
sx={{ position: 'absolute', top: 0, left: -8 }}>
69-
*
70-
</RequiredAsterisk>
71-
) : null}
72-
7363
{/* Label typography */}
7464
{/* Added 0.5 marginTop (4px) because item labels doesn't look in line with their fields */}
7565
{/* flexGrow: 1 is important if xhtml and markdown rendering has width: 100% */}
@@ -85,6 +75,16 @@ const ItemLabel = memo(function ItemLabel(props: ItemLabelProps) {
8575
...(parentStyles || {}),
8676
...itemStyles
8777
}}>
78+
{/* Required asterisk position is in front of text */}
79+
{required && requiredIndicatorPosition === 'start' ? (
80+
<RequiredAsterisk
81+
readOnly={readOnly}
82+
variant={variant}
83+
sx={{ position: 'absolute', top: 4, left: -8 }}>
84+
*
85+
</RequiredAsterisk>
86+
) : null}
87+
8888
<ItemTextSwitcher qItem={qItem} />
8989

9090
{/* Required asterisk position is behind text */}

packages/smart-forms-renderer/src/components/FormComponents/ItemParts/RequiredAsterisk.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import React from 'react';
1919
import type { TypographyProps } from '@mui/material/Typography';
2020
import Typography from '@mui/material/Typography';
2121
import { useRendererConfigStore } from '../../../stores';
22+
import { SrOnly } from '../SrOnly.styles';
2223

2324
interface RequiredAsteriskProps extends TypographyProps {
2425
readOnly?: boolean;
@@ -32,9 +33,18 @@ function RequiredAsterisk(props: RequiredAsteriskProps) {
3233
const readOnlyTextColor = readOnlyVisualStyle === 'disabled' ? 'text.disabled' : 'text.secondary';
3334

3435
return (
35-
<Typography component="span" color={readOnly ? readOnlyTextColor : 'error'} {...rest}>
36-
{children}
37-
</Typography>
36+
<>
37+
<Typography
38+
component="span"
39+
color={readOnly ? readOnlyTextColor : 'error'}
40+
aria-hidden="true" // Hides visible asterisk from screen readers
41+
{...rest}>
42+
{children}
43+
</Typography>
44+
45+
{/* Screen-reader-only accessible label */}
46+
<SrOnly>Mandatory field</SrOnly>
47+
</>
3848
);
3949
}
4050

packages/smart-forms-renderer/src/components/Tabs/FormBodySingleTab.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import type { QuestionnaireItem } from 'fhir/r4';
2525
import ContextDisplayItem from '../FormComponents/ItemParts/ContextDisplayItem';
2626
import { useFocusTabHeading } from '../../hooks/useFocusTabHeading';
2727
import useDisplayCqfAndCalculatedExpression from '../../hooks/useDisplayCqfAndCalculatedExpression';
28+
import RequiredAsterisk from '../FormComponents/ItemParts/RequiredAsterisk';
29+
import useRenderingExtensions from '../../hooks/useRenderingExtensions';
2830

2931
interface FormBodySingleTabProps {
3032
qItem: QuestionnaireItem;
@@ -40,6 +42,10 @@ const FormBodySingleTab = memo(function FormBodySingleTab(props: FormBodySingleT
4042
const switchTab = useQuestionnaireStore.use.switchTab();
4143
const disableHeadingFocusOnTabSwitch =
4244
useRendererConfigStore.use.disableHeadingFocusOnTabSwitch();
45+
const requiredIndicatorPosition = useRendererConfigStore.use.requiredIndicatorPosition();
46+
47+
const { required } = useRenderingExtensions(qItem);
48+
const readOnly = qItem.readOnly ?? false;
4349

4450
const focusHeading = useFocusTabHeading();
4551

@@ -68,14 +74,28 @@ const FormBodySingleTab = memo(function FormBodySingleTab(props: FormBodySingleT
6874
<ListItemText
6975
primary={
7076
<Box display="flex" alignItems="center" justifyContent="space-between">
71-
<Typography
72-
id={`tab-${listIndex}`}
73-
component="span"
74-
fontWeight={600}
75-
fontSize="0.8125rem"
76-
aria-label={itemTextAriaLabel}>
77-
{tabLabel}
78-
</Typography>
77+
<Box display="flex" gap={0.25}>
78+
{/* Required asterisk position is in front of text */}
79+
{required && requiredIndicatorPosition === 'start' ? (
80+
<RequiredAsterisk>*</RequiredAsterisk>
81+
) : null}
82+
<Typography
83+
id={`tab-${listIndex}`}
84+
component="span"
85+
fontWeight={600}
86+
fontSize="0.8125rem"
87+
aria-label={itemTextAriaLabel}>
88+
{tabLabel}
89+
</Typography>
90+
91+
{/* Required asterisk position is behind text */}
92+
{required && requiredIndicatorPosition === 'end' ? (
93+
<RequiredAsterisk readOnly={readOnly} variant="groupHeading">
94+
*
95+
</RequiredAsterisk>
96+
) : null}
97+
</Box>
98+
7999
<Box display="flex" minHeight={24} minWidth={24} ml={1}>
80100
{contextDisplayItems.map((item) => {
81101
return <ContextDisplayItem key={item.linkId} displayItem={item} />;

0 commit comments

Comments
 (0)