Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3f6b6c5
Update to NHS.UK frontend v10.3.0
colinrotherham Jan 20, 2026
b5b2902
Update snapshots with duplicate class now removed
colinrotherham Jan 20, 2026
411c4b2
Rename input prefix and suffix HTML class
colinrotherham Jan 19, 2026
b931744
Update the HTML for header search
colinrotherham Jan 19, 2026
8fe3364
Update the HTML for responsive table cell content
colinrotherham Jan 19, 2026
45b5ea6
Rename checkbox and radio button 'aria-controls' attributes
colinrotherham Jan 19, 2026
cb47449
Update the HTML for tables as a panel
colinrotherham Jan 19, 2026
0716975
Add support for `size` on headings
colinrotherham Jan 19, 2026
ba8e576
Add support for `visuallyHiddenText` to render React elements
colinrotherham Jan 20, 2026
151e51c
Add support for `visuallyHiddenText` prop on headings
colinrotherham Jan 20, 2026
787d352
Add support for `visuallyHiddenText` prop on back link
colinrotherham Jan 21, 2026
90c4a6c
Add boolean props for other card types
colinrotherham Jan 20, 2026
fc93a9b
Update the HTML for warning callouts
colinrotherham Jan 20, 2026
6437c86
Add interruption panel variant
colinrotherham Jan 19, 2026
110a4ba
Add file upload component
colinrotherham Jan 20, 2026
d2671d2
Enable NHS.UK frontend i18n support
colinrotherham Jan 20, 2026
434085b
Formatting
colinrotherham Jan 20, 2026
3fea9b8
Simplify summary list component to avoid actions wrapper
colinrotherham Jan 20, 2026
52b399d
Simplify card component to avoid unnecessary context
colinrotherham Jan 20, 2026
d70051f
Set up story backgrounds and column widths
colinrotherham Jan 21, 2026
c411271
Add support for card actions
colinrotherham Jan 20, 2026
5dd42ff
Add more card examples
colinrotherham Jan 21, 2026
0d3a16f
Add support for `reverse` prop in link components
colinrotherham Jan 21, 2026
fac75a6
Update migration guide
colinrotherham Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .storybook/preview.ts

This file was deleted.

53 changes: 53 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import './storybook.scss';
import { type Preview } from '@storybook/react-vite';

import { Col, Container, Row } from '#components/layout/index.js';

const preview: Preview = {
decorators: (Story, { parameters }) =>
parameters.width === false ? (
<Story />
) : (
<Container>
<main className="nhsuk-main-wrapper" id="maincontent">
<Row>
<Col width={parameters.width ?? 'two-thirds'}>
<Story />
</Col>
</Row>
</main>
</Container>
),
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
backgrounds: {
options: {
dark: { name: 'Dark', value: '#005eb8' },
light: { name: 'Light', value: '#f0f4f5' },
white: { name: 'White', value: '#fff' },
grey: { name: 'Grey', value: '#d8dde0' },
},
},
width: 'two-thirds',
layout: 'fullscreen',
initialGlobals: {
backgrounds: { value: 'light' },
},
options: {
storySort: {
order: [
'Welcome',
'Migration Guides',
'Form Elements',
'Content Presentation',
'Navigation',
'Layout',
'Patterns',
],
},
},
},
tags: ['autodocs'],
};

