# Table Styling and Formatting

    library(sumExtras)
    library(gtsummary)
    library(dplyr)
    library(gt)

    use_jama_theme()

## Group Headers

`gtsummary::add_variable_group_header()` creates section headers in your table. `{sumExtras}` provides functions to style them.

    trial |>
      select(age, marker, grade, stage, response, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Demographics",
        variables = age
      ) |>
      add_variable_group_header(
        header = "Clinical Measures",
        variables = marker:response
      )

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Demographics |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Clinical Measures |  |  |  |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
Tumor Response | 61 (32%) | 28 (29%) | 33 (34%) | 0.530
Unknown | 7 | 3 | 4 |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

The headers are there, but they don’t stand out. That’s where `add_group_styling()` comes in.

## `add_group_styling()`

Adds bold and/or italic formatting to group headers. Also restores left-justified variable label indentation that `add_variable_group_header()` changes.

#### Without styling

    trial |>
      select(age, marker, grade, stage, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Patient Variables",
        variables = age:stage
      )

#### With add_group_styling()

    trial |>
      select(age, marker, grade, stage, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Patient Variables",
        variables = age:stage
      ) |>
      add_group_styling()

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Patient Variables |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Patient Variables |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

The `format` argument controls the text style:

    # Bold only
    trial |>
      select(age, marker, grade, stage, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Patient Characteristics",
        variables = age:stage
      ) |>
      add_group_styling(format = "bold")

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Patient Characteristics |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

Options are `"bold"`, `"italic"`, or `c("bold", "italic")` (the default).

## `add_group_colors()`

Adds a background color to group header rows. This is a terminal operation in that it converts the table to `{gt}`. It must be the last step in your pipeline.

    trial |>
      select(age, marker, grade, stage, response, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Demographics",
        variables = age
      ) |>
      add_variable_group_header(
        header = "Clinical Measures",
        variables = marker:response
      ) |>
      add_group_styling() |>
      add_group_colors(color = "#E3F2FD")

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Demographics |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Clinical Measures |  |  |  |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
Tumor Response | 61 (32%) | 28 (29%) | 33 (34%) | 0.530
Unknown | 7 | 3 | 4 |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

The default color is `"#E8E8E8"` (light gray). Pass any CSS color string, or a vector of colors (one per group):

    trial |>
      select(age, marker, grade, stage, response, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Demographics",
        variables = age
      ) |>
      add_variable_group_header(
        header = "Clinical Measures",
        variables = marker:response
      ) |>
      add_group_styling() |>
      add_group_colors(color = c("#E3F2FD", "#FFF9E6"))

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Demographics |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Clinical Measures |  |  |  |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
Tumor Response | 61 (32%) | 28 (29%) | 33 (34%) | 0.530
Unknown | 7 | 3 | 4 |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

## `get_group_rows()`

If you need more control than `add_group_colors()` provides, `get_group_rows()` returns the row indices of group headers. You can then use those with `gt::tab_style()` directly:

    my_table <- trial |>
      select(age, marker, grade, stage, trt) |>
      tbl_summary(by = trt) |>
      extras() |>
      add_variable_group_header(
        header = "Demographics",
        variables = age:marker
      ) |>
      add_variable_group_header(
        header = "Disease",
        variables = grade:stage
      ) |>
      add_group_styling()

    group_rows <- get_group_rows(my_table)

    my_table |>
      as_gt() |>
      gt::tab_style(
        style = list(
          gt::cell_fill(color = "#E8E8E8"),
          gt::cell_text(weight = "bold")
        ),
        locations = gt::cells_body(rows = group_rows)
      )

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Demographics |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
Disease |  |  |  |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

## Matching gt Tables with `theme_gt_compact()`

If you mix `{gtsummary}` tables with plain `{gt}` tables in the same document, they won’t match visually. `theme_gt_compact()` applies the same JAMA compact look to `{gt}` tables so everything is consistent:

#### gtsummary with extras()

    trial |>
      tbl_summary(
        by = trt,
        include = c(age, grade, marker)
      ) |>
      extras()

#### gt with theme_gt_compact()

    trial |>
      select(trt, age, grade, marker) |>
      head(10) |>
      gt() |>
      theme_gt_compact()

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Unknown | 11 | 7 | 4 |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
Unknown | 10 | 6 | 4 |
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

Chemotherapy Treatment | Age | Grade | Marker Level (ng/mL)
--- | --- | --- | ---
Drug A | 23 | II | 0.160
Drug B | 9 | I | 1.107
Drug A | 31 | II | 0.277
Drug A | NA | III | 2.067
Drug A | 51 | III | 2.767
Drug B | 39 | I | 0.613
Drug A | 37 | II | 0.354
Drug A | 32 | I | 1.739
Drug A | 31 | II | 0.144
Drug B | 34 | I | 0.205

See `vignette("themes")` for more on theming.

## Complete Example

    dictionary <- tibble::tribble(
      ~variable,    ~description,
      "trt",        "Treatment Assignment",
      "age",        "Age at Baseline (years)",
      "marker",     "Biomarker Level (ng/mL)",
      "stage",      "Clinical Stage",
      "grade",      "Tumor Grade",
      "response",   "Treatment Response",
      "death",      "Patient Died"
    )

    trial |>
      select(trt, age, marker, grade, stage, response, death) |>
      tbl_summary(by = trt, missing = "no") |>
      add_auto_labels(dictionary = dictionary) |>
      extras() |>
      add_variable_group_header(
        header = "BASELINE CHARACTERISTICS",
        variables = age:marker
      ) |>
      add_variable_group_header(
        header = "DISEASE CHARACTERISTICS",
        variables = grade:stage
      ) |>
      add_variable_group_header(
        header = "OUTCOMES",
        variables = response:death
      ) |>
      add_group_styling() |>
      add_group_colors(color = "#E8E8E8")

| Overall N = 200 1 | Drug A N = 98 1 | Drug B N = 102 1 | p-value 2
--- | --- | --- | --- | ---
BASELINE CHARACTERISTICS |  |  |  |
Age | 47 (38, 57) | 46 (37, 60) | 48 (39, 56) | 0.718
Marker Level (ng/mL) | 0.64 (0.22, 1.41) | 0.84 (0.23, 1.60) | 0.52 (0.18, 1.21) | 0.085
DISEASE CHARACTERISTICS |  |  |  |
Grade |  |  |  | 0.871
I | 68 (34%) | 35 (36%) | 33 (32%) |
II | 68 (34%) | 32 (33%) | 36 (35%) |
III | 64 (32%) | 31 (32%) | 33 (32%) |
T Stage |  |  |  | 0.866
T1 | 53 (27%) | 28 (29%) | 25 (25%) |
T2 | 54 (27%) | 25 (26%) | 29 (28%) |
T3 | 43 (22%) | 22 (22%) | 21 (21%) |
T4 | 50 (25%) | 23 (23%) | 27 (26%) |
OUTCOMES |  |  |  |
Tumor Response | 61 (32%) | 28 (29%) | 33 (34%) | 0.530
Patient Died | 112 (56%) | 52 (53%) | 60 (59%) | 0.412
1 Median (Q1, Q3); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test

## More Vignettes

- `vignette("sumExtras-intro")` – getting started with extras()
- `vignette("labeling")` – dictionary-based labeling
- `vignette("themes")` – JAMA compact themes for `{gtsummary}` and `{gt}` tables
- `vignette("options")` – .Rprofile options for automatic labeling