Viewing Nested Lists with xfun::tabset()

An interactive way to explore complex data structures in R and alternative to str()

Yihui Xie 2026-02-08

Complex nested lists in R can be difficult to explore and understand at a glance. The str() function is helpful for examining structure, but large nested lists can quickly become overwhelming.

While I was writing the documentation for tabsets in litedown, I almost laughed at myself for the support for nested tabsets, because I had no idea why anyone would want this feature. However, I suddenly realized that it can be a very useful tool for exploring nested lists in an interactive way, so I wrote a quick implementation: xfun::tabset().

Introduction

The xfun::tabset() function converts a (potentially nested) list into an interactive tabset representation in Markdown format. The tab titles are derived from the names of list members, and the tab content displays the values of those members. When a list member is itself a list, it is represented recursively with a child tabset, creating a hierarchical, interactive interface.

The hierarchy of tabs allows you to explore complex nested structures without being overwhelmed by a long str() output. Each level of nesting gets its own set of tabs, making it easy to navigate through the data. You only see one tab at a time, and you can click through to explore deeper levels of the structure as needed.

Usage

You can pass any object to xfun::tabset(), e.g., the penguins dataset:

xfun::tabset(penguins)

And it will display the structure of the dataset in a tabbed format like this:

Note that if the object has attributes, they will be displayed in the last tab titled attr(*) (e.g., names and classes).

By default, tabset() uses the str() function to display the structure of bottom-level elements. This gives you a concise summary of what each component contains. However, you can customize this display by passing a function to the second argument, e.g.,

# show the full content of each element instead of the structure
xfun::tabset(penguins, dput)

# show the default print output of each element
xfun::tabset(penguins, print)

The power of tabset() really shines when working with deeply nested structures. Consider a recorded plot object, which is the most complicated data structure I can think of in base R:1

plot(1:10)
p = recordPlot()
xfun::tabset(p)

Generalization

I didn’t spend much time on this function, but it definitely has the potential to be generalized to display richer content in the tabs, such as tables, images, or even interactive visualizations. As a quick example, we can use the following function to display numeric vectors as stem-and-leaf plots and factors as frequency tables:

xfun::tabset(penguins, function(x) {
  if (is.numeric(x)) stem(x) else {
    if (is.factor(x)) c(table(x)) else str(x)
  }
})

I’ll leave this as an exercise for readers who are interested in exploring the possibilities further. If you are curious about how xfun::tabset() is implemented, you can check out its source code, which is actually quite short.2

You may also need to read the documentation on tabsets in litedown to understand the Markdown/HTML structure of tabsets, which is the basis for this function.

Next time when you want to str(), you may consider giving xfun::tabset() a try.


  1. When I met Paul Murrell in 2024, I was surprised that he still looked so young. If I were to invent a data structure like this by myself, my hair would have turned gray long time ago. ↩︎

  2. I love recursion as much as I hate it as much as I love it as… It’s elegant and powerful, but also hard to wrap my head around at the beginning. ↩︎