The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Layout options

Overview

shinyGovstyle provides a set of layout functions that produce the HTML structure GOV.UK Frontend CSS expects. This vignette covers all of the layout functions available and explains how they fit together to build a complete app.

The layout functions fall into two groups:


Page-level components

These components form the outer frame of every page. They sit outside the main content area and are consistent across all pages of your app.

+-------------------------------------------------------+
|  skip_to_main()   [visually hidden, keyboard only]    |
+-------------------------------------------------------+
|  cookieBanner()   [optional]                          |
+-------------------------------------------------------+
|  header()                                             |
+-------------------------------------------------------+
|  service_navigation()   [optional, multi-page apps]   |
+-------------------------------------------------------+
|  banner()   [optional, e.g. Beta or Alpha]            |
+-------------------------------------------------------+
|                                                       |
|  gov_main_layout()   ← id = "main"                    |
|  +--------------------------------------------------+  |
|  |  your content goes here                         |  |
|  +--------------------------------------------------+  |
|                                                       |
+-------------------------------------------------------+
|  footer()                                             |
+-------------------------------------------------------+

skip_to_main()

Provides a visually hidden “Skip to main content” link that becomes visible when focused by a keyboard user. This is an accessibility requirement and should always be the first element in your UI, before the header.

skip_to_main()

By default it links to #main, which matches the id applied by gov_main_layout(). If you change the inputID argument of gov_main_layout(), pass the same value to skip_to_main().

For more information, read the documentation for the GOV.UK Skip link component.

cookieBanner()

Displays a GOV.UK-styled cookie consent banner. It requires shinyjs::useShinyjs() to be present in the UI. All element IDs within the banner are preset — see ?cookieBanner for the server-side observeEvent pattern needed to handle accept and reject interactions.

shinyjs::useShinyjs()
cookieBanner("My service name")

For more information, including when this should be used, read the documentation for the GOV.UK Cookie banner component.

The main content area

gov_main_layout() produces a <div class="govuk-width-container"> wrapping a <main class="govuk-main-wrapper">. The outer <div> constrains content width; the <main> element carries the responsive vertical padding. Everything between the page-level components and the footer lives inside it.

gov_main_layout(
  # your content here
)

The id (default "main") is applied directly to the <main> element, which is the correct target for skip_to_main(). The <main> element also carries role="main" and tabindex="-1", so keyboard focus moves to it when the skip link is activated.


The primary layout system

Inside gov_main_layout(), content is structured using a three-function grid system: gov_row(), gov_box(), and optionally gov_text().

gov_main_layout()
└── gov_row()
    ├── gov_box(size = "two-thirds")
    │   └── [your content]
    └── gov_box(size = "one-third")
        └── [your content]

gov_row()

Creates a GOV.UK grid row. You can have multiple rows inside gov_main_layout(), each stacked vertically.

gov_main_layout(
  gov_row(
    # columns go here
  ),
  gov_row(
    # another row
  )
)

gov_box()

Creates a column within a row. The size argument controls the column width using GOV.UK Frontend’s grid classes:

size Width
"full" 100%
"one-half" 50%
"two-thirds" 66%
"one-third" 33%
"three-quarters" 75%
"one-quarter" 25%

Sizes within a row should add up to a full width. For example, "two-thirds" and "one-third" sit side by side:

gov_main_layout(
  gov_row(
    gov_box(
      size = "two-thirds",
      heading_text("Main content", size = "l"),
      # inputs, text, etc.
    ),
    gov_box(
      size = "one-third",
      heading_text("Sidebar", size = "m"),
      # supporting content
    )
  )
)

For a simple single-column layout, use size = "full":

gov_main_layout(
  gov_row(
    gov_box(
      size = "full",
      heading_text("Page title", size = "l")
    )
  )
)

gov_text()

A wrapper that produces a <p class="govuk-body"> paragraph element. For full guidance on gov_text() and all other text functions, see the Headings and text vignette.


gov_layout() — legacy alternative

Warning: gov_layout() is not recommended for new development and may be removed in a future release. Use gov_main_layout() with gov_row() and gov_box() instead.

gov_layout() is a single-function alternative that combines a width container and a column in one call:

gov_layout(
  size = "two-thirds",
  heading_text("Page title", size = "l"),
  # content
)

It is well suited to simple, single-column apps where you want a width constraint without setting up the full gov_main_layout() / gov_row() / gov_box() hierarchy.

As soon as your app needs more than one column, multiple rows, or a combination of widths, switch to the full system. Nesting gov_layout() inside gov_main_layout() will produce doubled-up width container HTML and cause the content to appear visually inset from the page-level components.


Multi-page dashboards

