When creating R markdown websites, I often find myself wanting to organise content into a nice-looking grid of links. For example, in a recent project I wanted something like this:

Starting R markdown

An introduction to R markdown. The target audience is a novice R user with no previous experience with markdown.

Starting ggplot2

An introduction to ggplot2. The target audience is a novice user with no previous experience with R or ggplot2.

Starting programming

This is primarily a tutorial on making generative art in R, but in doing so introduces core programming constructs and data structures. It is assumed the user has some previous experience with ggplot2.

The goal of bs4cards is to make this task a little easier inside an R markdown document or website.

Enabling bootstrap 4

The first step in using the package is to make sure that your R markdown document uses version 4 of bootstrap: the bs4cards package takes its name from the cards system introduced in bootstrap version 4, and will not work properly if used in R markdown documents that rely on bootstrap version 3. To ensure that you are using bootstrap 4, you need to edit the YAML header for your document to specify which version of bootstrap you want to use. For example, here is the relevant section of YAML you might use in a simple R markdown site

output:
  html_document:
    theme:
      version: 4

This overrides the R markdown defaults to ensure that the output is built using bootstrap 4.5. To enable bootstrap 4 in a pkgdown site such as this one, edit the _pkgdown.yml file to include the following

template:
  bootstrap: 4

You may need to update to the development version of pkgdown to make this work.

Building bootstrap cards

The main function in bs4cards is the card() function. To create a minimal text-only card, call the card() function specifying the title and text arguments:

library(bs4cards)

card(
  title = "A bootstrap card", 
  text = "This is a minimal bootstrap 4 card containing title and text"
)
A bootstrap card

This is a minimal bootstrap 4 card containing title and text

In most cases you’ll also want to add an image to the card using the image argument. This argument specifies the path to the image file. For the purposes of this example I’ll set image paths using links to placekitten.com:

card(
  title = "A kitten card",
  image = "https://placekitten.com/300/200",
  text = "The image at the top of this card is from placekitten.com. It's very cute"
)
A kitten card

The image at the top of this card is from placekitten.com. It's very cute

Images are placed at the top of the card by default, but can be moved to the bottom by setting image_align = "bottom":

card(
  title = "A kitten card",
  image = "https://placekitten.com/300/200",
  text = "The image at the bottom of this card is from placekitten.com. It's still cute",
  image_align = "bottom"
)
A kitten card

The image at the bottom of this card is from placekitten.com. It's still cute

In most cases you’ll want cards to be associated with a link, which is set by specifying the link argument to card(). This will convert both the card title and image to links:

card(
  title = "Placekittens",
  image = "https://placekitten.com/300/200",
  text = "The image at the top of this card is from placekitten.com. It's very cute",
  link = "https://placekitten.com"
)
Placekittens

The image at the top of this card is from placekitten.com. It's very cute

Building card grids

Cards are usually arranged into grids rather than presented in isolation. The card_grid() function takes a list of cards as input and lays them out on a grid. To illustrate how this works, I’ll use the sample_card() function that generates a random card

A kitten card

Nec vero hoc oratione solum, sed multo magis vita et factis et moribus comprobavit. Graece donan, Latine voluptatem vocant. Qui ita affectus, beatum esse numquam probabis;

and use this to create a list of eight cards:

This list is then passed to card_grid():

card_grid(kitty_list)
A kitten card

Graece donan, Latine voluptatem vocant. Contemnit enim disserendi elegantiam, confuse loquitur. Nobis aliter videtur, recte secusne, postea;

A kitten card

His enim rebus detractis negat se reperire in asotorum vita quod reprehendat. Nescio quo modo praetervolavit oratio. Honesta oratio, Socratica, Platonis etiam.

A kitten card

Sed emolumenta communia esse dicuntur, recte autem facta et peccata non habentur communia. Eodem modo is enim tibi nemo dabit, quod, expetendum sit, id esse laudabile. Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere.

A kitten card

Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere. Itaque hic ipse iam pridem est reiectus; Scaevola tribunus plebis ferret ad plebem vellentne de ea re quaeri.

A kitten card

Si quicquam extra virtutem habeatur in bonis. Nosti, credo, illud: Nemo pius est, qui pietatem-; Omnia contraria, quos etiam insanos esse vultis.

A kitten card

Nam cui proposito sit conservatio sui, necesse est huic partes quoque sui caras suo genere laudabiles. Si alia sentit, inquam, alia loquitur, numquam intellegam quid sentiat; Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus;

A kitten card

Sed venio ad inconstantiae crimen, ne saepius dicas me aberrare; Dolere malum est: in crucem qui agitur, beatus esse non potest. Haec para/doca illi, nos admirabilia dicamus.

A kitten card

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? Quid est, quod ab ea absolvi et perfici debeat?

The cards are arranged into a responsive grid. The images in each card retain their original aspect ratio, but the size of the card is stretched vertically to ensure that all cards within any row have the same height.

