Typography
Our fonts and typographic styles, and how to apply them.
Font
Roboto
Roboto is the primary typeface across every Single Record product. It is an open-source typeface, freely available from Google Fonts, and it is supported on web, Blazor, React and .NET MAUI. Using one typeface everywhere keeps clinical and administrative screens consistent for staff who move between products during a shift.
Roboto is set at four weights: Regular 400 for body text, Medium 500 for labels and smaller headings, Bold 700 for headings, and Light 300 where a large heading needs to feel less dense.
Fallback font
Default to Arial when Roboto is not available. Arial is present on every managed NHS Wales device, has closely matching metrics, and so avoids a visible layout shift while Roboto loads. Set the stack once, at the root, and let every component inherit it.
:root {
font-family: Roboto, Arial, sans-serif;
}
Headings
Use heading tags such as <h1> and <h2> to mark up the
headings on a page, then apply a heading class such as sr-type-heading-l to style
them. Applying headings consistently gives every screen a clear content structure that both
sighted users and screen reader users can navigate.
Use one <h1> per page and do not skip levels to reach a size you like.
The heading level describes the structure of the page; the class controls how it looks. When a
heading needs to be visually smaller, change the class, not the level.
<h1 class="sr-type-heading-xl">Patient summary</h1>
<h2 class="sr-type-heading-l">Current medication</h2>
<h3 class="sr-type-heading-m">Repeat prescriptions</h3>
<h4 class="sr-type-heading-s">Issued in the last 6 months</h4>
<h5 class="sr-type-heading-xs">Dosage changes</h5>
Headings on record and dashboard screens
Single Record screens are denser than a public-facing service, so the top of the scale is used sparingly. Heading XL is reserved for the page title. Within a patient record, Heading M and Heading S carry most of the section structure, which keeps a long record scannable without the headings crowding out the content.
Labels and legends as headings
On a form where a single question fills the page, the label or legend is the page heading. Wrap it in the heading tag and apply the heading class to the label or legend itself, so the question is announced once rather than twice.
On the multi-question forms that are more common in Single Record, keep labels as labels.
Use sr-type-label at 14px Medium, which pairs with the 14px data values used
across tables and record views.
<label class="sr-type-label" for="nhs-number">NHS number</label>
<span class="sr-type-caption" id="nhs-number-hint">10 digits, spaced in groups of 3, 3 and 4</span>
<input class="sr-input" id="nhs-number" aria-describedby="nhs-number-hint">
<fieldset>
<legend class="sr-type-heading-s">Contact preferences</legend>
</fieldset>
Paragraphs
Body text has two sizes and the choice between them is about content type, not about fitting more on screen.
- Body M, 16px for long-form reading: clinical notes, guidance, letters and anything the user reads start to finish.
- Body S, 14px for data-dense content: table cells, record fields, supporting text and form values.
- Caption, 12px for hints, timestamps and metadata. Never use Caption for primary content.
Body S at 14px is a deliberate divergence from the 16px default that public-facing NHS and GOV.UK guidance uses. Single Record is a clinical, table-heavy system where a clinician needs to compare many values at once, and 14px keeps a usable number of rows in view. This still meets WCAG 2.2 AA: there is no minimum font size criterion, and resize to 200%, reflow and contrast are all met at this size.
<p class="sr-type-body-m">Long-form reading and clinical notes.</p>
<p class="sr-type-body-s">Primary content in tables and data-dense views.</p>
<p class="sr-type-caption">Last updated 06 Dec 2024 at 14:22</p>
Font size and weight overrides
There are no override utilities in this system, and that is deliberate. If a size or weight you need is not in the scale, that is a decision about the scale, not something to patch in a single component.
Two rules cover almost every case:
- Pick the style by role: heading, body, label or caption. Do not pick it by the size you want the text to be.
- Never set
font-size,font-weightorline-heightdirectly on a component. Change the class instead.
If you find yourself needing a value the scale does not have, request the change so it can be assessed once and applied everywhere.
<!-- Correct: a heading that needs to look smaller takes a smaller style,
and the heading level still reflects the document structure. -->
<h2 class="sr-type-heading-s">Allergies and adverse reactions</h2>
<!-- Wrong: never hardcode type values on a component. -->
<h2 style="font-size: 21px; font-weight: 600">Allergies and adverse reactions</h2>
Links
Links are underlined and use the link colour token, which meets AA contrast against every surface in the system. Keep the underline. In a dense clinical interface, colour alone is not a reliable signal, and removing the underline fails WCAG 1.4.1.
Write link text that makes sense on its own, because screen reader users often navigate by pulling up a list of links. Use "current medication list" rather than "click here". Where a link opens in a new tab, say so in the link text.
Review the current medication list before prescribing.
<p class="sr-type-body-m">
Review the <a href="/medication">current medication list</a> before prescribing.
</p>
<!-- Opening a new tab must be announced in the link text. -->
<a href="https://www.nhs.uk/" target="_blank" rel="noopener">
NHS.UK guidance (opens in a new tab)
</a>
Lists
Lists inherit the body styles, so pick the body size that matches the surrounding content and apply it to the list. Use an unordered list where the order does not matter, such as a list of current medications, and an ordered list for steps that must happen in sequence.
Keep list items short. If an item runs past two lines, it is usually a paragraph or a sub-heading with content under it, not a list item.
- Aspirin 75mg, once daily
- Atorvastatin 20mg, once daily at night
- Confirm the patient identity
- Check for recorded allergies
<ul class="sr-type-body-m">
<li>Aspirin 75mg, once daily</li>
<li>Atorvastatin 20mg, once daily at night</li>
</ul>
<ol class="sr-type-body-m">
<li>Confirm the patient identity</li>
<li>Check for recorded allergies</li>
</ol>
Section break
Use a horizontal rule to separate distinct groups of content, for example between a patient banner and the record beneath it. The rule uses the default border token so it stays quiet against the page.
Use section breaks sparingly. In a record view, whitespace and headings usually do the job better, and too many rules make a screen look busier than it is.
Patient banner sits above the rule.
The record begins below it.
<hr>
<!-- Where the break is structural but should not be seen,
use spacing instead of a visible rule. -->
<div style="margin-block: var(--space-6)"></div>
Text alignment
Align text left. Left-aligned text gives every line the same starting point, which is what makes a column of text or a table of values quick to scan, and it is easier to read for users with dyslexia. Do not justify text: justification creates uneven word spacing and rivers of white space that make reading harder.
The one exception is numeric table columns, where right-aligning the values lets users compare magnitudes down the column. Right-align the column heading to match the values beneath it. Never centre body text or table content.
| Medication | Dose (mg) |
|---|---|
| Atorvastatin | 20 |
| Aspirin | 75 |
| Levothyroxine | 125 |
<!-- Text columns: left aligned, which is the default. -->
<td class="sr-table__cell">Atorvastatin 20mg</td>
<!-- Numeric columns: right aligned, heading matches the values. -->
<th scope="col" class="sr-table__cell--numeric">Dose (mg)</th>
<td class="sr-table__cell sr-table__cell--numeric">20</td>
The type scale
Every style below is rendered live from the built tokens, so what you see is what ships.
| Class | Desktop | Mobile | Weight | Use when |
|---|---|---|---|---|
sr-type-heading-xl | 36/44 | 28/36 | Bold | Page title, one per page |
sr-type-heading-l | 28/36 | 24/32 | Bold | Major section |
sr-type-heading-m | 24/32 | 20/28 | Bold | Sub-section |
sr-type-heading-s | 20/28 | 18/24 | Bold | Card or group heading |
sr-type-heading-xs | 16/24 | 16/24 | Medium | Smallest heading |
sr-type-body-m | 16/24 | 16/24 | Regular | Long-form reading, clinical notes |
sr-type-body-s | 14/20 | 14/20 | Regular | Primary content in tables and data-dense views |
sr-type-label | 14/20 | 14/20 | Medium | Form labels, column headers |
sr-type-caption | 12/16 | 12/16 | Regular | Hints, timestamps, metadata |
Accessibility requirements
| Requirement | WCAG SC | How Single Record meets it | Test method |
|---|---|---|---|
| Text resizes to 200% without loss of content | 1.4.4 | All sizes are set in relative units, so the whole scale grows with the browser text size. | Browser zoom to 200% |
| Content reflows to a single column | 1.4.10 | Line lengths are capped and layouts reflow at 320px with no horizontal scrolling. | Resize to 320px |
| Body text meets contrast | 1.4.3 | Text colours are checked against every surface token they can sit on before release. | Automated contrast |
| Headings describe the structure | 1.3.1 / 2.4.6 | Heading level follows the document outline; the class only controls the visual size. | Heading map, screen reader |
| Links identifiable without colour | 1.4.1 | Links keep their underline in body content rather than relying on colour alone. | Greyscale review |
| Text spacing can be overridden | 1.4.12 | Line heights sit on the 4px grid and are set in relative units, so user stylesheets apply cleanly. | Text spacing bookmarklet |