---
title: "Styling Guide (ksTFL)"
author: "ksTFL Team"
output:
rmarkdown::html_vignette:
fig_width: 6
fig_height: 4
dev: pdf
vignette: >
%\VignetteIndexEntry{Styling Guide (ksTFL)}
%\VignetteEncoding{UTF-8}
%\VignetteEngine{knitr::rmarkdown}
editor_options:
markdown:
wrap: 80
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(
comment = "", collapse = TRUE, eval = FALSE,
dev = "png",
dev.args = if (capabilities("cairo")) list(type = "cairo") else NULL
)
options(pkgdown.internet = FALSE)
```
## Overview
This guide covers the complete styling system in ksTFL:
\- **Style primitives** (`s_*` helpers) for fonts, paragraphs, spacing,
indentation, tables, and borders
\- **Declaring named styles** with `add_style()`
\- **Referencing and combining styles** with style references and
`f_combine()`
\- **Applying styles** to columns, labels, stubs, and content
\- **Best practices** for maintainable, reusable style systems
For runnable reporting examples integrating styles see [Reporting
Examples](Reporting_Examples_with_ksTFL.html). For a quick start see
[Getting Started](Getting_Started_with_ksTFL.html).
## Category and scope
This is the reference vignette for readers building reusable style systems for
teams or studies. The focus is style primitives, composition, references, and
maintainable conventions.
------------------------------------------------------------------------
## Styling philosophy and workflow
### Why declarative styles?
Professional clinical documents require:
- **Consistency**: common elements should render the same way everywhere.
- **Maintainability**: one style change should update every reference.
\- **Composability**: Build complex styles from simple building blocks
(font + paragraph alignment + table background)
\- **Separation of concerns**: Define styles once, apply them many times
without repeating details
ksTFL uses a **named style system**: you declare styles with
`add_style()` giving each an `id`, then reference them by name where
needed. Styles are composable (`f_combine()`) and consolidated
automatically when you build reports.
### Best practices workflow
1. **Define atomic styles first**: Create small, focused styles (bold
headers, right-aligned text, light gray background)
2. **Reference by name**: In `define_cols()`, `add_title()`,
`add_span_header()` (with tidyselect support), use `labelStyleRef`
or `valueStyleRef` to point to styles by id
3. **Combine when needed**: Use `f_combine()` to merge multiple styles
on-the-fly for ad-hoc combinations
4. **Let `create_report()` consolidate**: The report builder
automatically merges combined styles so the renderer receives clean,
consolidated styles
5. **Never inspect internal fields**: Don't manually look at
`spec$attribs$styles` — let the package manage consolidation
------------------------------------------------------------------------
## Style primitives (s\_\* helpers)
All style primitives begin with `s_` and must be used **only inside
`add_style()`**. The helpers validate parameter names and values,
raising informative errors if used incorrectly.
### `s_font()` — Font properties
Control font appearance (name, size, weight, color, decorations).
**Parameters**:
\- `font_name`: Font family (e.g., "Arial", "Courier New", "Times New
Roman", "Georgia")
\- `font_size`: Size with units (e.g., "12pt", "11pt") - `bold`: Logical
(TRUE/FALSE)
\- `italic`: Logical (TRUE/FALSE)
\- `underline`: Logical (TRUE/FALSE)
\- `color`: Color as hex code (e.g., "#000000", "#FF0000") or color name
(e.g., "red", "black", "blue")
\- `highlight`: Background highlight color as hex code or color name
**Example**:
```{r s_font_example, eval = FALSE}
spec <- create_table(mtcars)
# Bold, 12pt Arial, red text (using hex code)
spec <- add_style(spec, id = "header_bold_red",
s_font(font_name = "Arial", font_size = "12pt", bold = TRUE, color = "#CC0000"))
# Underlined, 10pt monospace, blue text (using color name)
spec <- add_style(spec, id = "code_style",
s_font(font_name = "Courier New", font_size = "10pt", underline = TRUE, color = "blue"))
# Yellow highlight with black text
spec <- add_style(spec, id = "highlighted",
s_font(color = "black", highlight = "yellow"))
```
### `s_paragraph()` — Paragraph properties
Control text alignment, spacing before/after, line spacing, and
indentation.
**Parameters**:
\- `alignment`: Text alignment — "left", "right", "center", "justify",
"distributed"
\- `spacing`: Spacing before/after (use `s_spacing(...)`)
\- `indents`: Left/right indentation, first-line indent (use
`s_indents(...)`)
**Example**:
```{r s_paragraph_example, eval = FALSE}
spec <- create_table(mtcars)
# Centered text with 6pt spacing before/after
spec <- add_style(spec, id = "centered_spaced",
s_paragraph(alignment = "center",
spacing = s_spacing(before = "6pt", after = "6pt")))
# Right-aligned with left indent
spec <- add_style(spec, id = "right_indented",
s_paragraph(alignment = "right",
indents = s_indents(left = "10pt")))
```
### `s_spacing()` — Line and paragraph spacing
Controls spacing **before** a paragraph, **after** a paragraph, and
**between lines**.
**Parameters**:
\- `before`: Space before paragraph (e.g., "6pt", "12pt")
\- `after`: Space after paragraph (e.g., "6pt", "12pt")
\- `line_spacing`: Line spacing multiplier (e.g., 1.0, 1.5, 2.0 for
single/1.5-line/double spacing)
**Note**: `s_spacing()` is always used **inside `s_paragraph()`**, never
standalone.
**Example**:
```{r s_spacing_example, eval = FALSE}
spec <- create_table(mtcars)
# Double-spaced with 12pt spacing after each paragraph
spec <- add_style(spec, id = "double_spaced",
s_paragraph(spacing = s_spacing(after = "12pt", line_spacing = 2.0)))
```
### `s_indents()` — Indentation
Controls left/right margins and first-line indentation within a
paragraph.
**Parameters**:
\- `left`: Left indent (e.g., "10pt", "1cm")
\- `right`: Right indent (e.g., "10pt")
\- `first_line`: First-line indent (e.g., "20pt" for hanging indent)
**Note**: `s_indents()` is always used **inside `s_paragraph()`**, never
standalone.
**Example**:
```{r s_indents_example, eval = FALSE}
spec <- create_table(mtcars)
# Hanging indent (first line outdented, rest indented)
spec <- add_style(spec, id = "hanging_indent",
s_paragraph(indents = s_indents(left = "20pt", first_line = "-20pt")))
# Left and right margins with left indent
spec <- add_style(spec, id = "block_indent",
s_paragraph(indents = s_indents(left = "30pt", right = "30pt")))
```
### `s_table_style()` — Table cell properties
Control cell background, row height, vertical alignment, text
orientation, and borders.
**Parameters**:
\- `background_color`: Cell background color (hex, e.g., "#E8E8E8")
\- `row_height`: Height of table row (e.g., "25pt")
\- `topEmptyLine`: Optional empty spacer row after header (e.g., "6pt",
use `NULL` or `0pt` to disable)
\- `bottomEmptyLine`: Optional empty spacer row before the bottom border
(e.g., "6pt", use `NULL` or `0pt` to disable)
\- `vertical_alignment`: "top", "center", "bottom"
\- `text_orientation`: "horizontal", "vertical_90", "vertical_270"
\- `borders`: Border specification (use `s_borders(...)`)
**Example**:
```{r s_table_style_example, eval = FALSE}
spec <- create_table(mtcars)
# Light gray background, centered vertically, fixed row height
spec <- add_style(spec, id = "header_cell",
s_table_style(background_color = "#F2F2F2",
row_height = "30pt",
vertical_alignment = "center"))
# Vertically rotated text (90 degrees)
spec <- add_style(spec, id = "rotated_header",
s_table_style(text_orientation = "vertical_90"))
# Add table-level spacer rows via set_document()
spec <- set_document(
spec,
topEmptyLine = "6pt",
bottomEmptyLine = "6pt"
)
```
### `s_borders()` — Border specifications
Define borders for all four sides of a cell. Each side takes
`s_border()` with line style, width, and color.
**Parameters** (each side):
\- `top`: Top border (use `s_border(...)`)
\- `bottom`: Bottom border (use `s_border(...)`)
\- `left`: Left border (use `s_border(...)`)
\- `right`: Right border (use `s_border(...)`)
**Note**: `s_borders()` can be used **inside `s_table_style()`** for
cell-level borders, or **inside `s_paragraph()`** for paragraph-level
borders.
### `s_border()` — Individual border line
Defines a single border line with style, width, and color.
**Parameters**:
\- `color`: Color as hex code (e.g., "#000000") or color name (e.g.,
"black", "red")
\- `width`: Line width (e.g., "1pt", "2pt", "0.5pt")
\- `line_style`: "single", "double", "dashed", "dotted", "thick", "none"
**Example**:
```{r s_borders_example, eval = FALSE}
spec <- create_table(mtcars)
# All borders: thin single lines in dark gray
spec <- add_style(spec, id = "all_borders",
s_table_style(
borders = s_borders(
top = s_border(color = "grey40", width = "1pt", line_style = "single"),
bottom = s_border(color = "grey40", width = "1pt", line_style = "single"),
left = s_border(color = "grey40", width = "1pt", line_style = "single"),
right = s_border(color = "grey40", width = "1pt", line_style = "single")
)
)
)
# Heavy bottom border in dark color (using hex code)
spec <- add_style(spec, id = "bottom_border_heavy",
s_table_style(
borders = s_borders(
bottom = s_border(color = "#333333", width = "2pt", line_style = "thick")
)
)
)
```
### Paragraph-level borders
MS Word distinguishes between cell borders (``) and
paragraph borders (``). Cell borders always span the full cell
width, while paragraph borders follow the text within the cell. This is
particularly useful for spanning headers where a cell border would
stretch across all merged columns, but a paragraph border only underlines
the header label.
Paragraph borders are set via `s_borders()` inside `s_paragraph()`:
```{r para_borders_example, eval = FALSE}
spec <- create_table(my_data)
# Paragraph bottom border — underlines only the text, not the full cell
spec <- add_style(spec, id = "span_underline",
s_font(bold = TRUE),
s_paragraph(
alignment = "center",
borders = s_borders(
bottom = s_border(color = "#000000", width = "0.5pt", line_style = "single")
)
)
)
# Apply to a spanning header
spec <- add_span_header(spec,
cols = c("trt_a", "trt_b"),
label = "Treatment Arms",
labelStyleRef = "span_underline"
)
# Combine paragraph border atom with other styles
spec <- add_span_header(spec,
cols = c("age", "sex"),
label = "Demographics",
labelStyleRef = f_combine("b", "ac", "pb_th")
)
```
**Key difference**: structural borders (`header_top_border`,
`header_bottom_border`) override cell-level borders on header rows but
cannot affect paragraph borders. This makes paragraph borders useful
when you need visual separators independent of the table's structural
framing.
------------------------------------------------------------------------
## Declaring named styles with `add_style()`
Named styles are the foundation of the ksTFL styling system. Each style
has a unique `id` and contains one or more style primitives.
### Basic syntax
```{r add_style_basic, eval = FALSE}
spec <- add_style(spec, id = "style_name",
s_font(...),
s_paragraph(...),
s_table_style(...)
)
```
**Parameters**:
\- `spec`: A `TFL_spec` object
\- `id`: Unique name for the style (e.g., "header_bold",
"numeric_right")
\- `...`: One or more style primitives (`s_font()`, `s_paragraph()`,
`s_table_style()`, etc.)
### Example: Define a complete style
```{r add_style_complete, eval = FALSE}
spec <- create_table(mtcars)
# Comprehensive header style: bold white text on gray background, centered
spec <- add_style(spec, id = "table_header",
s_font(font_name = "Arial", font_size = "12pt", bold = TRUE, color = "#FFFFFF"),
s_paragraph(alignment = "center", spacing = s_spacing(before = "6pt", after = "6pt")),
s_table_style(background_color = "#333333", row_height = "30pt", vertical_alignment = "center")
)
# Simple numeric style: right-aligned
spec <- add_style(spec, id = "numeric_right",
s_paragraph(alignment = "right")
)
# ID/key style: bold
spec <- add_style(spec, id = "id_bold",
s_font(bold = TRUE)
)
```
### Multiple calls merge with last-win strategy
Calling `add_style()` multiple times with the same `id` merges the
styles (last-win semantics):
```{r add_style_merge, eval = FALSE}
spec <- create_table(mtcars)
# First call: defines font
spec <- add_style(spec, id = "emphasis", s_font(bold = TRUE))
# Second call: adds paragraph alignment; bold is preserved
spec <- add_style(spec, id = "emphasis", s_paragraph(alignment = "center"))
# Result: "emphasis" has both bold font AND center alignment
```
------------------------------------------------------------------------
## Referencing and applying styles
Once you declare styles with `add_style()`, reference them by id in
various places:
### 1. Column labels — `labelStyleRef` in `define_cols()`
Apply styles to **column header labels**:
```{r apply_label_style, eval = FALSE}
spec <- create_table(mtcars)
spec <- add_style(spec, id = "header_bold", s_font(bold = TRUE, font_size = "12pt"))
# Apply to single column
spec <- define_cols(spec, mpg, label = "MPG (miles/gallon)", labelStyleRef = "header_bold")
# Apply to multiple columns with recycling
spec <- define_cols(spec, c(hp, cyl), label = c("HP", "Cylinders"), labelStyleRef = "header_bold")
# Different styles for different columns
spec <- define_cols(spec, c(mpg, hp),
label = c("MPG", "HP"),
labelStyleRef = c("header_bold", "header_italic"))
```
### 2. Column values — `valueStyleRef` in `define_cols()`
Apply styles to **data values** in a column:
```{r apply_value_style, eval = FALSE}
spec <- create_table(mtcars)
spec <- add_style(spec, id = "numeric_right", s_paragraph(alignment = "right"))
# Right-align all numeric values in the mpg column
spec <- define_cols(spec, mpg, type = "numeric", valueStyleRef = "numeric_right")
```
### 3. Spanning headers — `labelStyleRef` in `add_span_header()`
Apply styles to **stub (spanning header) labels**:
```{r apply_stub_style, eval = FALSE}
spec <- create_table(mtcars)
spec <- add_style(spec, id = "stub_label",
s_font(bold = TRUE, font_size = "11pt"),
s_table_style(background_color = "#EFEFEF"))
spec <- add_span_header(spec, cols = c("mpg", "cyl"), label = "Engine",
labelStyleRef = "stub_label")
```
### 4. Content — `styleRef` in `add_title()`, `add_footnote()`
Apply styles to titles, subtitles, footnotes:
```{r apply_content_style, eval = FALSE}
spec <- create_table(mtcars)
spec <- add_style(spec, id = "title_style",
s_font(bold = TRUE, font_size = "14pt"),
s_paragraph(alignment = "center"))
spec <- add_title(spec, "Motor Trends Analysis", styleRef = "title_style")
```
------------------------------------------------------------------------
## Combining styles with `f_combine()`
`f_combine()` lets you apply multiple styles to a single element without
pre-defining a combined style.
### Basic usage
```{r f_combine_basic, eval = FALSE}
spec <- create_table(mtcars)
spec <- add_style(spec, id = "bold", s_font(bold = TRUE))
spec <- add_style(spec, id = "red", s_font(color = "red")) # Using color name
spec <- add_style(spec, id = "centered", s_paragraph(alignment = "center"))
# Apply bold + red + centered to a column header
spec <- define_cols(spec, mpg, label = "MPG",
labelStyleRef = f_combine("bold", "red", "centered"))
```
### When to use `f_combine()` vs named styles
| Use Case | Approach |
| -------------------------------------------- | ------------------------------------------------------- |
| **Reusable style** (used in 3+ places) | Define a named style with `add_style()` |
| **One-off combination** (used once or twice) | Use `f_combine()` inline |
| **Complex style** (many properties) | Define named style, then optionally combine with others |
| **Per-column variations** | Use `f_combine()` with per-column vectors |
### Combining with per-column mapping
Apply different combinations to different columns:
```{r f_combine_per_column, eval = FALSE}
spec <- create_table(mtcars)
# Define base styles
spec <- add_style(spec, id = "bold", s_font(bold = TRUE))
spec <- add_style(spec, id = "italic", s_font(italic = TRUE))
spec <- add_style(spec, id = "centered", s_paragraph(alignment = "center"))
spec <- add_style(spec, id = "right", s_paragraph(alignment = "right"))
# Apply different combinations per column
spec <- define_cols(spec, c(mpg, cyl, hp),
label = c("MPG", "Cylinders", "HP"),
labelStyleRef = c(
f_combine("bold", "centered"), # mpg: bold + centered
f_combine("italic", "right"), # cyl: italic + right
f_combine("bold", "italic", "centered") # hp: bold + italic + centered
)
)
```
------------------------------------------------------------------------
## Allowed values (enumerations)
### Color names
Color parameters accept both **hex codes** (e.g., `"#FF0000"`) and
**named colors**:
**Basic colors**: black, white, red, green, blue, yellow, orange,
purple, pink, brown, cyan, magenta, navy, teal, lime, maroon, olive,
silver, gold, coral, salmon, turquoise, violet, indigo, khaki, lavender,
plum, tan
**Grayscale**: grey10, grey20, grey30, grey40, grey60, grey70, grey80,
grey90 (or gray with 'a')
### Font names (common)
- Arial
- Courier New
- Times New Roman
- Georgia
- Verdana
- Trebuchet MS
### Alignment
- "left"
- "right"
- "center"
- "justify"
- "distributed"
### Border line styles
- "single" (solid line)
- "double" (two lines)
- "dashed" (dashed line)
- "dotted" (dotted line)
- "thick" (thicker solid line)
- "none" (no border)
### Vertical alignment
- "top"
- "center"
- "bottom"
### Text orientation
- "horizontal" (default)
- "vertical_90" (rotated 90° counter-clockwise)
- "vertical_270" (rotated 270° counter-clockwise)
------------------------------------------------------------------------
## Common styling patterns
### Pattern 1: Clinical table headers
```{r pattern_clinical_header, eval = FALSE}
spec <- create_table(my_data)
spec <- add_style(spec, id = "clinical_header",
s_font(font_name = "Arial", font_size = "11pt", bold = TRUE, color = "#FFFFFF"),
s_paragraph(alignment = "center"),
s_table_style(background_color = "#003366", row_height = "28pt", vertical_alignment = "center")
)
spec <- define_cols(spec, c("col1", "col2", "col3"), labelStyleRef = "clinical_header")
```
### Pattern 2: Right-aligned numeric columns
```{r pattern_numeric, eval = FALSE}
spec <- add_style(spec, id = "numeric_format",
s_font(font_name = "Courier New", font_size = "10pt"),
s_paragraph(alignment = "right")
)
spec <- define_cols(spec, c(age, weight, dose), type = "numeric", valueStyleRef = "numeric_format")
```
### Pattern 3: ID/key columns (bold, wide)
```{r pattern_id_column, eval = FALSE}
spec <- add_style(spec, id = "id_column",
s_font(bold = TRUE, font_size = "11pt"),
s_paragraph(alignment = "left")
)
spec <- define_cols(spec, subject_id,
label = "Subject ID",
labelStyleRef = "id_column",
colWidth = "15%",
isID = TRUE)
```
### Pattern 4: Multi-level headers with styled stubs
```{r pattern_multi_stub, eval = FALSE}
spec <- add_style(spec, id = "level1_stub",
s_font(bold = TRUE, font_size = "12pt", color = "#FFFFFF"),
s_table_style(background_color = "#666666", vertical_alignment = "center"))
spec <- add_style(spec, id = "level2_stub",
s_font(bold = TRUE, font_size = "11pt"),
s_table_style(background_color = "#CCCCCC", vertical_alignment = "center"))
spec <- add_span_header(spec, cols = c("var1", "var2", "var3"), label = "Baseline",
stubOrder = 1, labelStyleRef = "level1_stub")
spec <- add_span_header(spec, cols = c("var1", "var2"), label = "Safety",
stubOrder = 2, labelStyleRef = "level2_stub")
```
------------------------------------------------------------------------
## Troubleshooting
### Error: "Context error" or "can only be used inside add_style()"
**Problem**: You used an `s_*` helper outside `add_style()`.
```{r error_context, eval = FALSE}
# WRONG
my_style <- s_font(bold = TRUE) # ERROR
# CORRECT
spec <- add_style(spec, id = "my_style", s_font(bold = TRUE))
```
### Error: "Invalid parameter" or "Allowed values are..."
**Problem**: You used an invalid parameter value (e.g., "bolded" instead
of TRUE).
```{r error_invalid, eval = FALSE}
# WRONG
s_font(bold = "bolded") # ERROR: must be TRUE/FALSE
# CORRECT
s_font(bold = TRUE)
```
### Styles not applied after `create_report()`
**Problem**: If you used `f_combine()`, you must call `create_report()`
before the styles are consolidated.
```{r error_consolidate, eval = FALSE}
spec <- create_table(mtcars)
spec <- define_cols(spec, mpg, labelStyleRef = f_combine("bold", "red"))
# CORRECT: create_report() consolidates combined styles before rendering
report <- create_report(spec) # Merges "bold" + "red" into a single resolved style
write_doc(report, name = "out", outDir = "./output", metaPath = tempdir())
```
### Styles looking different in renderer than expected
**Problem**: Renderer may override or ignore certain style properties
based on its own template system.
**Solution**: Test with simple styles first, then incrementally add
properties. Contact your renderer maintainer for constraints.
------------------------------------------------------------------------
## Advanced notes
### Style consolidation in `create_report()`
When you call `create_report()`, the package:
1\. Collects all specs
2\. For each spec, finds all style references used with `f_combine()`
3\. Merges those combined styles into single consolidated styles
4\. Generates unique hash-based names for consolidated styles
5\. Updates all references to point to the consolidated style
You don't need to inspect or manipulate `spec$attribs$styles` — the
consolidation is automatic and transparent.
### Style reference resolution
Style references are resolved in this order:
1\. **Named styles** defined in the same spec with `add_style()`
2\. **Built-in style atoms** shipped with the package (e.g., `"b"`,
`"tw_80"`, `"grp_hdr"`)
3\. **Error**: If reference not found, `create_report()` will error with
an informative message
------------------------------------------------------------------------
## Built-in style atoms
ksTFL ships a library of single-property style atoms accessible via
`f_combine()` or directly as `styleRef` values. Each atom sets exactly
one visual property; compose them freely with `f_combine()`.
**Discovering atoms programmatically**: Use `tfl_print_style_atoms()`
(or its alias `tfl_style_atoms_catalog()`) to print all available atoms
grouped by category with colour-coded output in the console:
```{r discover_atoms, eval = FALSE}
# Print all built-in atoms categorised and colour-coded
tfl_print_style_atoms()
```
### Complete atom reference
| Atom | Effect |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| **Font — decoration** | |
| `b` / `font_bold` | Bold |
| `i` / `font_italic` | Italic |
| `u` / `font_underline` | Underline |
| **Font — family** | |
| `font_arial` | Set `font_name = "Arial"` |
| `font_courier_new` | Set `font_name = "Courier New"` |
| `font_times_new_roman` | Set `font_name = "Times New Roman"` |
| `font_georgia` | Set `font_name = "Georgia"` |
| `font_verdana` | Set `font_name = "Verdana"` |
| `font_trebuchet_ms` | Set `font_name = "Trebuchet MS"` |
| **Font — size** | |
| `fs_7` … `fs_11` | Font size 7 pt … 11 pt |
| **Font — colour** | |
| `fc_black`, `fc_red`, `fc_blue`, `fc_green` | Pure text colours |
| `fc_gray` / `fc_grey` | Secondary / reference text (#595959) |
| `fc_navy`, `fc_teal`, `fc_olive`, `fc_rust`, `fc_plum`, `fc_slate` | Muted clinical palette |
| **Text highlight (cell shading)** | |
| `hl_yellow`, `hl_red`, `hl_green`, `hl_gray` / `hl_grey` | Strong highlight colours |
| `hl_peach`, `hl_mint`, `hl_sky`, `hl_lemon`, `hl_lilac` | Pastel highlight palette |
| **Paragraph — alignment** | |
| `al` / `text_left` | Left-align |
| `ar` / `text_right` | Right-align |
| `ac` / `text_center` | Center-align |
| **Paragraph — left indentation** | |
| `ind0` / `indent_0` | No indent (reset to left margin) |
| `ind1` / `indent_1` | 0.5 cm left indent (top-level category) |
| `ind2` / `indent_2` | 1.0 cm left indent (first sub-group) |
| `ind3` / `indent_3` | 1.5 cm left indent (second sub-group) |
| `ind4` / `indent_4` | 2.0 cm left indent (detail) |
| **Paragraph — right indentation** | |
| `rind0` / `rindent_0` | No right indent (reset to right margin) |
| `rind1` / `rindent_1` | 0.5 cm right indent |
| `rind2` / `rindent_2` | 1.0 cm right indent |
| `rind3` / `rindent_3` | 1.5 cm right indent |
| `rind4` / `rindent_4` | 2.0 cm right indent |
| **Paragraph — table-width shrink** | |
| `tw_95` … `tw_50` | Symmetric left+right indent to match table at 95 %…50 % width (5 % steps) |
| **Paragraph — spacing** | |
| `sp_0` | No space before/after paragraph |
| `sp_2` | 2 pt space before and after |
| `sp_4` | 4 pt space before and after |
| **Paragraph — pagination** | |
| `kl` | Keep all lines of a cell on the same page |
| `kn` | Keep this row on the same page as the next row |
| **Group / category header composites** | |
| `grp_hdr` | Bold + 4 pt space above + left indent reset (category header) |
| `grp_hdr_i` | Bold + italic + 4 pt space above + left indent reset |
| **Cell — vertical alignment** | |
| `va_t` / `va_top` | Top |
| `va_m` / `va_center` | Middle |
| `va_b` / `va_bottom` | Bottom |
| **Cell — text orientation** | |
| `to_h` / `text_horizontal` | Horizontal (default) |
| `to_90` / `text_vertical_90` | Rotated 90° (bottom-to-top) |
| `to_270` / `text_vertical_270` | Rotated 270° (top-to-bottom) |
| **Cell — background colour** | |
| `bg_blue`, `bg_gray` / `bg_grey` | Standard backgrounds |
| `bg_peach`, `bg_mint`, `bg_sky`, `bg_lemon`, `bg_lilac` | Pastel backgrounds |
| `bg_navy`, `bg_slate`, `bg_steel` | Dark/medium header backgrounds |
| **Row height** | |
| `row_h2`, `row_h4`, `row_h6` | Row height 2 / 4 / 6 pt (separator rows) |
| **Border — sides (1 pt black)** | |
| `bt`, `bb`, `bl`, `br` | Top / bottom / left / right border |
| **Border — thin sides (0.5 pt black)** | |
| `bt_th`, `bb_th` | Thin top / bottom border |
| **Border — colour override** | |
| `bc_gray` / `bc_grey` | All sides → medium gray (#AAAAAA) |
| `bc_white` | All sides → white / none (suppress borders) |
| **Border — thick white sides (column separation)** | |
| `brw_thick` | Right border 4 pt white (visual column gap) |
| `blw_thick` | Left border 4 pt white (visual column gap) |
| **Paragraph border — bottom** | |
| `pb` | Paragraph bottom border 1 pt black |
| `pb_th` | Paragraph bottom border 0.5 pt black (thin) |
**Usage:**
```{r atoms_usage, eval = FALSE}
# Single atom as styleRef
spec <- add_footnote(spec, "Source: database.", styleRef = "tw_80")
# Combine atoms with f_combine()
spec <- define_cols(spec, value,
valueStyleRef = f_combine("font_verdana", "ar", "fs_9"))
# Combine atoms with named styles
spec <- add_style(spec, id = "group_hdr",
s_font(bold = TRUE),
s_paragraph(spacing = s_spacing(before = "4pt")))
spec <- compute_cols(spec, firstOf(group),
c_style(everything(), styleRef = f_combine("group_hdr", "bg_gray")))
```
### Table-width shrink atoms (`tw_*`)
When a table is narrower than the full content width, titles, subtitles,
footnotes, and body text will span the full page width by default —
wider than the table itself. The `tw_*` atoms apply symmetric left and
right paragraph indentation so that text blocks visually align with the
table edges.
Indent values are calculated for **A4 landscape with 0.5 in left/right
page margins** (content width ≈ 27.16 cm). Each 5 % step corresponds to
0.68 cm per side.
| Atom | Table width | Indent each side |
| ------- | ----------- | ---------------- |
| `tw_95` | 95 % | 0.68 cm |
| `tw_90` | 90 % | 1.36 cm |
| `tw_85` | 85 % | 2.04 cm |
| `tw_80` | 80 % | 2.72 cm |
| `tw_75` | 75 % | 3.40 cm |
| `tw_70` | 70 % | 4.07 cm |
| `tw_65` | 65 % | 4.75 cm |
| `tw_60` | 60 % | 5.43 cm |
| `tw_55` | 55 % | 6.11 cm |
| `tw_50` | 50 % | 6.79 cm |
**Usage — match footnotes and titles to a narrower table:**
```{r tw_basic, eval = FALSE}
spec <- create_table(data) |>
set_document(contentWidth = "80%") |>
add_title("Demographics Table", styleRef = "tw_80") |>
add_subtitle("Safety Population", styleRef = "tw_80") |>
add_footnote("Source: study database.", styleRef = "tw_80")
```
**Combine with other atoms** using `f_combine()`:
```{r tw_combine, eval = FALSE}
# Bold title, indented to match a 75 % table
spec <- add_title(spec, "Efficacy Summary",
styleRef = f_combine("b", "tw_75"))
# Small italic footnote, indented to match a 70 % table
spec <- add_footnote(spec, "Values are least-squares means.",
styleRef = f_combine("i", "fs_8", "tw_70"))
```
**Apply via a named style** for reuse across multiple specs:
```{r tw_named, eval = FALSE}
# Define the style on each spec (or use a helper function to apply it)
add_footnote_80 <- function(spec, text) {
spec <- add_style(spec, id = "fn_80",
s_font(font_size = "8pt", italic = TRUE),
s_paragraph(alignment = "left",
indents = s_indents(left = "2.72cm", right = "2.72cm")))
add_footnote(spec, text, styleRef = "fn_80")
}
spec1 <- add_footnote_80(spec1, "a. p < 0.05")
spec2 <- add_footnote_80(spec2, "b. LOCF imputation")
```
> **Note**: The indent values assume the default A4 landscape page with
> 0.5 in margins. If you use a different page size, orientation, or
> margins via `set_page_style()`, calculate your own indents with
> `s_indents(left = ..., right = ...)` inside `add_style()`.
------------------------------------------------------------------------
## See also
- Function documentation: `?add_style`, `?s_font`, `?s_paragraph`,
`?f_combine`
- [Reporting Examples](Reporting_Examples_with_ksTFL.html) — working
examples with styles
- [Getting Started](Getting_Started_with_ksTFL.html) — quick overview