Adding headers and footers

The card() function includes header and footer arguments that can be used to add headers and footers to a bootstrap card, as illustrated below:

card(
  title = "A kitten card",
  image = "https://placekitten.com/300/200",
  text = "The image at the top of this card is from placekitten.com. It's very cute",
  header = "Header",
  footer = "Footer"
)
Header
A kitten card

The image at the top of this card is from placekitten.com. It's very cute

When arranged into a grid, headers and footers line up with one another:

kitties_with_footers <- list(
  sample_card(footer = "Kitten A"), 
  sample_card(footer = "Kitten B"), 
  sample_card(footer = "Kitten C")
) 

card_grid(kitties_with_footers)
A kitten card

Contemnit enim disserendi elegantiam, confuse loquitur. Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere. Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii.

A kitten card

Et quod est munus, quod opus sapientiae? Omnia contraria, quos etiam insanos esse vultis. Facillimum id quidem est, inquam. Paria sunt igitur.

A kitten card

Nosti, credo, illud: Nemo pius est, qui pietatem-; Neque solum ea communia, verum etiam paria esse dixerunt. Haec para/doca illi, nos admirabilia dicamus.

kitties_with_headers <- list(
  sample_card(header = "Kitten A"), 
  sample_card(header = "Kitten B"), 
  sample_card(header = "Kitten C")
)

card_grid(kitties_with_headers)
Kitten A
A kitten card

Ergo ita: non posse honeste vivi, nisi honeste vivatur? Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere. Quicquid enim a sapientia proficiscitur, id continuo debet expletum esse omnibus suis partibus;

Kitten B
A kitten card

Graece donan, Latine voluptatem vocant. Equidem e Cn. Duo Reges: constructio interrete. Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus;

Kitten C
A kitten card

Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit? Honesta oratio, Socratica, Platonis etiam. Si alia sentit, inquam, alia loquitur, numquam intellegam quid sentiat;

Setting width

The layout of bootstrap cards on different sized devices is controlled by the grid system. The display is divided into 12 vertical columns, and the bootstrap col CSS classes can be used to specify how many columns are spanned by a card in a responsive way. The bs4cards package makes this functionality available through the bs_col() function. By default, a card generated by bs4cards has width:

bs_col(medium = 4)
#> [1] "col-12 col-md-4"

For devices of medium (or larger) size, the col-md-4 class applies, and card spans 4 of the 12 columns, allowing three cards to be laid out horizontally. For smaller devices, col-12 class is applied, with the consequence that the card spans all 12 columns.

The card() function has a width argument that can be used to modify this default:

card(
  text = "On small devices this card spans 12 columns, on medium devices it spans 6, and on large devices it spans 4 columns",
  width = bs_col(medium = 6, large = 4) 
)

On small devices this card spans 12 columns, on medium devices it spans 6, and on large devices it spans 4 columns

This makes it possible to build card grids that contain cards of different width. On medium or larger devices the right card is twice as wide as the left card. On smaller devices, however, both cards revert to full width and are laid out one above the other:

uneven_cards <- list(
  card(text = "A narrow card", width = bs_col(medium = 4)),
  card(text = "A wide card", width = bs_col(medium = 8))
)

card_grid(uneven_cards)

A narrow card

A wide card

Setting margins and padding

The bs4cards package also allows you to control the margins and padding for cards using the bs_mar() and bs_pad() functions. By default, cards generated by card() do not have margins. Setting margins around cards allows the cards within a grid to be separated from one another:

spaced_cards <- list(
  card(text = "The first card", margin = bs_mar(2)),
  card(text = "The second card", margin = bs_mar(2))
)

card_grid(spaced_cards)

The first card

The second card

The bs_mar() function also allows finer grained control by setting margins separately for each side of the card:

spaced_cards <- list(
  card(text = "The first card", margin = bs_mar(right = 2)),
  card(text = "The second card", margin = bs_mar(top = 5))
)

card_grid(spaced_cards)

The first card

The second card

The bs_pad() function operates similarly. The default for card() applies a moderate amount of padding on all sides (the default is bs_pad(2)). To override this, specify the pad argument as shown below:

padded_cards <- list(
  
  card(
    title = "A kitten card",
    image = "https://placekitten.com/300/200",
    text = "This card applies the default padding",
    margin = bs_mar(2)
  ),
  
  card(
    title = "A kitten card",
    image = "https://placekitten.com/300/200",
    text = "This card has no padding",
    margin = bs_mar(2),
    pad = bs_pad(0)
  )
)

card_grid(padded_cards)
A kitten card

This card applies the default padding

A kitten card

This card has no padding

Additional functionality

The bs4cards package also provides functions that allow finer grain control over the individual components of a card, such as card_title(), card_image(), and card_footer(). The intention with these functions is to allow the user to exercise a high degree of control over the appearance of cards. These are a work in progress :-)