What's new
What changed in each release, and whether you need to do anything about it.
Releases are announced by the design lead. There is no fixed cadence, a release happens when there is something worth shipping.
Versions are pinned in your package.json, deliberately. Nothing changes under you. To take a new release, change the version in all four tarball URLs and run npm install.
The system is 0.x. Minor bumps may contain breaking changes; where they do, this file says so and tells you what to change.
| Marker | Means |
|---|---|
| Action needed | You have to change something to take this release |
| Optional | Worth doing, but the previous release keeps working |
| No action | Take it and carry on |
Unreleased
No action to take this, but read the Border/Strong note if you use that token directly.
Search shipped. The system's only search field, Basic, With Button, With Icon Button and Typeahead, with the full WAI-ARIA combobox keyboard model. @dhcw/sr-react → Search. The Input set's Type=Search variants were removed in Figma on 2026-06-04; use Search with its label / hint / required props rather than wrapping an Input around it. Autocomplete now composes the Search field and the Select listbox.
Tabs gained Level=Secondary, the sub-tab pill, for the second level of a two-level tab structure. A tab with sub-views is a second tablist inside the first tab's panel, never a chevron on a tab.
Tabs overflow now wraps instead of scrolling. A scrolling strip gave no indication that tabs were off-screen. Two defects were fixed at the same time: every tab was roughly twice as wide as its label, and 52px tall rather than the specified 40px. If you were compensating for either, stop.
data-label is gone from Tabs markup. It fed a width-reservation trick that did not work and has been removed. Nothing reads it now.
The Switch's off state is visible now. Its track was Border/Default, so the white thumb was 1.37:1 against it, and the thumb's position is what tells you the switch is off. The track is now Border/Strong. Hover moved to the label (previewing the checked colour would make an off switch look on), and the checked thumb moved to Text/On Fill so it stays white in dark mode.
Border/Strong moved from Grey/600 #4C6272 to Grey/500 #768692. Still clears the 3:1 that SC 1.4.11 requires for a control boundary (3.75:1 on white, 3.44:1 on the page), but reads as an affordance rather than shouting. It affects checkbox and radio rest borders, the switch hover track, the date-picker today ring and Tag/black. Optional: if you hardcoded #4C6272 for a control boundary anywhere, move to the token.
v0.3.0, 2026-09-07
Action needed if you use icons by name. This release renames, retires and adds icons, and corrects the icon token layer. It is the first stable release on npm, latest moves off the release candidate.
`` npm install @dhcw/sr-tokens@0.3.0 @dhcw/sr-icons@0.3.0 @dhcw/sr-web@0.3.0 @dhcw/sr-react@0.3.0 ``
Icons renamed, update these names
| Was | Now |
|---|---|
action/edit2 | action/edit-note |
nav/menu2 | nav/menu-kebab |
nav/more | nav/more-horizontal |
clinical/lab-result | clinical/test |
clinical/cross | clinical/treatment |
schedule/bookmark | action/bookmark |
schedule/urgent | schedule/appointment (the old schedule/appointment is now schedule/calendar) |
Icons retired, pick a replacement
| Gone | Use instead |
|---|---|
clinical/diagnosis | clinical/result |
clinical/consent | action/edit-note |
status/alert | status/warning |
action/pause | action/hold |
Each was a second name for a drawing another icon already owned. One glyph now carries one meaning, and the build fails if that stops being true.
Icons whose drawing changed
clinical/referral, clinical/discharge and location/room were reassigned to different glyphs so no drawing means two things. action/scan is unchanged, it was briefly wrong in the repository and has been restored to the framed scanner it has always been.
23 icons added
Including a new device/ domain for mobile hardware (camera, camera-swap, video, torch-on, torch-off), and action/send, action/watchlist, action/bookmark-off, action/expand, action/collapse, action/unlock, clinical/result, clinical/request, clinical/assessment, clinical/attendance, nav/log-out, nav/account, nav/support, nav/feedback, people/demographics, people/patient-search, schedule/calendar, schedule/events.
Icon tokens now reach your platform
Optional, but this is the fix worth knowing about. The semantic icon tokens , sizes, stroke and eight colour roles, were defined but emitted to no platform at all. They now land in CSS, SCSS, XAML and JSON:
``css var(--sr-icon-size-md) var(--sr-icon-stroke) var(--sr-icon-color-subtle) ` `xml {StaticResource SrIconSizeMd} {StaticResource SrIconStroke} {StaticResource SrIconColorSubtle} ``
Stroke is 1, at every size. If you read --icon-stroke-default (2) or IconStrokeDefault (2), stop: those were wrong and are gone. No icon has ever been drawn at 2px. SrIconStroke is now an x:Double, so MAUI can bind it to a Path's StrokeThickness.
v0.2.1-rc.0, 2026-09-03
No action. This is a release candidate, not a release. It exists to prove the npm publishing route end to end before a real version goes out.
It is published under the next tag, which means a version range will not pick it up and nobody receives it without asking for it by name. If you are on v0.2.0, stay there, this changes nothing for you.
Trying it, if you have been asked to
`` npm install @dhcw/sr-tokens@next @dhcw/sr-icons@next @dhcw/sr-web@next @dhcw/sr-react@next ``
No URLs, no credentials. That is the point of the exercise: confirming that a plain npm install works before v0.2.1 goes to latest.
Tell us if the install fails, if anything renders differently from v0.2.0, or if your build resolves a path it did not before.
What changed since v0.2.0
Two accessibility fixes, both of which change what you see:
- Checkbox and Radio have a visible border at rest. They used Border/Default (Grey/200), which is 1.37:1 on white, under the 3:1 WCAG 2.2 requires for the boundary of a control. They now use Border/Strong (Grey/600, 6.37:1). Hover moves to the primary blue, since it previously used the colour that rest now has.
- The focus ring moves from Cyan/700 to Cyan/800 (
#0D8BAD). Cyan/700 was 2.95:1 on a white card against the 3:1 a focus indicator requires. Cyan/800 is the only step that clears 3:1 in both light and dark mode. Every focusable component is slightly darker on focus.
Plus lighter status surfaces (the 50 step rather than 100), which raises the contrast of text on status banners and tags.
Nothing renames. No markup, class name, token name or component prop moves.
v0.2.0, 2026-08-13
Action needed to upgrade, but only to change the version in four URLs. See "Upgrading" below for a one-liner that does it.
Once installed, no code changes are required: no markup, class name, token name or component prop moves in this release. Icons render lighter and there are new brand marks and icons available. Nothing you already wrote stops working.
Upgrading
From a product repository, run this and commit the result:
`` node -e "const v='0.2.0',f='package.json',s=require('fs');s.writeFileSync(f,s.readFileSync(f,'utf8').replace(/\/download\/v[0-9.]+\/dhcw-sr-([a-z]+)-[0-9.]+\.tgz/g,'/download/v'+v+'/dhcw-sr-$1-'+v+'.tgz'))" && npm install ``
It rewrites both halves of all four URLs. That is the part worth knowing: the version appears twice per dependency, once as the release tag and once in the tarball filename, so a hand-edit is eight values, and changing only the tag produces a URL that 404s. This release's notes carry the same command.
On release frequency. Batching changes into fewer, larger releases is a reasonable response to this friction, but it trades one cost for a worse one: larger releases are harder to review, harder to roll back, and leave products on stale versions for longer. The friction itself is fixable, see "Why the URLs at all" below.
Changed
- Icons are now 1px, not 2px. Every icon in every product changes weight. Lucide ships a 2px stroke and the design system had taken it wholesale; the Figma library had already moved to 1px, 121 of 125 components
, and nothing recorded the divergence. 2px read heavier than the text beside it, most visibly on the design-system website.
No action needed: no markup, class name, token or prop changes. Icons simply render lighter.
One thing to know: at 16px a 1px stroke resolves to about two-thirds of a device pixel and goes visibly light. Prefer 20px or 24px for any icon that carries meaning on its own; 16px is fine beside a text label. Do not reintroduce a per-size stroke, size the control up instead.
- records when to wrap a third-party component on web. The test is behavioural complexity, not component importance: build on native elements, wrap for keyboard interaction models. Menu, Tabs and Autocomplete are now blocked on a library evaluation rather than hand-built; Select stays as it is. Summarised for engineers in → "Wrapped or built?".
- The DHCW logo is now the real lockup.
logoFullSrcused to return a drawn placeholder, a blue square with a diamond in it. It now returns the official GIG Cymru / NHS Wales / Digital Health and Care Wales lockup. Anything rendering it picks up the change with no code edit; expect the mark to look different, and to be wider than the placeholder at the same height. - The lockup is a PNG rather than a vector. That was the deliberate choice: the artwork is trademarked and accuracy matters more than file size here.
- The icon-only mark is now real too.
logoSymbolSrc, used by mobile headers and by navigation when it is collapsed, used to render a generic square-and-diamond. It now returns the official DHCW icon, taken from the Figma Logos component's own vector geometry rather than cropped out of the lockup. Unlike the lockup it is an SVG, so it stays sharp at any size.
Added
logoSymbolInverseSrc, the icon-only mark in white, for dark or coloured backgrounds. Use it instead of recolouringlogoSymbolSrcwith CSS; the brand rules forbid recolouring the artwork.- Checkbox, Radio and Select now have website pages, each with usage guidance, every state, and copyable HTML, React, Blazor and MAUI markup. All three had code but nowhere documenting when to reach for which; each page now opens with the same "which one" table so the choice between them is the first thing you see.
DHCW.SingleRecord.Mauipublishes to GitHub Packages. A MAUI app installs it withdotnet add packageinstead of building it out of a checkout. Action needed for MAUI teams: the feed requires authentication for every install, including public packages, add anuget.configand a token withread:packages. That is deliberate rather than an oversight: the package carries NHS Wales and DHCW brand marks that its MIT licence does not cover. Setup is on the "Get the files" page.- The npm packages stay on release tarballs, and still need URL edits per release. GitHub Packages was considered and rejected for them: it would require the scope to be renamed away from
@dhcw, and it would put a token in every developer's.npmrcfor what is currently a credential-free install. records the reasoning and names npmjs.org as the escape hatch. DHCW.SingleRecord.Mauion NuGet, MAUI apps no longer copy XAML by hand. ShipsSrColors,SrIconsandSrStylesas typed ResourceDictionaries, plus the brand marks registered asMauiImageautomatically. npm cannot serve
.NET; this is the route for a MAUI app. Merge SrStyles last, it references keys from the other two. Copying the XAML by hand still works; the merge syntax differs between the two routes.
- Three new icons:
action/star,schedule/bookmark,file/pin. 123 icons now, across web, React, the sprite and MAUI. - Brand marks for NHS Wales, Welsh Clinical Portal and WNCR. Nine logo variants extracted from Figma and shipped from
@dhcw/sr-web, as data URIs (nhsWalesLogoSrc,wcpSymbolSrc, and so on) and as real files under@dhcw/sr-web/logos. Previously only the DHCW marks existed, so no product or co-brand mark could be rendered at all. - Brand marks are not icons and are not in
@dhcw/sr-icons. Icons inheritcurrentColorand are meant to be recoloured; brand marks must never be. Each mark ships as one file per ink, pick the variant that suits the background rather than recolouring. - Three new Radio types,
card-radio,card, andcard-icon. An option that needs a line of explanation was previously unbuildable. Passtypeanddescriptionto<Radio>, or use thesr-radio--cardclasses. The plain radio is unchanged and stays the default.
Known gap
UEChas no asset, so Urgent and Emergency Care cannot render its own mark. Its Figma artwork needs fixing first, the icons have unfilled vectors, the lockups carry live text.wncris white only, so a WNCR mark cannot go on a light background. Its navy variant is drawn with strokes and cannot be exported until outlined.
Why the URLs at all
npm cannot install a single workspace out of a git repository, so tarballs attached to a release are the standard answer while these packages are not on a registry. The cost is the URL editing above.
A registry removes it entirely, npm update and nothing to edit, ever. examined GitHub Packages for this and rejected it for npm: it requires the package scope to equal the owning account (@dhcw versus DHCW-Digital-Health-and-Care-Wales), and it requires an access token for every install, which would replace a credential-free install with one that needs a PAT in every developer's .npmrc and every Azure DevOps pipeline.
npmjs.org is the route that would work: public scoped packages, anonymous install, @dhcw preserved. The open question is whether the @dhcw scope is available there. That is worth answering if release friction is shaping how often the design system ships.
v0.1.1, 2026-08-12
Optional. v0.1.0 keeps working. Two of these are worth taking when you have a moment.
Added
- Tags is published, with a component page on the website covering all three variants:
status(a filled pill),filter(an outlined pill with a close button) andcount(a 24px disc holding a number). Tags/countis new in code. It existed in Figma and had never been built, seven types including a Dark Blue reserved for the one primary total on a screen. Past two digits the circle becomes a pill rather than clipping the number.@dhcw/sr-web/foundations, the font, the tokens and the typography utilities with no component CSS.
Changed
- React apps should import
foundations, notsingle-record.css:
```diff
- import '@dhcw/sr-web/dist/single-record.css'; + import '@dhcw/sr-web/foundations'; ```
Every React component already imports its own stylesheet, so the complete file was shipping all 21 component stylesheets plus a duplicate of each one you used. On a screen using seven components that is 238KB before, 131KB after, identical styling, 45% less of it.
Nothing renders differently. If you do not make this change, everything keeps working exactly as it does now.
Plain HTML keeps single-record.css. With no bundler there is nothing to assemble the per-component files, and one <link> working is the whole point of that file.
- Internal version pins are now ranges rather than exact.
@dhcw/sr-webpinned its siblings to an exact0.1.0, so a version bump had to move all four packages in lockstep or npm went looking for a version that is on no registry and the install failed. This only affected releases, not consumers.
Fixed
@dhcw/sr-tokenshad no entry points, so every import of it failed, including the CSS files that are the package's whole purpose.
v0.1.0, 2026-08-11
The first installable release. Action needed if you followed an earlier guide, see below.
Added
- Packages ship as tarballs attached to each GitHub release, installable with plain npm: no registry, no credentials, nothing in
.npmrc, and the same behaviour in Azure DevOps CI.
``json "dependencies": { "@dhcw/sr-tokens": "../releases/download/v0.1.0/dhcw-sr-tokens-0.1.0.tgz", "@dhcw/sr-icons": "../releases/download/v0.1.0/dhcw-sr-icons-0.1.0.tgz", "@dhcw/sr-web": "../releases/download/v0.1.0/dhcw-sr-web-0.1.0.tgz", "@dhcw/sr-react": "../releases/download/v0.1.0/dhcw-sr-react-0.1.0.tgz" } ``
All four are required, the React package depends on the other three.
- A native.NET MAUI token and style layer (
Colors.xaml,Styles.xaml,Icons.xaml), and 120 icons as XAML path geometry so an icon takes its colour from a token and follows the theme. MAUI is native XAML throughout; there is no Blazor Hybrid anywhere in the mobile estate.
SrDocumentViewerspecification, the API and visual contract for the PDF viewer wrapper, for a developer with a Syncfusion licence to implement.
Fixed
- The documented npm install never worked. Both of these fetch the repository root, which is a private workspace container with no entry point:
`` npm install github:DHCW-../single-record-design-system#main "@dhcw/sr-react": "github:DHCW-../single-record-design-system#main" ``
They appear to install and then throw ERR_MODULE_NOT_FOUND on import. npm cannot install a single workspace out of a git repository.
If your package.json still has a github: dependency, or your Vite config has resolve.alias entries pointing inside node_modules/@dhcw, replace them with the tarball URLs above and delete the aliases.
@dhcw/sr-web/dist/single-record.csswas not importable. Anexportsmap is a closed list, and./dist/*was missing, so the path every guide printed threwERR_PACKAGE_PATH_NOT_EXPORTEDeven though the file was right there.
- Missing subpath exports on
@dhcw/sr-reactforautocomplete,radio,selectandtags. The barrel import worked;@dhcw/sr-react/selectdid not.
Known gaps
modalandtagsare built with no website page (tags landed in v0.1.1).status-indicatoris built and undocumented.link,progress-indicatorsandsearchare specified but not built.- Dark mode tokens are provisional and not yet reconciled.
The website records the full picture of what is documented, built and published.