export default preview;
42 changes: 21 additions & 21 deletions docs/upgrade-to-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,28 +130,24 @@ This replaces the [list panel component](#list-panel) which was removed in NHS.U

The [summary list](https://service-manual.nhs.uk/design-system/components/summary-list) component now includes improvements from NHS.UK frontend v9.6.2:

- new props `noBorder` and `noActions` supported at `<SummaryList.Row>` level
- new prop `noBorder` supported at `<SummaryList.Row>` level
- new child component `<SummaryList.Action>` for row actions
- removed unnecessary child component `<SummaryList.Actions>`

```patch
<SummaryList>
- <SummaryList.Row>
+ <SummaryList.Row noBorder>
<SummaryList.Key>Name</SummaryList.Key>
<SummaryList.Value>Karen Francis</SummaryList.Value>
<SummaryList.Actions>
- <SummaryList.Actions>
- <a href="#">
- Change<span className="nhsuk-u-visually-hidden"> name</span>
- </a>
+ <SummaryList.Action href="#" visuallyHiddenText="name">
+ Change
+ </SummaryList.Action>
</SummaryList.Actions>
</SummaryList.Row>
- <SummaryList.Row>
+ <SummaryList.Row noActions>
<SummaryList.Key>Date of birth</SummaryList.Key>
<SummaryList.Value>15 March 1984</SummaryList.Value>
- </SummaryList.Actions>
</SummaryList.Row>
</SummaryList>
```
Expand Down Expand Up @@ -239,8 +235,8 @@ Read about [our guidance on error messages](https://service-manual.nhs.uk/design
If you are using the `headingLevel` prop you will need to update any uppercase values to lowercase:

```patch
- <Card.Heading headingLevel="H3">Example heading</Card.Heading>
+ <Card.Heading headingLevel="h3">Example heading</Card.Heading>
- <Card heading="Example heading" headingLevel="H3">
+ <Card heading="Example heading" headingLevel="h3">
```

### Restore visually hidden text for accessibility
Expand Down Expand Up @@ -460,14 +456,11 @@ Before:
After:

```jsx
<Card cardType="feature">
<Card.Content>
<Card.Heading id="C">C</Card.Heading>
<ul className="nhsuk-list nhsuk-list--border">
<li><a href="/conditions/chest-pain/">Chest pain</a></li>
<li><a href="/conditions/cold-sores/">Cold sore</a></li>
</ul>
</Card.Content>
<Card heading="C" headingProps={{ id: 'C' }} feature>
<ul className="nhsuk-list nhsuk-list--border">
<li><a href="/conditions/chest-pain/">Chest pain</a></li>
<li><a href="/conditions/cold-sores/">Cold sore</a></li>
</ul>
</Card>

<div className="nhsuk-back-to-top">
Expand Down Expand Up @@ -622,6 +615,13 @@ To align with NHS.UK frontend, you must make the following changes:
</Table>
```

If you are using the `Table.Panel` child component, you should migrate to the feature card:

```patch
- <Table.Panel heading="Other conditions like impetigo">
+ <Card heading="Other conditions like impetigo" feature>
```

### Textarea

You must rename the `Textarea` prop `textareaRef` to `ref` for consistency with other components:
Expand All @@ -642,12 +642,12 @@ You must rename the `TextInput` prop `inputRef` to `ref` for consistency with ot

### Warning callout

To align with NHS.UK frontend, the warning callout `WarningCallout.Label` component has been renamed to `WarningCallout.Heading` as shown:
To align with NHS.UK frontend, the warning callout `WarningCallout.Label` component has been moved to the `heading` prop as shown:

```patch
<WarningCallout>
- <WarningCallout>
+ <WarningCallout heading="School, nursery or work">
- <WarningCallout.Label>School, nursery or work</WarningCallout.Label>
+ <WarningCallout.Heading>School, nursery or work</WarningCallout.Heading>
<p>
Stay away from school, nursery or work until all the spots have crusted over. This is
usually 5 days after the spots first appeared.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"jest-axe": "^10.0.0",
"jest-environment-jsdom": "^30.2.0",
"lodash": "^4.17.21",
"nhsuk-frontend": "^10.2.2",
"nhsuk-frontend": "^10.3.1",
"outdent": "^0.8.0",
"prettier": "^3.7.4",
"react": "^19.2.3",
Expand Down
8 changes: 2 additions & 6 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@ describe('Index', () => {
'BreadcrumbItem',
'Button',
'Card',
'CardContent',
'CardContext',
'CardAction',
'CardDescription',
'CardGroup',
'CardGroupItem',
'CardHeading',
'CardImage',
'CardLink',
'cardTypeIsCareCard',
'CharacterCount',
'Checkboxes',
'CheckboxesContext',
Expand Down Expand Up @@ -57,6 +54,7 @@ describe('Index', () => {
'ErrorSummaryListItem',
'ErrorSummaryTitle',
'Fieldset',
'FileUpload',
'Footer',
'FooterCopyright',
'FooterList',
Expand Down Expand Up @@ -116,7 +114,6 @@ describe('Index', () => {
'SkipLink',
'SummaryList',
'SummaryListAction',
'SummaryListActions',
'SummaryListKey',
'SummaryListRow',
'SummaryListValue',
Expand All @@ -143,7 +140,6 @@ describe('Index', () => {
'useFormContext',
'UserIcon',
'WarningCallout',
'WarningCalloutHeading',
]);
});
});
16 changes: 13 additions & 3 deletions src/components/content-presentation/panel/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@ import { PanelTitle } from './components/index.js';

import { childIsOfComponentType } from '#util/types/TypeGuards.js';

export type PanelProps = ComponentPropsWithoutRef<'div'>;
export interface PanelProps extends ComponentPropsWithoutRef<'div'> {
interruption?: boolean;
}

const PanelComponent = forwardRef<HTMLDivElement, PanelProps>(
({ children, className, ...rest }, forwardedRef) => {
({ children, className, interruption, ...rest }, forwardedRef) => {
const items = Children.toArray(children);
const title = items.find((child) => childIsOfComponentType(child, PanelTitle));
const bodyItems = items.filter((child) => child !== title);

return (
<div className={classNames('nhsuk-panel', className)} ref={forwardedRef} {...rest}>
<div
className={classNames(
'nhsuk-panel',
{ 'nhsuk-panel--interruption': interruption },
className,
)}
ref={forwardedRef}
{...rest}
>
{title}
{bodyItems ? <div className="nhsuk-panel__body">{bodyItems}</div> : null}
</div>
Expand Down
113 changes: 75 additions & 38 deletions src/components/content-presentation/panel/__tests__/Panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,59 @@ import { Panel, type PanelTitleProps } from '..';
import { renderClient, renderServer } from '#util/components';

describe('Panel', () => {
const Example = (props: Parameters<typeof Panel>[0]) => (
<Panel {...props}>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>
);

const ExampleInterruption = (props: Parameters<typeof Panel>[0]) => (
<Panel interruption {...props}>
<Panel.Title size="l">Jodie Brown had a COVID-19 vaccine less than 3 months ago</Panel.Title>
<p>They had a COVID-19 vaccine on 25 September 2025.</p>
<p>
For most people, the minimum recommended gap between COVID-19 vaccine doses is 3 months.
</p>
</Panel>
);

it('matches snapshot', async () => {
const { container } = await renderClient(
<Panel>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
{ className: 'nhsuk-panel' },
);
const { container } = await renderClient(<Example />, {
className: 'nhsuk-panel',
});

expect(container).toMatchSnapshot();
});

it('matches snapshot (via server)', async () => {
const { container, element } = await renderServer(
<Panel>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
{ className: 'nhsuk-panel' },
);
const { container, element } = await renderServer(<Example />, {
className: 'nhsuk-panel',
});

expect(container).toMatchSnapshot('server');

await renderClient(element, {
className: 'nhsuk-panel',
hydrate: true,
container,
});

expect(container).toMatchSnapshot('client');
});

it('matches snapshot as interruption panel', async () => {
const { container } = await renderClient(<ExampleInterruption />, {
className: 'nhsuk-panel',
});

expect(container).toMatchSnapshot();
});

it('matches snapshot as interruption panel (via server)', async () => {
const { container, element } = await renderServer(<ExampleInterruption />, {
className: 'nhsuk-panel',
});

expect(container).toMatchSnapshot('server');

Expand All @@ -41,36 +74,40 @@ describe('Panel', () => {
it('forwards refs', async () => {
const ref = createRef<HTMLDivElement>();

const { modules } = await renderClient(
<Panel ref={ref}>
<Panel.Title>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
{ className: 'nhsuk-panel' },
);
const { modules } = await renderClient(<Example ref={ref} />, {
className: 'nhsuk-panel',
});

const [panelEl] = modules;

expect(ref.current).toBe(panelEl);
expect(ref.current).toHaveClass('nhsuk-panel');
});

it.each<PanelTitleProps | undefined>([
undefined,
{ headingLevel: 'h1' },
{ headingLevel: 'h2' },
{ headingLevel: 'h3' },
{ headingLevel: 'h4' },
])('renders heading level $headingLevel if specified', (props) => {
const { container } = render(
<Panel>
<Panel.Title {...props}>Booking complete</Panel.Title>
We have sent you a confirmation email
</Panel>,
);

const title = container.querySelector('.nhsuk-panel__title');

expect(title).toHaveProperty('tagName', props?.headingLevel?.toUpperCase() ?? 'H1');
describe('Panel.Title', () => {
it.each<PanelTitleProps>([
{ headingLevel: 'h1' },
{ headingLevel: 'h2' },
{ headingLevel: 'h3' },
{ headingLevel: 'h4' },
])('renders with custom heading level $headingLevel', (props) => {
const { container } = render(<Panel.Title {...props}>Booking complete</Panel.Title>);

const title = container.querySelector('.nhsuk-panel__title');

expect(title).toHaveProperty('tagName', props?.headingLevel?.toUpperCase() ?? 'H1');
});

it.each<PanelTitleProps['size']>(['m', 'l', 'xl'])('renders with custom size %s', (size) => {
const { container } = render(
<Panel.Title size={size}>
Jodie Brown had a COVID-19 vaccine less than 3 months ago
</Panel.Title>,
);

const title = container.querySelector('.nhsuk-panel__title');

expect(title).toHaveClass(`nhsuk-panel__title--${size}`);
});
});
});
Loading