Semantic HTML and Shiny Applications

[This article was first published on Ashley's Blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

A couple of weeks ago I was looking around for different CSS frameworks to play around with, and came across Semantic HTML (not to be confused with Semantic UI) and I’m hooked! There are several “classless CSS frameworks” that are implemented under the ideology of Semantic HTML: styling of elements (e.g. height and colour) is applied to the HTML tags rather than classes, meaning that the HTML is less of a sea of <div> and <span> tags with 6 or 7 classes, and more of a wider range of HTML tags that better explain what is contained in the web page.

Some examples include:

Why use Semantic HTML?

One of the key things about making a good UI is to ensure that it is accessible as possible. Screen readers don’t take any account of JS or CSS within a web page, so having Semantic HTML at least provided a basic level of structure and understanding for all. When over 2% of Americans would benefit from screen readers1,2, this should be considered for any {shiny} application.

There is also a file size advantage using classless css over the more commonly seen frameworks. For example, the most recent release of Bootstrap (5.1.3) is 161kB (and that doesn’t include all of the JavaScript required for components like modals), whereas some of these classless frameworks can be as small as a couple of kilobytes (Sakura is only 4kB and isn’t even 200 lines long). The smaller files mean that web pages load quicker and are less prone to unexpected behaviour.

Trade-offs are bound to occur; the more well known frameworks have been updated and iterated for years, and include many components and features that won’t be available in these classless frameworks, making it much quicker to build an application without writing any extra CSS. They have also been including more accessibility features by using ARIA attributes (a list of all attributes are available on Mozilla Web Docs), they provide enhanced accessibility compared to Semantic HTML.

You don’t need to use a classless framework to make it more semantic. Simply by changing the <div> and <span> tags within your existing framework is sometimes enough. Because most styling is attached to the classes rather than the components, changing these tags will not affect the UI but will make the web page more accessible.


Both of these UIs are using the same classes, one with <div> elements and the other with Semantic HTML3.

Semantic HTML in Shiny Applications

Here are a few simple changes that can be applied to any {shiny} application that can make the layout more semantic and accessible to users. All HTML tags are available in the tags list from the {htmltools} package.

When to use and instead of and

These pairs might look interchangeable in the UI of the web page, however they a screen reader will read both of these differently. Whilst <b> and <i> look bold and italicized, the screen reader will pronounce as if it is standard text (and is now recommended to use the font-weight style instead of <b>). <strong> and <em> are recognised by the screen reader and will emphasize accordingly.

Try using screen reader on this sentence and work out which “this” is strong and which is b.

for outputs

<output> is a container which injects the results of a calculation or a user action. This is exactly what all of the output functions are doing in your UI. Certain functions, such as textOutput, contain a parameter container that enables you to choose the HTML tag to use (by default it is <div> or <span> depending on whether or not the output has been specified to be inline).

Other outputs, such as plots or tables, can be wrapped within one of these <output> tags so that the user can differentiate between images that are shown on application load, and images that are generated by selecting different options.

for images

Whilst you can simply add a new paragraph under an image, table, or even a quote, by using <figure> and <figcaption> you can more explicitly link the caption to the component. It might look like they are linked styling several <div>s, but this will let the screen reader better know that the caption is describing the figure.

  # Add inputs here e.g. imageOutput() or DTOutput()

for abbreviations and acronyms

Know what either SCUBA or CAPTCHA mean? Me neither, and dashboards can be full of acronyms users might be unaware of. Using tags$abbr(title = "longhand", "abbreviation") will include a tooltip of the longhand of the acronyms, making it easier to keep track of them.


in hierarchical order

I always fall foul of this particular issue; I will use whichever header tag I like best, whether or not it is in the correct order. Google Lighthouse is a great tool to measure website accessibility, and one of the things it checks is that web pages use headers in the correct order to help better structure the page, meaning you shouldn’t skip levels just because

looks nicer than

. Instead copy the style of the headers you want to use and assign them to h1, h2 and h3 in a CSS file so that you adhere to this rule.

Interesting HTML Tags

Whilst writing this post, I found out about a load of HTML tags that I’ve never used but are certainly useful. Here are just a few of them:

A piece of inline text denoting an input required from a keyboard Ctrl + Shift + S


These work in a similar fashion to <ul> and <ol> to create a descriptive list. Unlike the aforementioned, there are no bullet points, but each item includes a term <dt>, and the description <dd> can refer to one or several terms.


These tags are great for creating a semantic section of inputs within an application. Separate the sections of an input form with <fieldset>s, each with the first element with a legend. These work really nicely as part of shiny::sidebarPanel as this wraps all contents within a <form>.

  class = "well", # Adds the same styling as shiny::sidebarPanel
    tags$legend("Section 1"),
    # Add inputs here
    tags$legend("Section 2")
    # Add inputs here

Example form using form, fieldset and legend HTML tags

For a list of all HTML tags available with definitions and examples, have a look at this Mozilla Web Docs article.

To leave a comment for the author, please follow the link and comment on their blog: Ashley's Blog. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)