Behind the scene, for HTML tables, kableExtra uses xml2 to add styles and new contents to existing table. kableExtra is trying its best to get you the result in a literal way but in some rare case, I hope you can have the ability to freedom to be a HTML Ninja (quoting @yihui’s xaringan). :)

In kableExtra 1.0, I exported two functions that were previously used internally in this package: kable_as_xml and xml_as_kable. Here is an example for how to use these 2 function and xml2 to hack your table and do something kableExtra is not capable to do (at least right now).


demo <- kable(mtcars[1:4, 1:4]) %>%  # As always, mtcars ;)
  kable_styling(full_width = F) %>%

demo %>%                     # Transform to xml
  xml_child(2) %>%           # Select <tbody> (1 is <thead>)
  xml_child(2) %>%           # Select 2nd row <tr> in <tbody>
  xml_child(1) %>%           # Select 1st cell in 2nd row <td>
  xml_set_attr("class", "badIdea")   # Add attribute to style

xml_as_kable(demo)             # Render that xml back as kable
mpg cyl disp hp
Mazda RX4 21.0 6 160 110
Mazda RX4 Wag 21.0 6 160 110
Datsun 710 22.8 4 108 93
Hornet 4 Drive 21.4 6 258 110

Only for illustration. Animating your texts like this is a horrible idea and will distract audience’s attention.
I mean seriously, what I want to show here is xml2

For those %>% fans, keep in mind that since xml2 makes modification to an external xml object. Although it looks like you can put everything in one pipe, the reality is that you will have to save the entire xml first, run the xml2 code and then get it rendered.

Note: Here is my simple rotation css. You need to put them in rmarkdown to get things work.

@-webkit-keyframes rotate {
  0%   {transform: rotate(0deg)}
  100% {transform: rotate(360deg)}

.badIdea {
  animation: rotate 3s linear infinite;