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:
- Pico.css
- Sakura
- Almond.CSS
- MVP.css
- Classless.css * Despite its name, Classless.css does include a few classes to create grids and cards
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 <strong>
and <em>
instead of <b>
and <i>
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.
<output>
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.
<figure>
and <figcaption>
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.
tags$figure(
# Add inputs here e.g. imageOutput() or DTOutput()
tags$figcaption("Caption")
)
<abbr>
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.
Using <h1>
to <h6>
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 <h4>
looks nicer than <h2>
. 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:
<kbd>
A piece of inline text denoting an input required from a keyboard Ctrl + Shift + S
<dl>
, <dt>
and <dl>
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.
<form>
, <fieldset>
and <legend>
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>
.
tags$form(
class = "well", # Adds the same styling as shiny::sidebarPanel
tags$fieldset(
tags$legend("Section 1"),
# Add inputs here
),
tags$fieldset(
tags$legend("Section 2")
# Add inputs here
)
)
For a list of all HTML tags available with definitions and examples, have a look at this Mozilla Web Docs article.