For apps with multiple sections, use service_navigation() in combination with a hidden tab panel. The navigation bar renders as a row of links below the header; clicking a link fires a Shiny input that you use in your server to switch the visible panel.

Wiring navigation to panels

Use a hidden tab panel for the content area and observeEvent() in your server to switch panels when a navigation link is clicked. When the user clicks a service navigation link, the JavaScript binding updates the active state automatically — you only need to switch the panel:

# ui.R — shiny tabsetPanel
shiny::tabsetPanel(
  type = "hidden",
  id = "main_panels",
  shiny::tabPanel("Summary",       value = "nav_summary",  "Content"),
  shiny::tabPanel("Detailed data", value = "nav_detail",   "Content"),
  shiny::tabPanel("User guide",    value = "nav_guide",    "Content")
)

# server.R — nav link click: JS handles the active state, just switch the panel
shiny::observeEvent(input$nav_summary, {
  shiny::updateTabsetPanel(session, "main_panels", selected = "nav_summary")
})

If you prefer bslib tab panels, use bslib::navset_hidden() and bslib::nav_select() instead:

# ui.R — bslib navset_hidden
bslib::navset_hidden(
  id = "main_panels",
  bslib::nav_panel("Summary",       value = "nav_summary",  "Content"),
  bslib::nav_panel("Detailed data", value = "nav_detail",   "Content"),
  bslib::nav_panel("User guide",    value = "nav_guide",    "Content")
)

# server.R
shiny::observeEvent(input$nav_summary, {
  bslib::nav_select("main_panels", "nav_summary")
})

Repeat the observeEvent block for each navigation link.

update_service_navigation() is only needed when navigation is triggered programmatically — for example, via a next / back button — because in that case the nav link itself is not clicked and the active state does not update automatically. See ?update_service_navigation for full details and examples.

# server.R — programmatic navigation: must update both the panel and the nav
shiny::observeEvent(input$next_btn, {
  shiny::updateTabsetPanel(session, "main_panels", selected = "nav_detail")
  shinyGovstyle::update_service_navigation(session, "nav_detail")
})

Modularising the code

Once an app has multiple pages, it is strongly recommended to use Shiny modules to keep each page’s UI and server logic self-contained. The inst/example_app bundled with this package demonstrates this pattern: each page is a module in inst/example_app/modules/, with mod_<name>_ui() and mod_<name>_server() functions called from the top-level ui.R and server.R. This keeps individual files focused and makes it straightforward to add or remove pages without touching the overall app structure.


Complete example

The following is a minimal but complete multi-page app that uses all of the layout components covered in this vignette:

library(shiny)
library(shinyGovstyle)

ui <- bslib::page_fluid(
  skip_to_main(),
  header(
    org_name = "My department",
    service_name = "My dashboard"
  ),
  service_navigation(
    c(
      "Summary"  = "nav_summary",
      "About"    = "nav_about"
    )
  ),
  banner(
    inputId = "phase",
    type = "Beta",
    label = "This is a new service."
  ),

  gov_main_layout(
    shiny::tabsetPanel(
      type = "hidden",
      id = "main_panels",

      shiny::tabPanel(
        "Summary", value = "nav_summary",
        gov_row(
          gov_box(
            size = "two-thirds",
            heading_text("Summary", size = "l"),
            gov_text("Welcome to the summary page.")
          ),
          gov_box(
            size = "one-third",
            heading_text("Quick facts", size = "m"),
            gov_text("Supporting information goes here.")
          )
        )
      ),

      shiny::tabPanel(
        "About", value = "nav_about",
        gov_row(
          gov_box(
            size = "full",
            heading_text("About this dashboard", size = "l"),
            gov_text("This page describes the dashboard.")
          )
        )
      ),

      shiny::tabPanel(
        "Accessibility statement", value = "accessibility_panel",
        gov_row(
          gov_box(
            size = "full",
            heading_text("Accessibility statement", size = "l"),
            gov_text("This page describes the accessibility of the dashboard.")
          )
        )
      )
    )
  ),

  footer(
    links = c(`Accessibility statement` = "accessibility_footer_link")
  )
)

server <- function(input, output, session) {
  shiny::observeEvent(input$nav_summary, {
    shiny::updateTabsetPanel(session, "main_panels", selected = "nav_summary")
  })
  shiny::observeEvent(input$nav_about, {
    shiny::updateTabsetPanel(session, "main_panels", selected = "nav_about")
  })
  shiny::observeEvent(input$accessibility_footer_link, {
    shiny::updateTabsetPanel(session, "main_panels",
                             selected = "accessibility_panel")
  })
}

shiny::shinyApp(ui, server)

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.