Skip to content

Commit 9a8bb7b

Browse files
Merge branch 'main' into patch-1
2 parents 50c45c6 + a8d56be commit 9a8bb7b

File tree

61 files changed

+2139
-945
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2139
-945
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# NHS.UK React components
22

3+
## 6.0.0-beta.3 - 27 October 2025
4+
5+
This version provides support for NHS.UK frontend v10.1 and includes:
6+
7+
- [Smaller radios](https://service-manual.nhs.uk/design-system/components/radios#smaller-radios) and [smaller checkboxes](https://service-manual.nhs.uk/design-system/components/checkboxes#smaller-checkboxes)
8+
- [Numbered pagination](https://service-manual.nhs.uk/design-system/components/pagination#for-navigating-between-pages-of-items)
9+
- React strict mode support
10+
11+
For a full list of changes in this release please refer to the [migration doc](https://github.com/NHSDigital/nhsuk-react-components/blob/main/docs/upgrade-to-6.0.md).
12+
313
## 6.0.0-beta.2 - 13 October 2025
414

515
This version provides support for NHS.UK frontend v10.x, React Server Components (RSC) and fixes a Rollup `'use client'` directive issue.

docs/upgrade-to-6.0.md

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@ The updated [header](https://service-manual.nhs.uk/design-system/components/head
1818
- update NHS logo in the header to have higher contrast when focused
1919
- refactor CSS classes and BEM naming, use hidden attributes instead of modifier classes, use generic search element
2020

21-
### Panel component
21+
#### Smaller versions of radio buttons and checkboxes
2222

23-
The [panel](https://service-manual.nhs.uk/design-system/components/panel) component from NHS.UK frontend v9.3.0 has been added:
23+
You can now use smaller versions of the [radios](https://service-manual.nhs.uk/design-system/components/radios) and [checkboxes](https://service-manual.nhs.uk/design-system/components/checkboxes) components by adding the `small` prop.
24+
25+
### Numbered pagination component
26+
27+
The [pagination](https://service-manual.nhs.uk/design-system/components/notification-banner) component from NHS.UK frontend v10.1 has been updated to support numbered pagination:
2428

2529
```jsx
26-
<Panel>
27-
<Panel.Title>Booking complete</Panel.Title>
28-
We have sent you a confirmation email
29-
</Panel>
30+
<Pagination>
31+
<Pagination.Link href="/results?page=1" previous />
32+
<Pagination.Item href="/results?page=1" number={1} />
33+
<Pagination.Item href="/results?page=2" number={2} current />
34+
<Pagination.Item href="/results?page=3" number={3} />
35+
<Pagination.Link href="/results?page=3" next />
36+
</Pagination>
3037
```
3138

32-
This replaces the [list panel component](#list-panel) which was removed in NHS.UK frontend v6.0.0.
33-
3439
### Notification banner component
3540

3641
The [notification banner](https://service-manual.nhs.uk/design-system/components/notification-banner) component from NHS.UK frontend v10 has been added:
@@ -42,23 +47,32 @@ The [notification banner](https://service-manual.nhs.uk/design-system/components
4247
</NotificationBanner>
4348
```
4449

50+
### Panel component
51+
52+
The [panel](https://service-manual.nhs.uk/design-system/components/panel) component from NHS.UK frontend v9.3.0 has been added:
53+
54+
```jsx
55+
<Panel>
56+
<Panel.Title>Booking complete</Panel.Title>
57+
We have sent you a confirmation email
58+
</Panel>
59+
```
60+
61+
This replaces the [list panel component](#list-panel) which was removed in NHS.UK frontend v6.0.0.
62+
4563
### Support for React Server Components (RSC)
4664

4765
All components have been tested as React Server Components (RSC) but due to [multipart namespace component limitations](https://ivicabatinic.from.hr/posts/multipart-namespace-components-addressing-rsc-and-dot-notation-issues) an alternative syntax (without dot notation) can be used as a workaround:
4866

4967
```patch
50-
<Pagination>
51-
- <Pagination.Link href="/section/treatments" previous>
52-
+ <PaginationLink href="/section/treatments" previous>
53-
Treatments
54-
- </Pagination.Link>
55-
+ </PaginationLink>
56-
- <Pagination.Link href="/section/symptoms" next>
57-
+ <PaginationLink href="/section/symptoms" next>
58-
Symptoms
59-
- </Pagination.Link>
60-
+ </PaginationLink>
61-
</Pagination>
68+
<Breadcrumb>
69+
- <Breadcrumb.Item href="#">Home</Breadcrumb.Item>
70+
- <Breadcrumb.Item href="#">NHS services</Breadcrumb.Item>
71+
- <Breadcrumb.Item href="#">Hospitals</Breadcrumb.Item>
72+
+ <BreadcrumbItem href="#">Home</BreadcrumbItem>
73+
+ <BreadcrumbItem href="#">NHS services</BreadcrumbItem>
74+
+ <BreadcrumbItem href="#">Hospitals</BreadcrumbItem>
75+
</Breadcrumb>
6276
```
6377

6478
## Breaking changes
@@ -437,6 +451,26 @@ To align with NHS.UK frontend, the error summary component is automatically aler
437451
</ErrorSummary>
438452
```
439453

454+
### Pagination
455+
456+
To align with NHS.UK frontend, the pagination link component automatically renders its own "Previous page" or "Next page" text, with "page" being visually hidden. You will need to make the following changes:
457+
458+
- rename the `Pagination.Link` component to `Pagination.Item`
459+
- move text content (or the `children` prop) to the `labelText` prop
460+
461+
```patch
462+
<Pagination>
463+
- <Pagination.Link href="/section/treatments" previous>
464+
- Treatments
465+
- </Pagination.Link>
466+
- <Pagination.Link href="/section/symptoms" next>
467+
- Symptoms
468+
- </Pagination.Link>
469+
+ <Pagination.Item labelText="Treatments" href="/section/treatments" previous />
470+
+ <Pagination.Item labelText="Symptoms" href="/section/symptoms" next />
471+
</Pagination>
472+
```
473+
440474
### Select
441475

442476
You must rename the `Select` prop `selectRef` to `ref` for consistency with other components:

eslint.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ export default defineConfig([
4343
'import/no-named-as-default-member': 'off',
4444
'import/no-unresolved': 'off',
4545
'import/no-unused-modules': 'off',
46+
47+
// Prefer rules that are type aware
48+
'no-unused-vars': 'off',
49+
'@typescript-eslint/no-unused-vars': [
50+
'error',
51+
{
52+
argsIgnorePattern: '^_',
53+
varsIgnorePattern: '^_',
54+
ignoreRestSiblings: true,
55+
},
56+
],
4657
},
4758
settings: {
4859
'import/resolver': {

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nhsuk-react-components",
3-
"version": "6.0.0-beta.2",
3+
"version": "6.0.0-beta.3",
44
"license": "MIT",
55
"author": {
66
"name": "NHS England"
@@ -107,7 +107,7 @@
107107
"jest-axe": "^10.0.0",
108108
"jest-environment-jsdom": "^30.2.0",
109109
"lodash": "^4.17.21",
110-
"nhsuk-frontend": "^10.0.0",
110+
"nhsuk-frontend": "^10.1.0",
111111
"outdent": "^0.8.0",
112112
"prettier": "^3.6.2",
113113
"react": "^19.2.0",
@@ -124,7 +124,7 @@
124124
},
125125
"peerDependencies": {
126126
"classnames": ">=2.5.0",
127-
"nhsuk-frontend": ">=10.0.0 <11.0.0",
127+
"nhsuk-frontend": ">=10.1.0 <11.0.0",
128128
"react": ">=18.2.0",
129129
"react-dom": ">=18.2.0",
130130
"tslib": ">=2.8.0"

src/__tests__/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ describe('Index', () => {
9696
'NotificationBannerLink',
9797
'NotificationBannerTitle',
9898
'Pagination',
99+
'PaginationItem',
99100
'PaginationLink',
101+
'PaginationLinkText',
100102
'Panel',
101103
'PanelTitle',
102104
'Radios',

src/components/content-presentation/notification-banner/NotificationBanner.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import {
44
Children,
5-
createRef,
65
forwardRef,
76
useEffect,
7+
useImperativeHandle,
8+
useRef,
89
useState,
910
type ComponentPropsWithoutRef,
1011
} from 'react';
@@ -27,19 +28,22 @@ const NotificationBannerComponent = forwardRef<HTMLDivElement, NotificationBanne
2728
(props, forwardedRef) => {
2829
const { children, className, title, titleId, success, role, disableAutoFocus, ...rest } = props;
2930

30-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
31+
const moduleRef = useRef<HTMLDivElement>(null);
32+
const importRef = useRef<Promise<NotificationBannerModule | void>>(null);
3133
const [instanceError, setInstanceError] = useState<Error>();
3234
const [instance, setInstance] = useState<NotificationBannerModule>();
3335

36+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
37+
3438
useEffect(() => {
35-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
39+
if (!moduleRef.current || importRef.current || instance) {
3640
return;
3741
}
3842

39-
import('nhsuk-frontend')
43+
importRef.current = import('nhsuk-frontend')
4044
.then(({ NotificationBanner }) => setInstance(new NotificationBanner(moduleRef.current)))
4145
.catch(setInstanceError);
42-
}, [moduleRef, instance]);
46+
}, [moduleRef, importRef, instance]);
4347

4448
const items = Children.toArray(children);
4549

src/components/content-presentation/table/components/__tests__/TableCell.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Table.Cell', () => {
2929
</table>,
3030
);
3131

32-
expect(console.warn).toHaveBeenCalledTimes(1);
32+
expect(console.warn).toHaveBeenCalled();
3333
expect(console.warn).toHaveBeenLastCalledWith(
3434
'Table.Cell used outside of a Table.Head or Table.Body component. Unable to determine section type from context.',
3535
);

src/components/content-presentation/tabs/Tabs.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import classNames from 'classnames';
44
import { type Tabs as TabsModule } from 'nhsuk-frontend';
55
import {
6-
createRef,
76
forwardRef,
87
useEffect,
8+
useImperativeHandle,
9+
useRef,
910
useState,
1011
type ComponentPropsWithoutRef,
1112
type FC,
@@ -55,19 +56,22 @@ export const TabsContents: FC<TabsContentsProps> = ({ children, id, ...rest }) =
5556
const TabsComponent = forwardRef<HTMLDivElement, TabsProps>((props, forwardedRef) => {
5657
const { children, className, ...rest } = props;
5758

58-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLDivElement>());
59+
const moduleRef = useRef<HTMLDivElement>(null);
60+
const importRef = useRef<Promise<TabsModule | void>>(null);
5961
const [instanceError, setInstanceError] = useState<Error>();
6062
const [instance, setInstance] = useState<TabsModule>();
6163

64+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
65+
6266
useEffect(() => {
63-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
67+
if (!moduleRef.current || importRef.current || instance) {
6468
return;
6569
}
6670

67-
import('nhsuk-frontend')
71+
importRef.current = import('nhsuk-frontend')
6872
.then(({ Tabs }) => setInstance(new Tabs(moduleRef.current)))
6973
.catch(setInstanceError);
70-
}, [moduleRef, instance]);
74+
}, [moduleRef, importRef, instance]);
7175

7276
if (instanceError) {
7377
throw instanceError;

src/components/form-elements/button/Button.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import classNames from 'classnames';
44
import { type Button as ButtonModule } from 'nhsuk-frontend';
55
import {
6-
createRef,
76
forwardRef,
87
useEffect,
8+
useImperativeHandle,
9+
useRef,
910
useState,
1011
type ForwardedRef,
1112
type MouseEvent,
@@ -45,19 +46,22 @@ const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>((props, forwa
4546
...rest
4647
} = props;
4748

48-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLButtonElement>());
49+
const moduleRef = useRef<HTMLButtonElement>(null);
50+
const importRef = useRef<Promise<ButtonModule | void>>(null);
4951
const [instanceError, setInstanceError] = useState<Error>();
5052
const [instance, setInstance] = useState<ButtonModule>();
5153

54+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
55+
5256
useEffect(() => {
53-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
57+
if (!moduleRef.current || importRef.current || instance) {
5458
return;
5559
}
5660

57-
import('nhsuk-frontend')
61+
importRef.current = import('nhsuk-frontend')
5862
.then(({ Button }) => setInstance(new Button(moduleRef.current)))
5963
.catch(setInstanceError);
60-
}, [moduleRef, instance]);
64+
}, [moduleRef, importRef, instance]);
6165

6266
if (instanceError) {
6367
throw instanceError;
@@ -104,19 +108,22 @@ const ButtonLinkComponent = forwardRef<HTMLAnchorElement, ButtonLinkProps>(
104108
...rest
105109
} = props;
106110

107-
const [moduleRef] = useState(() => forwardedRef || createRef<HTMLAnchorElement>());
111+
const moduleRef = useRef<HTMLAnchorElement>(null);
112+
const importRef = useRef<Promise<ButtonModule | void>>(null);
108113
const [instanceError, setInstanceError] = useState<Error>();
109114
const [instance, setInstance] = useState<ButtonModule>();
110115

116+
useImperativeHandle(forwardedRef, () => moduleRef.current!, [moduleRef]);
117+
111118
useEffect(() => {
112-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
119+
if (!moduleRef.current || importRef.current || instance) {
113120
return;
114121
}
115122

116-
import('nhsuk-frontend')
123+
importRef.current = import('nhsuk-frontend')
117124
.then(({ Button }) => setInstance(new Button(moduleRef.current)))
118125
.catch(setInstanceError);
119-
}, [moduleRef, instance]);
126+
}, [moduleRef, importRef, instance]);
120127

121128
if (instanceError) {
122129
throw instanceError;

src/components/form-elements/character-count/CharacterCount.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
import classNames from 'classnames';
44
import { type CharacterCount as CharacterCountModule } from 'nhsuk-frontend';
5-
import { createRef, forwardRef, useEffect, useState, type ComponentPropsWithoutRef } from 'react';
5+
import {
6+
forwardRef,
7+
useEffect,
8+
useImperativeHandle,
9+
useRef,
10+
useState,
11+
type ComponentPropsWithoutRef,
12+
} from 'react';
613
import { FormGroup } from '#components/utils/index.js';
714
import { type FormElementProps } from '#util/types/FormTypes.js';
815

@@ -16,19 +23,22 @@ export interface CharacterCountProps
1623

1724
export const CharacterCount = forwardRef<HTMLTextAreaElement, CharacterCountProps>(
1825
({ maxLength, maxWords, threshold, formGroupProps, ...rest }, forwardedRef) => {
19-
const [moduleRef] = useState(() => formGroupProps?.ref || createRef<HTMLDivElement>());
26+
const moduleRef = useRef<HTMLDivElement>(null);
27+
const importRef = useRef<Promise<CharacterCountModule | void>>(null);
2028
const [instanceError, setInstanceError] = useState<Error>();
2129
const [instance, setInstance] = useState<CharacterCountModule>();
2230

31+
useImperativeHandle(formGroupProps?.ref, () => moduleRef.current!, [moduleRef]);
32+
2333
useEffect(() => {
24-
if (!('current' in moduleRef) || !moduleRef.current || instance) {
34+
if (!moduleRef.current || importRef.current || instance) {
2535
return;
2636
}
2737

28-
import('nhsuk-frontend')
38+
importRef.current = import('nhsuk-frontend')
2939
.then(({ CharacterCount }) => setInstance(new CharacterCount(moduleRef.current)))
3040
.catch(setInstanceError);
31-
}, [moduleRef, instance]);
41+
}, [moduleRef, importRef, instance]);
3242

3343
if (instanceError) {
3444
throw instanceError;
@@ -41,9 +51,9 @@ export const CharacterCount = forwardRef<HTMLTextAreaElement, CharacterCountProp
4151
...formGroupProps,
4252
'className': classNames('nhsuk-character-count', formGroupProps?.className),
4353
'data-module': 'nhsuk-character-count',
44-
'data-maxlength': maxLength,
45-
'data-maxwords': maxWords,
46-
'data-threshold': threshold,
54+
'data-maxlength': maxLength?.toString(),
55+
'data-maxwords': maxWords?.toString(),
56+
'data-threshold': threshold?.toString(),
4757
'ref': moduleRef,
4858
}}
4959
{...rest}

0 commit comments

Comments
 (0)