This article outlines various layouts made possible by
layout_column_wrap()
. To illustrate, we’ll use three
card()
instances with varying content, but keep in mind
that layout_column_wrap()
is designed to work other UI
elements as well, such as value boxes or
even for multiple columns within a
card.
Note: The examples in this section are not intended to be viewed on mobile devices. At small window widths, all of the layouts here collapse into a more mobile-friendly approach of “show each card at maximum width”.
library(bslib)
card1 <- card(
card_header("Scrolling content"),
lapply(
lorem::ipsum(paragraphs = 3, sentences = c(5, 5, 5)),
tags$p
)
)
card2 <- card(
card_header("Nothing much here"),
"This is it."
)
card3 <- card(
full_screen = TRUE,
card_header("Filling content"),
card_body(
class = "p-0",
shiny::plotOutput("p")
)
)
Uniform width and height
When displaying multiple cards (or value boxes, etc) at once, it’s
often most visually appealing to have them displayed in a grid-like
layout where each card has the same height and width.
layout_column_wrap()
optimizes for this design principle,
and only demands a width
for each column (or a number of
columns). In the event that there are more cards than columns available,
cards are wrapped into a new row (by default, all rows have the same
height, but you can easily vary the row
height).
Fixed number of columns
For a fixed number of columns, provide width = 1/n
,
where n
is the number of columns.1 As the animation
(except on mobile devices) below shows, as the width of the
layout_column_wrap()
container changes, each card grows or
shrinks to maintain its 1/2 width.
layout_column_wrap(
width = 1/2, height = 300,
card1, card2, card3
) |>
anim_width("100%", "67%")
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
One potential issue with a fixed number of columns is that, on medium sized screens, the card width may become too small. If that happens to be a problem, specifying a “responsive” number of columns may be preferable.
Responsive number of columns
For a responsive number of columns (i.e., the number of columns
depends on the window size), provide width
with any valid
CSS unit, like 200 pixels. In our case (with three cards), the 3rd card
gets wrapped onto a new line when the viewport is less than 600 pixels,
but on wider screens, the cards equally distribute the free space.
layout_column_wrap(
width = "200px", height = 300,
card1, card2, card3
) |>
anim_width("100%", "67%")
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
Fixed column width
To keep the width
of each column fixed (don’t allow
cards to grow to take up free space), set
fixed_width = TRUE
.
layout_column_wrap(
width = "200px", height = 300,
fixed_width = TRUE,
card1, card2, card3
) |>
anim_width("100%", "67%")
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
Varying heights
By default, when layout_column_wrap()
wraps columns onto
a new row, all rows are given equal height.
By row
To allow the height of each row to be different, set
heights_equal = "row"
:
layout_column_wrap(
width = 1/2,
heights_equal = "row",
card1, card3, card2
) |>
anim_height(300, 450)
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
By cell
Since each card is a fill item by default (i.e.,
fill = TRUE
), each card grows/shrinks to fill the available
vertical space in a particular row. This can be prevented by setting
fill = FALSE
on a particular card.
layout_column_wrap(
width = "200px",
card1, card3,
card(fill = FALSE,
card_header("Nothing much here"),
"This is it."
)
) |>
anim_height(300, 450)
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
Varying widths
Set width
to NULL
and provide a custom grid-template-columns
property (and possibly other CSS grid
properties) to accomplish more complex layouts, like varying column
widths. This particular layout gives the 1st and 3rd card twice as much
space as the 2nd:
layout_column_wrap(
width = NULL, height = 300, fill = FALSE,
style = css(grid_template_columns = "2fr 1fr 2fr"),
card1, card2, card3
) |>
anim_height(300, 450)
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
Nested layouts
More complex layouts can be achieved by leveraging the fact that
layout_column_wrap()
can appear within another
layout_column_wrap()
s. For example
layout_column_wrap(
width = 1/2,
height = 300,
card1,
layout_column_wrap(
width = 1,
heights_equal = "row",
card2, card3
)
)
Elit et aliquet, cubilia pellentesque vivamus vehicula dis? Iaculis non senectus viverra hendrerit a varius litora; magna ultricies magna. Egestas dis enim interdum tincidunt dictumst venenatis, taciti scelerisque arcu nisi accumsan dignissim. Inceptos parturient eget netus varius placerat congue fames; tempor diam accumsan hac. Quis fusce augue imperdiet?
Amet imperdiet suscipit, accumsan at fusce et torquent, suspendisse integer ac pharetra! Quis inceptos magna morbi lectus tempor conubia montes, accumsan conubia. Congue semper vulputate, natoque volutpat velit. Aliquam est praesent dis tristique, sociosqu himenaeos lobortis placerat primis posuere habitant nisl parturient. At convallis nam integer justo nostra odio facilisi enim accumsan, pulvinar dis eu.
Consectetur malesuada potenti ornare integer gravida vulputate pharetra convallis. Montes torquent est, leo enim eros commodo. Risus aptent dictumst suscipit magnis rhoncus metus lacus scelerisque tempus iaculis blandit? Scelerisque proin eleifend, nulla, curae inceptos suscipit libero! Phasellus bibendum risus nulla urna sem nascetur pulvinar felis quis vitae taciti vestibulum arcu donec.
Other grid-based layouts
layout_column_wrap()
provides a simplified interface to
CSS
grid that won’t accommodate everything it can do. In this case, we
recommend using {gridlayout}
and/or the Shiny UI editor to
produce the layout.
Appendix
In the spirit of reproducibility, this section discloses custom CSS and R code used in the examples above.
The following CSS is used to give plotOutput()
a
background color; it’s necessary here because this documentation page is
not actually hooked up to a Shiny app, so we can’t show a real plot.
These R functions add animation-related CSS class and styles to whatever tags you give it.
library(htmltools)
anim_width <- function(x, width1, width2) {
x |> tagAppendAttributes(
class = "animate-width",
style = css(
`--width1` = validateCssUnit(width1),
`--width2` = validateCssUnit(width2),
),
)
}
anim_height <- function(x, height1, height2) {
# Wrap in a div fixed at the height of height2, so the rest of
# the content on the page doesn't shift up and down
div(style = css(height = validateCssUnit(height2)),
x |> tagAppendAttributes(
class = "animate-height",
style = css(
`--height1` = validateCssUnit(height1),
`--height2` = validateCssUnit(height2),
),
)
)
}
And here are the CSS animation rules that power those
anim_width
and anim_height
R functions.
@keyframes changewidth {
from { width: var(--width1); }
25% { width: var(--width1); }
50% { width: var(--width2); }
75% { width: var(--width2); }
to { width: var(--width1); }
}
.animate-width {
animation-duration: 6s;
animation-name: changewidth;
animation-iteration-count: infinite;
border-right: 2px solid #DDD;
padding-right: 1rem;
padding-bottom: 3rem;
}
@keyframes changeheight {
from { height: var(--height1); }
25% { height: var(--height1); }
50% { height: var(--height2); }
75% { height: var(--height2); }
to { height: var(--height1); }
}
.animate-height {
height: 600px;
animation-duration: var(--anim-duration, 6s);
animation-name: changeheight;
animation-iteration-count: infinite;
padding-bottom: 3rem;
}