Skip to contents

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.

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_box(
  size = "two-thirds",
  gov_text("This is a paragraph of body text.")
)

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.


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

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"
)

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.