Skip to content

Various functions in usethis insert a badge into a package’s README file. A badge consists of an image with information about the package, which is usually hyperlinked to a location with further details. Here is a typical badge:

badge_example

For example, the badge message might be “passing” or “failing”, reflecting the R CMD check status of its current development state, and then link to the actual log file. Or the badge could display the current test coverage, as a percentage of lines, and then link to a site where one can explore test coverage line-by-line.

R-CMD-check Codecov test coverage

A key point is that the badge often presents dynamic information.

We’d like to improve the user experience for those consuming the README using assistive technology, such as a screen reader.

There are at least two ways that usethis can influence this:

  • The construction of the hyperlinked badge. For example, we control the alt text.
  • The image itself. We can encourage the badge providers to build accessibility into their badge SVGs. If the “native” badge is not accessible, usethis can substitute an accessible badge from a 3rd party, such as shields.io.

This article is meant to gather badge facts in one place and facilitate a dialogue with those using assistive technology.

usethis currently inserts badges in a special “badge block” in the README, fenced with HTML comment tags. Within the badge block, each badge is a hyperlinked image, with alt text. Here is the basic form:

[![alt_text](http://path/to/the/badge/image)](http://path/to/more/details)

Note that the alt_text should be a static label explaining the badge’s purpose, whereas the dynamic information is baked into the externally-served image.

usethis definitely controls this alt_text, so that is something I’d like feedback on.

Badge image

Most badges these days tend to be SVG files, which is fortunate, since SVGs can carry attributes to support accessibility. We demonstrate this with a custom badge generated via the shields.io web service.

I will request a badge with this URL:

https://img.shields.io/badge/my__label-my__message-orange

Here it is as a badge, with the hyperlink set to https://shields.io/. The badge is inside an HTML comment block, like usethis does for README badges. I’ve prefixed every field I can influence with “my”; there are 3 such fields:

  1. The alt text.
  2. The label. This static text appears on the left side of the badge, on a grey background, and describes the badge.
  3. The message. This text appears on the right side of the badge, on a colored background. This is often the dynamic part, where both the color and the text convey a dynamic fact, e.g. “passing” on a green background or “failing” on a red background.

my_alt_text

Now we inspect the badge SVG. I apologize in advance for the amount of data displayed here, but it seems good to show one badge in full gory detail. Later, we will display badge information very selectively.

library(xml2)
library(purrr)

inspect_badge <- function(badge) {
  badge %>%
    read_xml() %>%
    as_list() %>%
    pluck("svg")
}

inspect_badge("https://img.shields.io/badge/my__label-my__message-orange")
#> $title
#> $title[[1]]
#> [1] "my_label: my_message"
#> 
#> 
#> $linearGradient
#> $linearGradient$stop
#> list()
#> attr(,"offset")
#> [1] "0"
#> attr(,"stop-color")
#> [1] "#bbb"
#> attr(,"stop-opacity")
#> [1] ".1"
#> 
#> $linearGradient$stop
#> list()
#> attr(,"offset")
#> [1] "1"
#> attr(,"stop-opacity")
#> [1] ".1"
#> 
#> attr(,"id")
#> [1] "s"
#> attr(,"x2")
#> [1] "0"
#> attr(,"y2")
#> [1] "100%"
#> 
#> $clipPath
#> $clipPath$rect
#> list()
#> attr(,"width")
#> [1] "144"
#> attr(,"height")
#> [1] "20"
#> attr(,"rx")
#> [1] "3"
#> attr(,"fill")
#> [1] "#fff"
#> 
#> attr(,"id")
#> [1] "r"
#> 
#> $g
#> $g$rect
#> list()
#> attr(,"width")
#> [1] "61"
#> attr(,"height")
#> [1] "20"
#> attr(,"fill")
#> [1] "#555"
#> 
#> $g$rect
#> list()
#> attr(,"x")
#> [1] "61"
#> attr(,"width")
#> [1] "83"
#> attr(,"height")
#> [1] "20"
#> attr(,"fill")
#> [1] "#fe7d37"
#> 
#> $g$rect
#> list()
#> attr(,"width")
#> [1] "144"
#> attr(,"height")
#> [1] "20"
#> attr(,"fill")
#> [1] "url(#s)"
#> 
#> attr(,"clip-path")
#> [1] "url(#r)"
#> 
#> $g
#> $g$text
#> $g$text[[1]]
#> [1] "my_label"
#> 
#> attr(,"aria-hidden")
#> [1] "true"
#> attr(,"x")
#> [1] "315"
#> attr(,"y")
#> [1] "150"
#> attr(,"fill")
#> [1] "#010101"
#> attr(,"fill-opacity")
#> [1] ".3"
#> attr(,"transform")
#> [1] "scale(.1)"
#> attr(,"textLength")
#> [1] "510"
#> 
#> $g$text
#> $g$text[[1]]
#> [1] "my_label"
#> 
#> attr(,"x")
#> [1] "315"
#> attr(,"y")
#> [1] "140"
#> attr(,"transform")
#> [1] "scale(.1)"
#> attr(,"fill")
#> [1] "#fff"
#> attr(,"textLength")
#> [1] "510"
#> 
#> $g$text
#> $g$text[[1]]
#> [1] "my_message"
#> 
#> attr(,"aria-hidden")
#> [1] "true"
#> attr(,"x")
#> [1] "1015"
#> attr(,"y")
#> [1] "150"
#> attr(,"fill")
#> [1] "#010101"
#> attr(,"fill-opacity")
#> [1] ".3"
#> attr(,"transform")
#> [1] "scale(.1)"
#> attr(,"textLength")
#> [1] "730"
#> 
#> $g$text
#> $g$text[[1]]
#> [1] "my_message"
#> 
#> attr(,"x")
#> [1] "1015"
#> attr(,"y")
#> [1] "140"
#> attr(,"transform")
#> [1] "scale(.1)"
#> attr(,"fill")
#> [1] "#fff"
#> attr(,"textLength")
#> [1] "730"
#> 
#> attr(,"fill")
#> [1] "#fff"
#> attr(,"text-anchor")
#> [1] "middle"
#> attr(,"font-family")
#> [1] "Verdana,Geneva,DejaVu Sans,sans-serif"
#> attr(,"text-rendering")
#> [1] "geometricPrecision"
#> attr(,"font-size")
#> [1] "110"
#> 
#> attr(,"width")
#> [1] "144"
#> attr(,"height")
#> [1] "20"
#> attr(,"role")
#> [1] "img"
#> attr(,"aria-label")
#> [1] "my_label: my_message"
#> attr(,"xmlns")
#> [1] "http://www.w3.org/2000/svg"
#> attr(,"xmlns:xlink")
#> [1] "http://www.w3.org/1999/xlink"

It is my understanding that the two main pieces of metadata are the title and the aria-label attribute. Here I reveal just those two items from the badge:

inspect_badge <- function(badge) {
  x <- badge %>%
    read_xml() %>%
    as_list() %>%
    pluck("svg")
  list(
    title = pluck(x, "title", 1),
    `aria-label` = pluck(x, attr_getter("aria-label"))
  )
}

inspect_badge("https://img.shields.io/badge/my__label-my__message-orange")
#> $title
#> [1] "my_label: my_message"
#> 
#> $`aria-label`
#> [1] "my_label: my_message"

This badge carries the same information in the title and in aria-label, which is “my_label: my_message”. I would be interested to learn more about why the same information is included twice and if that is a good or bad thing for the screen reader experience.

shields.io badges are accessible today

One of the reasons I inspected a shields.io badge is that this may provide a usable alternative for any service whose official badge is not (yet?) screen-reader-friendly.

The custom badge above is completely static. But shields.io also supports custom dynamic badges, when the necessary information (label, message, color) is available from a suitable JSON endpoint. Finally, and most relevant to usethis, shields.io offers pre-configured dynamic badges for the most commonly “badged” services, including GitHub Actions and Codecov.

Here is a shields.io badge for usethis’s R CMD check workflow on GitHub Actions:

my_alt_text-R-CMD-check

Again, I am indicating fields I control with my_alt_text and my_label, so it’s easier to get feedback on what usethis should do.

Here is the title and aria-label of the badge above:

inspect_badge("https://img.shields.io/github/workflow/status/r-lib/usethis/R-CMD-check?label=my_label-R-CMD-check")
#> $title
#> [1] "my_label-R-CMD-check: passing"
#> 
#> $`aria-label`
#> [1] "my_label-R-CMD-check: passing"

Now we will take inventory of the main badges inserted by usethis and where things stand re: accessibility.

R CMD check status

We generally obtain the current R CMD check status from a GitHub Actions workflow.

Here is the official badge provided by GitHub:

R-CMD-check

Here is the title and aria-label of the badge above:

inspect_badge("https://github.com/r-lib/usethis/actions/workflows/R-CMD-check.yaml/badge.svg")
#> $title
#> [1] "R-CMD-check - passing"
#> 
#> $`aria-label`
#> NULL

At the time of writing (late December 2021), the badge does not include such information.

I have requested this in GitHub’s Actions and Packages Feedback forum and the response from GitHub is encouraging. Hopefully the native badge will gain improved accessibility early in 2022.

https://github.com/github/feedback/discussions/8974

In the meantime, one could use a shields.io badge to report R CMD check status, as demonstrated in the previous section. A maintainer could do this as a one-off or, if the GitHub badge upgrade is slow in coming, we could implement it in usethis::use_github_actions_badge().

Package lifecycle

usethis::use_lifecycle_badge() declares the developmental stage of a package, using the framework from https://lifecycle.r-lib.org/articles/stages.html. This function already inserts a shields.io badge:

Lifecycle: stable

Here is the title and aria-label of the badge above:

inspect_badge("https://img.shields.io/badge/lifecycle-stable-brightgreen.svg")
#> $title
#> [1] "lifecycle: stable"
#> 
#> $`aria-label`
#> [1] "lifecycle: stable"

It’s possible that usethis should be using a badge “owned” by the lifecycle package. I think we don’t do so now, because declaring the lifecycle stage of a whole package does not necessarily imply the need to take a formal dependency on the lifecycle package, which is usually what causes badges to be copied into the man/figures/ directory.

Also, the badges shipped by the lifecycle package are not currently accessible, which is another reason not to use them. But I have opened an issue about this (https://github.com/r-lib/lifecycle/issues/117). This should be a relatively easy fix, since these are static badges. Once done, package maintainers would need to update the SVGs that are stored in man/figures/.

CRAN status

usethis::use_cran_badge() places a badge that indicates what package version is available on CRAN. This badge is served by METACRAN (https://www.r-pkg.org) and maintainer Gábor Csárdi has already incorporated aria-label into the badges (in this commit). All available badges are listed here.

Here’s the badge placed by usethis::use_cran_badge():

CRAN status

Here is the title and aria-label of the badge above:

inspect_badge("https://www.r-pkg.org/badges/version/usethis")
#> $title
#> NULL
#> 
#> $`aria-label`
#> [1] "CRAN 2.1.6"

At the time of writing (late December 2021), aria-label is present, but title is not. I would be interested to know if this is a good situation for those using a screen reader.

Code coverage

usethis::use_coverage(type = c("codecov", "coveralls", ...) calls the internal helper usethis:::use_codecov_badge() to insert a badge for either the Codecov or Coveralls service.

Here are examples of those two badges (note that usethis does not use Coveralls, so I’m using a different package to demo):

Codecov test coverage Coveralls test coverage

Here are the title and aria-label of those badges::

inspect_badge("https://codecov.io/gh/r-lib/usethis/branch/main/graph/badge.svg")
#> $title
#> NULL
#> 
#> $`aria-label`
#> NULL

inspect_badge("https://coveralls.io/repos/github/trinker/sentimentr/badge.svg?branch=master")
#> $title
#> NULL
#> 
#> $`aria-label`
#> NULL

At the time of writing (late December 2021), neither badge offers a title or aria-label.

Here are badges from shields.io for Codecov and Coveralls:

Codecov test coverage Coveralls test coverage

Here are the title and aria-label of the shields.io badges::

inspect_badge("https://img.shields.io/codecov/c/github/r-lib/usethis?label=test%20coverage&logo=codecov")
#> $title
#> [1] "test coverage: 46%"
#> 
#> $`aria-label`
#> [1] "test coverage: 46%"

inspect_badge("https://img.shields.io/coveralls/github/trinker/sentimentr?logo=coveralls")
#> $title
#> [1] "coverage: 19%"
#> 
#> $`aria-label`
#> [1] "coverage: 19%"

Bioconductor

If a package is on Bioconductor, usethis::use_bioc_badge() can be used to insert a badge for its Bioconductor build status.

Here is such a badge:

BioC status

Here is the title and aria-label of that badge:

inspect_badge("http://www.bioconductor.org/shields/build/release/bioc/biocthis.svg")
#> $title
#> NULL
#> 
#> $`aria-label`
#> NULL

At the time of writing (late December 2021), the badge does not have a title or aria-label.

It’s not immediately clear how to resolve this, but I suspect that Bioconductor would be receptive to a request to create more accessible badges.