Overview
This vignette covers all functions in shinyGovstyle that produce styled text content: headings, body text, lists, callout components, links, and typography. For page structure and layout, see the Layout options vignette.
Rich content in body components
Sometimes a plain sentence isn’t enough: you need a bold word, a link
in the middle of a sentence, or a line break. The body-content arguments
of insert_text() (content),
warning_text() (text), and
noti_banner() (body_txt), along with the list
items of gov_list() (list), accept
shiny tags instead of a plain string for exactly this. Pass
a shiny tag, a shiny::tagList(), or
shiny::HTML() output to embed inline emphasis, links, or
line breaks without writing raw HTML yourself.
insert_text(
inputId = "processing-note",
content = shiny::tagList(
shiny::tags$b("Important: "),
"it can take up to 8 weeks to process. ",
shinyGovstyle::external_link(
"https://www.gov.uk/",
"View the latest information on GOV.UK"
),
" before submitting."
)
)The same applies to banner() (label),
panel_output() (sub_text),
details() (help_text),
gov_summary() (info), and
accordion() (descriptions), which are covered
in Layout options and the function
reference.
Plain character strings continue to work unchanged, so the simpler per-component examples below remain valid.
heading_text()
Creates a semantic HTML heading element with a GOV.UK heading CSS class.
heading_text("Summary", size = "l", level = 2)Arguments
| Argument | Default | Description |
|---|---|---|
text_input |
— | The heading text to display |
size |
"xl" |
Visual size: "xl", "l", "m",
or "s"
|
level |
1 |
HTML heading level: integer 1–6 |
id |
auto | Element ID — auto-generated from text_input if
omitted |
Visual size and semantic level are independent
size and level control two different
things. size controls how big the heading looks
(the CSS class applied: govuk-heading-xl,
govuk-heading-l, and so on). level controls
what the heading is in the underlying HTML
(<h1>, <h2>, etc.), which
determines its place in the page’s document outline (the structure a
screen reader user navigates by).
Usually a bigger visual size goes with a higher-up level, but not
always. Sometimes you need a smaller-looking heading that’s still
structurally important, for example a page where the first visible
heading needs to be modest in size but is still structurally the
<h1>:
heading_text("User guide", size = "m", level = 1)Always set both arguments deliberately. Do not leave
level at its default of 1 for every heading on
a page.
Recommended size–level pairings
Standard pages (most dashboards and data tools):
| Heading role | size |
level |
|---|---|---|
| Page title | "l" |
1 |
| Section heading | "m" |
2 |
| Sub-section heading | "s" |
3 |
Long-form content (user guides, methodology, accessibility statements):
| Heading role | size |
level |
|---|---|---|
| Page title | "xl" |
1 |
| Section heading | "l" |
2 |
| Sub-section heading | "m" |
3 |
| Sub-sub-section heading | "s" |
4 |
Accessibility
Do not skip heading levels. Screen reader users
often jump from heading to heading to scan a page, the same way a
sighted user skims a page visually. Going straight from an
<h1> to an <h3> breaks that
outline and makes the page harder to navigate for them. This is also a
formal requirement, under WCAG
2.2 success criterion 1.3.1: Info and Relationships.
Write headings in sentence case. Only capitalise the first word and proper nouns.
# Correct — sentence case
heading_text("Summary of findings", size = "l", level = 1)
# Incorrect — title case
heading_text("Summary Of Findings", size = "l", level = 1)For more information, read the GOV.UK headings guidance.
Heading IDs
If id is not supplied it is auto-generated by
lowercasing text_input and replacing non-alphanumeric
characters with underscores. Supply an explicit id when you
need a stable anchor to link to, or when two headings would otherwise
generate the same ID:
heading_text("Methodology", size = "l", level = 2, id = "methodology")Page titles
The page title is the text shown in the browser tab. It is set by the
<title> element in the HTML
<head>, and you can read or change it at runtime via
document.title.
Why it matters
- Accessibility: screen readers announce the page title on navigation. If the title never changes, users who rely on assistive technology have no way of telling that the page has moved on.
- Wayfinding: users routinely have many tabs open. A title that reflects the current page lets them find the right tab at a glance.
-
GOV.UK convention: the GOV.UK
Design System recommends a consistent format of
"<page name> | <service name>", and that the title is kept in sync with the visible page.
If your app has multiple pages (tabs or panels the user switches
between), setting a title once in ui.R isn’t enough. The
title also needs to update as the user moves between pages, otherwise
the browser tab title stops matching what’s on screen.
Three options
1. Static title only
Fine for single-page apps, where the title never needs to change.
Pass title to gov_page():
gov_page(
title = "Annual school workforce statistics",
# ...
)2. Automatic sync via service_navigation()
(recommended)
If your app uses service_navigation() for top-level
navigation, page-title sync is on by default. Set
page_title_suffix to your service name to get the
"<page> | <service>" format:
service_navigation(
c("Summary" = "summary", "Detailed stats" = "detailed_stats"),
page_title_suffix = "School workforce"
)The tab title now updates to
"Summary | School workforce" (and similar for each link)
whenever the user navigates — both by clicking a nav link and via
update_service_navigation() from your server code.
To opt out, pass auto_page_title = FALSE and manage the
title yourself with option 3.
3. Programmatic control with
update_page_title()
Call from your server code when you need precise control. For example, when a page heading differs from its nav link label, or for pages reached without clicking a nav link (modal links, next/back buttons, footer shortcuts):
shiny::observeEvent(input$footer_cookies_link, {
bslib::nav_select("tabs", "cookies")
update_service_navigation(session, "sn_cookies")
update_page_title(
session,
page_title = "Cookies",
service_name = "School workforce"
)
})When service_name is supplied the title is composed as
"<page_title> | <service_name>"; when it is
NULL only page_title is shown. The JavaScript
handler ships with every shinyGovstyle component, so
update_page_title() works in any app that uses at least one
shinyGovstyle function in its UI.
gov_text()
A wrapper that produces a <p class="govuk-body">
paragraph element. Use it when you want to add body text with correct
GOV.UK styling without writing raw tag calls.
gov_list()
Creates a GOV.UK-styled list. The style argument
controls both the HTML element used and the visual presentation:
style |
HTML element | Appearance |
|---|---|---|
"none" (default) |
<ul> |
Plain, no markers |
"bullet" |
<ul> |
Bulleted list |
"number" |
<ol> |
Numbered list |
gov_list(c("First item", "Second item", "Third item"), style = "bullet")
gov_list(c("First step", "Second step", "Third step"), style = "number")Use style = "number" when order matters (steps in a
process, ranked results, or sequential instructions). Use
style = "bullet" for unordered items. The plain style
("none") is useful when you want list semantics for screen
readers without a visual marker.
Rich content in list items
Items are not limited to plain strings. Pass a shiny tag
or a shiny::tagList() for any item that needs a link or
inline emphasis, and mix them freely with plain-string items:
gov_list(
list(
"An ordinary item",
shiny::tagList(
"An item with a ",
external_link("https://www.gov.uk", "link to GOV.UK")
)
),
style = "bullet"
)For more information, read the GOV.UK lists guidance.
insert_text()
Displays a GOV.UK inset text box — a bordered callout for supplementary information that is related to, but not the main focus of, the surrounding content.
insert_text(
inputId = "processing-note",
content = "It can take up to 8 weeks to process your application."
)Use inset text for information the user needs to know but that is not
the primary action or decision on the page — for example, a processing
time, an exception to a rule, or a clarification. Do not use it for
warnings about consequences; use warning_text()
instead.
For more information, read the GOV.UK inset text guidance.
warning_text()
Displays a GOV.UK warning text component: a bold statement with a prominent “!” icon, used to warn users about something with serious consequences.
warning_text(
inputId = "fine-warning",
text = "You can be fined up to £5,000 if you do not register."
)The “!” icon is hidden from screen readers, and the word “Warning” is added as text you can’t see but a screen reader can, so what gets announced is “Warning: [your text]” rather than the icon character itself. Because “Warning” is already added for you, don’t start your own text with the word “Warning” too, otherwise screen reader users hear “Warning, warning: …”.
Use warning_text() when the consequence of missing the
information is serious. For less critical supplementary information, use
insert_text() instead.
For more information, read the GOV.UK warning text guidance.
noti_banner()
Displays a GOV.UK notification banner for information that is not directly related to the current page content — such as a service-wide problem, an upcoming deadline, or the outcome of a previous action.
Two types
Standard (default, blue): for neutral information such as service problems or upcoming events.
noti_banner(
inputId = "service-notice",
title_txt = "Important",
body_txt = paste0(
"This service will be unavailable on Saturday ",
"14 June from 8am to 6pm."
)
)Success (green): to confirm that a previous action
completed successfully. Uses role="alert" so screen readers
announce it automatically on page load.
noti_banner(
inputId = "submission-confirm",
title_txt = "Success",
body_txt = "Your report has been submitted.",
type = "success"
)When to use each component
| Situation | Component |
|---|---|
| Information not related to the current page task | noti_banner() |
| Supplementary information related to the page | insert_text() |
| Serious consequences if the user misses information | warning_text() |
| Form validation errors |
error_summary() / error messages |
Use notification banners sparingly — users often overlook them when they appear frequently. Show only one at a time, and never alongside an error summary.
For more information, read the GOV.UK notification banner guidance.
external_link()
A wrapper for HTML anchor elements that produces safe, accessible external links with consistent behaviour.
external_link("https://www.example.gov.uk/guidance", "Guidance for applicants")What the function does automatically
- Adds
target="_blank"to open the link in a new tab - Adds
rel="noopener noreferrer"to prevent reverse tabnabbing - Appends “(opens in new tab)” to the visible link text by default
- Adds a visually hidden “(opens in new tab)” span for screen readers
when
add_warning = FALSE
Descriptive link text
Screen reader users often pull up a list of all the links on a page
in isolation, without the surrounding sentence, so link text needs to
make sense on its own. external_link() checks this for you
and will error if you supply a raw URL as the link text, vague text such
as “click here” or “here”, or text ending with a full stop. It will also
warn if the text is fewer than 7 characters, since very short link text
is usually not descriptive enough.
This is also a formal requirement, under WCAG 2.2 success criterion 2.4.4: Link Purpose (In Context).
# Correct — descriptive
external_link("https://www.example.gov.uk/apply", "Apply for a licence")
# Will error — vague text
external_link("https://www.example.gov.uk/apply", "click here")
# Will error — raw URL as text
external_link(
"https://www.example.gov.uk/apply",
"https://www.example.gov.uk/apply"
)Grouped links
When displaying several external links together, repeating “(opens in
new tab)” on each is visually repetitive. Set
add_warning = FALSE and add a single explanatory sentence
above the group instead:
gov_text("The following links open in a new tab.")
gov_list(
list = list(
external_link(
"https://www.example.gov.uk/a",
"Guidance document A",
add_warning = FALSE
),
external_link(
"https://www.example.gov.uk/b",
"Guidance document B",
add_warning = FALSE
)
),
style = "bullet"
)Removing the visible “(opens in new tab)” text also removes the
visual cue for sighted users. Set add_warning = "icon" to
add a small decorative arrow icon after the link text instead, it is
hidden from screen readers (who still get the same hidden warning as
add_warning = FALSE), so keep the explanatory sentence
above the group:
external_link(
"https://www.example.gov.uk/a",
"Guidance document A",
add_warning = "icon"
)For more information, read the GOV.UK links guidance.
font()
GDS Transport is a restricted typeface and must only be used
on GOV.UK domains. If your app is not hosted on a GOV.UK
domain, do not call font(). You do not need to do anything
and your app will default to Arial instead, which is the correct
behaviour.
If you are on a GOV.UK domain and therefore want to use the
font() function, putting it within your UI will load the
GDS Transport typeface for use in your app. By default the GOV.UK
Frontend CSS specifies
font-family: GDS Transport, arial, sans-serif — if the font
files are not loaded the browser falls back to Arial automatically.
# Only include this if your app is on a GOV.UK domain
font()For more information on when GDS Transport is permitted, read the GOV.UK typeface guidance.
