Examples

---
title: "lt vs gt — gsDesign2 examples"
---

This document re-runs a few of the `as_gt()` examples from
[gsDesign2](https://github.com/Merck/gsDesign2)'s documentation, but builds
the tables with `lt` instead of `gt`. The first goal of `lt` is to be a
drop-in lightweight replacement for `gt` in gsDesign2 outputs.

Because `lt()` is an S3 generic, we define methods for gsDesign2's summary
classes. In practice these methods would live in gsDesign2 (or a bridge
package); here they're defined inline for demonstration.

```{r setup, message=FALSE}
library(lt)
library(gsDesign2)

enroll_rate = define_enroll_rate(duration = 18, rate = 20)
fail_rate = define_fail_rate(
  duration = c(4, 100), fail_rate = log(2) / 12,
  dropout_rate = .001, hr = c(1, .6)
)
study_duration = 36
alpha = 0.025
beta = 0.1

lt.fixed_design_summary = function(data, ...) {
  x = lt(as.data.frame(data), ...)
  if (!is.null(attr(data, "title")))
    x = lt_header(x, title = attr(data, "title"))
  if (!is.null(attr(data, "footnote")))
    x = lt_footnote(x, attr(data, "footnote"), "title")
  x
}

lt.gs_design_summary = function(data, ...) {
  x = lt(as.data.frame(data), ...) |>
    lt_group(~ Analysis, sep = TRUE) |>
    lt_spanner(
      label = "Cumulative boundary crossing probability",
      columns = c("Alternate hypothesis", "Null hypothesis")
    ) |>
    lt_format(
      c("Z", "~HR at bound", "Nominal p",
        "Alternate hypothesis", "Null hypothesis"),
      decimals = 4
    )
  x
}

registerS3method("lt", "fixed_design_summary", lt.fixed_design_summary, asNamespace("lt"))
registerS3method("lt", "gs_design_summary", lt.gs_design_summary, asNamespace("lt"))
```

## Fixed design — AHR method

Equivalent of `fixed_design_ahr() |> summary() |> as_gt()`.

```{r fixed-ahr}
fixed_design_ahr(
  alpha = alpha, power = 1 - beta,
  enroll_rate = enroll_rate, fail_rate = fail_rate,
  study_duration = study_duration, ratio = 1
) |> summary() |> lt()
```

## Fixed design — Fleming-Harrington

Equivalent of `fixed_design_fh() |> summary() |> as_gt()`.

```{r fixed-fh}
fixed_design_fh(
  alpha = alpha, power = 1 - beta,
  enroll_rate = enroll_rate, fail_rate = fail_rate,
  study_duration = study_duration, ratio = 1
) |> summary() |> lt()
```

## Group sequential — `gs_power_ahr`

Equivalent of `gs_power_ahr(lpar = ...) |> summary() |> as_gt()`. The long
"Analysis: N Time: ... AHR: ..." label is a natural row-group header.
The `lt.gs_design_summary` method handles grouping, spanner, and
formatting automatically.

```{r gs-power-ahr}
gs_power_ahr(lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.1)) |>
  summary() |>
  lt() |>
  lt_header(
    title = "Bound summary for AHR design",
    subtitle = "AHR approximations of ~HR at bound"
  ) |>
  lt_footnote(
    "Approximate hazard ratio to cross bound.",
    "column", "~HR at bound"
  ) |>
  lt_footnote(
    "One-sided p-value for experimental vs control treatment.",
    "column", "Nominal p"
  )
```

## Group sequential — `gs_design_ahr`

Equivalent of `gs_design_ahr() |> summary() |> as_gt()`.

```{r gs-design-ahr}
gs_design_ahr() |>
  summary() |>
  lt() |>
  lt_header(
    title = "Bound summary for AHR design",
    subtitle = "AHR approximations of ~HR at bound"
  )
```

## What's missing vs `as_gt()`

With `lt()` as an S3 generic, gsDesign2 can ship its own `lt.gs_design_summary`
and `lt.fixed_design_summary` methods (similar to the ones defined above),
making `summary() |> lt()` work out of the box. The mapping from gt to lt is:

| gt                                | lt                                  |
|-----------------------------------|-------------------------------------|
| `gt::gt(groupname_col=, rowname_col=)` | `lt_group(~ col)` (first column auto-becomes stub) |
| `gt::tab_header(title=, subtitle=)`    | `lt_header(title=, subtitle=)` |
| `gt::tab_spanner(label=, columns=)`    | `lt_spanner(label=, columns=)` |
| `gt::tab_footnote(footnote=, locations=cells_*())` | `lt_footnote(text, where, columns)` |
| `gt::tab_source_note(source_note=)`    | `lt_note(text)`                |
| `gt::fmt_number(columns=, decimals=)`  | `lt_format(columns, decimals)`    |
---
title: "lt.js — JavaScript API Reference"
output:
  html:
    meta:
      css: ["@default", "@lt"]
      js: ["@lt"]
---

This document demonstrates the lt.js runtime directly from JavaScript.
Each example shows a JSON spec and renders the table with `LT.build()`.

## Including lt.js

Add the stylesheet and script to your HTML page:

``` html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xiee/utils/css/lt.min.css">
<script src="https://cdn.jsdelivr.net/npm/@xiee/utils/js/lt.min.js"></script>
```

Then call `LT.build(spec)` from an inline `<script>` wherever you want a
table to appear. The runtime renders the table immediately after the
calling script element.

```{r, echo = FALSE}
iris5 = head(iris, 5)
mtcars6 = head(mtcars[, 1:6], 6)
```

```{js, echo = FALSE}
// Shim: lt.js is deferred, so queue calls until it loads
if (!window.LT) window.LT = {
  q: [],
  build(spec) { this.q.push({s: document.currentScript, d: spec}); }
};
```

Define sample datasets as JavaScript objects (interpolated from R via
litedown's `fill` option):

```{js, fill = xfun::tojson}
const mtcars = `{ mtcars6 }`;
const iris = `{ iris5 }`;
```

## Basic table

The simplest spec: just `data` (a column-oriented object).

```{js}
LT.build({ data: mtcars });
```

## Auto-formatting

Numeric columns are automatically formatted: decimal places are capped
dynamically based on magnitude, large numbers get thousand separators
(non-breaking space), and negative values use typographic minus (U+2212).

```{js}
LT.build({
  data: {
    Metric: ["Revenue", "Costs", "Profit", "Loss"],
    Value: [1234567.891, 987654.321, 246913.57, -42.001]
  }
});
```

### Disabling auto-format

Set `auto_fmt: false` to show raw values. Compare with the table above:

```{js}
LT.build({
  data: {
    Metric: ["Revenue", "Costs", "Profit", "Loss"],
    Value: [1234567.891, 987654.321, 246913.57, -42.001]
  },
  auto_fmt: false
});
```

### Percentage detection

Columns whose label matches `/%|[ _](pct|percent)$/i` are auto-detected
as percentages: values are multiplied by 100 and suffixed with "%".

```{js}
LT.build({
  data: {
    Endpoint: ["Primary", "Secondary"],
    success_pct: [0.7823, 0.4512]
  }
});
```

## Title and subtitle

```{js}
LT.build({
  data: iris,
  header: {
    title: "Iris Measurements",
    subtitle: "First five observations"
  }
});
```

## Column labels

Rename column headers without modifying the data keys:

```{js}
LT.build({
  data: iris,
  ops: [
    { type: "label", labels: {
      "Sepal.Length": "Length (cm)", "Sepal.Width": "Width (cm)",
      "Petal.Length": "Length (cm)", "Petal.Width": "Width (cm)"
    }}
  ]
});
```

## Column alignment

By default, numeric columns are right-aligned. Override with an `align` op:

```{js}
LT.build({
  data: mtcars,
  ops: [
    { type: "align", columns: ["mpg", "cyl"], align: "center" }
  ]
});
```

## Number formatting (`fmt_number`)

Explicit control over decimals and thousands separators:

```{js}
LT.build({
  data: {
    Item: ["Widget", "Gadget", "Doohickey"],
    Revenue: [1234567.891, 987654.321, 246913.57],
    Margin: [0.1234, 0.0567, 0.2345]
  },
  ops: [
    { type: "fmt_number", columns: ["Revenue"], decimals: 0, big_mark: "," },
    { type: "fmt_number", columns: ["Margin"], decimals: 2, percent: true }
  ]
});
```

### Percent without multiplication

Use `percent: "%"` when values are already in percent scale (no `*100`):

```{js}
LT.build({
  data: {
    Test: ["A", "B"],
    Rate: [78.5, 42.1]
  },
  ops: [
    { type: "fmt_number", columns: ["Rate"], decimals: 1, percent: "%" }
  ]
});
```

## Value substitution (`sub`)

Replace missing, zero, or small values with display text:

```{js}
LT.build({
  data: {
    Metric: ["HR", "p-value", "Events", "Rate"],
    Value: [0.62, 0.0003, 0, null]
  },
  ops: [
    { type: "fmt_number", columns: ["Value"], decimals: 2 },
    { type: "sub", columns: ["Value"], missing: "n/a", zero: "—", small: 0.001, small_text: "< 0.001" }
  ]
});
```

## Column merging (`merge`)

Combine multiple columns into one display column. The `pattern` uses
`{1}`, `{2}`, etc. for column positions. Wrap sections in `<<` `>>` for
conditional NA handling.

```{js}
LT.build({
  data: {
    Endpoint: ["Primary", "Secondary", "Tertiary"],
    est: [0.61, 0.79, 0.45],
    ci_lo: [0.40, 0.57, null],
    ci_hi: [0.82, 1.01, null]
  },
  ops: [
    { type: "fmt_number", columns: ["est", "ci_lo", "ci_hi"], decimals: 2 },
    { type: "merge", columns: ["est", "ci_lo", "ci_hi"], pattern: "{1}<< ({2}, {3})>>" },
    { type: "label", labels: { est: "Estimate (95% CI)" } }
  ]
});
```

## Column spanners

Group contiguous columns under a shared header:

```{js}
LT.build({
  data: iris,
  spanners: [
    { label: "Sepal", columns: ["Sepal.Length", "Sepal.Width"] },
    { label: "Petal", columns: ["Petal.Length", "Petal.Width"] }
  ]
});
```

### Auto-inferred spanners

Set `auto_span: true` to infer spanners from column names. Names are
split on the first `.` or `_`; contiguous columns sharing a prefix are
grouped, and labels are shortened to the suffix. Pass a custom regex
string (e.g., `"[.]"`) to change the separator.

```{js}
LT.build({ data: iris, auto_span: true });
```

## Row groups (separator rows)

Pass `row_group` as a string to use separator-row style:

```{js}
LT.build({
  data: {
    arm: ["Placebo", "Placebo", "Treatment", "Treatment"],
    stat: ["n", "Mean", "n", "Mean"],
    value: [30, 4.2, 31, 6.8]
  },
  row_group: "arm"
});
```

## Row groups (rowspan)

Pass `row_group` as an array to render as rowspan cells on the left:

```{js}
LT.build({
  data: {
    Region: ["East", "East", "East", "West", "West", "West"],
    State: ["NY", "NY", "MA", "WA", "WA", "OR"],
    City: ["New York", "Buffalo", "Boston", "Seattle", "Spokane", "Portland"],
    Population: [8336817, 278349, 675647, 737015, 228989, 652503]
  },
  row_group: ["Region", "State"]
});
```

## Row group ordering

Reorder separator-row groups with a `group_order` op:

```{js}
LT.build({
  data: {
    arm: ["Placebo", "Placebo", "Treatment", "Treatment"],
    stat: ["n", "Mean", "n", "Mean"],
    value: ["30", "4.2", "31", "6.8"]
  },
  row_group: "arm",
  ops: [
    { type: "group_order", order: ["Treatment", "Placebo"] }
  ]
});
```

## Manual row groups

Define groups explicitly by row index:

```{js}
LT.build({
  data: mtcars,
  ops: [
    { type: "row_group", label: "First three", rows: [1, 2, 3] },
    { type: "row_group", label: "Last three", rows: [4, 5, 6] }
  ]
});
```

## Row indentation

Indent specific rows to show hierarchy:

```{js}
LT.build({
  data: {
    label: ["Any AE", "SOC: Cardiac", "Tachycardia", "Bradycardia", "SOC: GI", "Nausea"],
    n_pct: ["45 (67%)", "30 (45%)", "15 (22%)", "18 (27%)", "20 (30%)", "12 (18%)"]
  },
  ops: [
    { type: "indent", rows: [2, 5], level: 1 },
    { type: "indent", rows: [3, 4, 6], level: 2 }
  ]
});
```

## Column widths

```{js}
LT.build({
  data: mtcars,
  ops: [
    { type: "width", widths: { mpg: "10em", cyl: "5em", disp: "10em", hp: "8em" } }
  ]
});
```

## Column reordering (`move`)

Move columns to the start or after a specific column:

```{js}
LT.build({
  data: iris,
  ops: [
    { type: "move", columns: ["Petal.Length", "Petal.Width"], after: null }
  ]
});
```

## Cell styling

Apply CSS to specific cells by column and/or row:

```{js}
LT.build({
  data: {
    Endpoint: ["Primary", "Secondary", "Exploratory"],
    HR: [0.62, 0.79, 0.91],
    P: [0.001, 0.042, 0.38]
  },
  ops: [
    { type: "fmt_number", columns: ["HR"], decimals: 2 },
    { type: "fmt_number", columns: ["P"], decimals: 3 },
    { type: "style", columns: ["P"], rows: [1, 2], css: "font-weight:bold;color:#06c" },
    { type: "style", columns: ["HR"], rows: [1], css: "background:#e8f4e8;border-bottom:2px solid #4a4" }
  ]
});
```

## Conditional styling

Use `test` with a JavaScript function to apply styles based on cell
values. The function receives the raw (pre-format) value and should
return `true` to apply. Use `class` to assign CSS classes instead of
inline `css`:

```{js}
LT.build({
  data: {
    Endpoint: ["Primary", "Secondary", "Exploratory"],
    HR: [0.62, 0.79, 1.05],
    P: [0.001, 0.042, 0.38]
  },
  ops: [
    { type: "fmt_number", columns: ["HR", "P"], decimals: 3 },
    { type: "style", columns: ["HR"], test: v => v < 1, class: "good" },
    { type: "style", columns: ["P"], test: v => v < 0.05, css: "font-weight:bold" }
  ]
});
```

```{css}
.lt-table .good { color: green; }
```

### Highlighting missing values

Apply a class to all null cells across the table (omit `columns` to
target all):

```{js}
LT.build({
  data: {
    arm: ["Treatment", "Control", "Treatment"],
    n: [30, null, 28],
    response: [0.67, null, 0.71]
  },
  ops: [
    { type: "style", test: v => v == null, class: "na" }
  ]
});
```

```{css}
.lt-table .na { background: #fee; }
```

## Footnotes

Footnotes are de-duplicated by text and numbered automatically. Supported
locations: `title`, `column_labels`, `column_spanners`, `row_groups`, `body`.

```{js}
LT.build({
  data: mtcars,
  header: { title: "Motor Trend Cars" },
  footnotes: [
    { text: "Source: 1974 Motor Trend US magazine.", location: { type: "title", group: "title" } },
    { text: "Miles per US gallon.", location: { type: "column_labels", columns: ["mpg"] } }
  ]
});
```

## Source notes

Notes appear below footnotes in the footer:

```{js}
LT.build({
  data: mtcars,
  notes: ["Data from the 1974 Motor Trend US magazine."]
});
```

## Complete example

Combining multiple features in a single spec:

```{js}
LT.build({
  data: {
    Group: ["Treatment", "Treatment", "Control", "Control"],
    Endpoint: ["Primary", "Secondary", "Primary", "Secondary"],
    Estimate: [0.6123, 0.7891, 0.4567, 0.5432],
    CI_Lower: [0.4012, 0.5678, 0.2345, 0.321],
    CI_Upper: [0.8234, 1.0104, 0.6789, 0.7654],
    P_Value: [0.0012, 0.0456, 0.1234, 0.2345]
  },
  row_group: "Group",
  header: {
    title: "Study Results",
    subtitle: "Primary and secondary endpoints"
  },
  spanners: [
    { label: "95% CI", columns: ["CI_Lower", "CI_Upper"] }
  ],
  ops: [
    { type: "fmt_number", columns: ["Estimate", "CI_Lower", "CI_Upper"], decimals: 3 },
    { type: "fmt_number", columns: ["P_Value"], decimals: 4 },
    { type: "style", columns: ["P_Value"], rows: [1, 3], css: "font-weight:bold" }
  ],
  footnotes: [
    { text: "Two-sided p-value from log-rank test.", location: { type: "column_labels", columns: ["P_Value"] } }
  ]
});
```

Examples (source: lt.Rmd)

---
title: Examples
---

```{r}
library(lt)
```

## A simple table

Pass any data frame to `lt()`:

```{r}
tbl_cars = lt(head(mtcars))
tbl_cars
```

## Title and subtitle

```{r}
tbl_iris = lt(head(iris))
tbl_iris |>
  lt_header(title = "Iris Measurements", subtitle = "First six observations")
```

## Column alignment

By default, numeric columns are right-aligned and character columns are
left-aligned. Override with `lt_align()`:

```{r}
tbl_cars |> lt_align(~ mpg + cyl, "center") |> lt_width(mpg = "6em")
```

## Number formatting

```{r}
tbl_cars |> lt_format(~ mpg + disp, decimals = 1)
```

### Decimal places

Control the number of decimal places with `decimals`:

```{r}
d = data.frame(
  Metric = c("Revenue", "Costs", "Profit"),
  Q1 = c(1234567.891, 987654.321, 246913.570),
  Q2 = c(1345678.912, 1012345.678, 333333.234)
)
lt(d) |>
  lt_format(~ Q1 + Q2, decimals = 2)
```

### Big mark (thousands separator)

```{r}
lt(d) |>
  lt_format(~ Q1 + Q2, decimals = 0, big_mark = ",")
```

## Footnotes

```{r}
tbl_cars |>
  lt_header(title = "Motor Trend Cars") |>
  lt_footnote("Source: 1974 Motor Trend US magazine.", "title") |>
  lt_footnote("Miles per US gallon.", "column", "mpg")
```

## Notes

Notes appear in the footer below numbered footnotes:

```{r}
tbl_cars |> lt_note("Data from the 1974 Motor Trend US magazine.")
```

## Column spanners

A spanner groups contiguous columns under a shared label:

```{r}
tbl_iris |>
  lt_spanner(Sepal ~ Sepal.Length + Sepal.Width) |>
  lt_spanner(Petal ~ Petal.Length + Petal.Width)
```

### Auto-inferred spanners

When column names share a common prefix separated by `.` or `_`, call
`lt_spanner()` with no arguments to infer spanners automatically:

```{r}
tbl_iris |> lt_spanner()
```

### Spanner with formatting

```{r}
tbl_iris |>
  lt_header(title = "Iris Dataset") |>
  lt_spanner(Sepal ~ Sepal.Length + Sepal.Width) |>
  lt_spanner(Petal ~ Petal.Length + Petal.Width) |>
  lt_format(~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, decimals = 1)
```

## Row groups

### Group by column

Pass a column name to `lt_group()` to partition rows by that column's
values. The column is removed from the body and its values become group
headers:

```{r}
d = data.frame(
  Region = c("East", "East", "West", "West", "West"),
  City = c("New York", "Boston", "Seattle", "Portland", "Denver"),
  Population = c(8336817, 675647, 737015, 652503, 715522)
)
lt(d) |>
  lt_group(~ Region) |>
  lt_header(title = "US Cities by Region") |>
  lt_format(~ Population, big_mark = ",")
```

### Row groups with data columns

```{r}
d = data.frame(
  Category = c("Fruits", "Fruits", "Vegetables", "Vegetables"),
  Item = c("Apple", "Banana", "Carrot", "Broccoli"),
  Calories = c(95, 105, 25, 55),
  Fiber_g = c(4.4, 3.1, 2.8, 5.1)
)
lt(d) |>
  lt_group(~ Category) |>
  lt_header(title = "Nutritional Information") |>
  lt_format(~ Fiber_g, decimals = 1)
```

### Group by multiple columns (rowspan)

Pass multiple columns to `lt_group()` to render hierarchical rowspan
cells on the left side of the table:

```{r}
d = data.frame(
  Region = c("East", "East", "East", "West", "West", "West"),
  State = c("NY", "NY", "MA", "WA", "WA", "OR"),
  City = c("New York", "Buffalo", "Boston", "Seattle", "Spokane", "Portland"),
  Population = c(8336817, 278349, 675647, 737015, 228989, 652503)
)
lt(d) |>
  lt_group(~ Region + State) |>
  lt_header(title = "Cities by Region and State") |>
  lt_format(~ Population, big_mark = ",")
```

### Separator-row style

Use `sep = TRUE` to render groups as full-width separator rows instead of
rowspan cells:

```{r}
d = data.frame(
  Region = c("East", "East", "West", "West", "West"),
  City = c("New York", "Boston", "Seattle", "Portland", "Denver"),
  Population = c(8336817, 675647, 737015, 652503, 715522)
)
lt(d) |>
  lt_group(~ Region, sep = TRUE) |>
  lt_header(title = "US Cities by Region") |>
  lt_format(~ Population, big_mark = ",")
```

### Manual row groups

Use named arguments in `lt_group()` for explicit control over which rows
belong to each group:

```{r}
tbl_cars |> lt_group("First three" = 1:3, "Last three" = 4:6)
```

## Column merging

Combine columns into one using a pattern. Source columns are hidden
automatically:

```{r}
d = data.frame(
  stat = c("Age", "Weight", "Height"),
  n = c(67, 65, 64),
  pct = c(100, 97.0, 95.5)
)
lt(d) |>
  lt_format(~ pct, decimals = 1) |>
  lt_merge(~ n + pct, pattern = "{1} ({2}%)") |>
  lt_label(n = "n (%)")
```

### Conditional merge with `<< >>`

Wrap pattern sections in `<<` and `>>` to drop them when any referenced
column is empty/NA:

```{r}
d = data.frame(
  endpoint = c("Primary", "Secondary", "Tertiary"),
  est = c(0.61, 0.79, 0.45),
  ci_lo = c(0.40, 0.57, NA),
  ci_hi = c(0.82, 1.01, NA)
)
lt(d) |>
  lt_format(~ est + ci_lo + ci_hi, decimals = 2) |>
  lt_merge(~ est + ci_lo + ci_hi, pattern = "{1}<< ({2}, {3})>>") |>
  lt_label(est = "Estimate (95% CI)")
```

## Row indentation

Indent the first column to show hierarchy:

```{r}
d = data.frame(
  label = c("Any AE", "SOC: Cardiac", "Tachycardia", "Bradycardia",
            "SOC: GI", "Nausea"),
  n_pct = c("45 (67%)", "30 (45%)", "15 (22%)", "18 (27%)", "20 (30%)", "12 (18%)")
)
lt(d) |>
  lt_header("Adverse Events", "Safety Population") |>
  lt_indent(c(2, 5), level = 1) |>
  lt_indent(c(3, 4, 6), level = 2)
```

## Substituting missing/small values

```{r}
d = data.frame(
  Metric = c("HR", "p-value", "Events", "Rate"),
  Value = c(0.62, 0.0003, 0, NA)
)
lt(d) |>
  lt_format(~ Value, decimals = 2) |>
  lt_sub(~ Value, missing = "n/a", zero = "—", small = 0.001, small_text = "< 0.001")
```

## Cell styling

Highlight specific cells with bold, color, background, or any CSS
property (camelCase or dash-case):

```{r}
d = data.frame(
  Endpoint = c("Primary", "Secondary", "Exploratory"),
  HR = c(0.62, 0.79, 0.91),
  P = c(0.001, 0.042, 0.38)
)
lt(d) |>
  lt_format(~ HR, decimals = 2) |>
  lt_format(~ P, decimals = 3) |>
  lt_style("P", rows = 1:2L, bold = TRUE, color = "#06c") |>
  lt_style("HR", rows = 1L, bg = "#e8f4e8", borderBottom = "2px solid #4a4")
```

## Conditional styling

Apply styles based on cell values using a JavaScript test function. Use
`class` to assign CSS classes, then define the rules with `lt_css()`:

```{r}
d = data.frame(
  Endpoint = c("Primary", "Secondary", "Exploratory"),
  HR = c(0.62, 0.79, 1.05),
  P = c(0.001, 0.042, 0.38)
)
lt(d) |>
  lt_format(~ HR + P, decimals = 3) |>
  lt_style("HR", test = "v => v < 1", class = "good") |>
  lt_style("P", test = "v => v < 0.05", class = "sig") |>
  lt_css(.good = list(color = "green"), .sig = list(fontWeight = "bold"))
```

### Highlighting NA cells

```{r}
d = data.frame(
  arm = c("Treatment", "Control", "Treatment"),
  n = c(30, NA, 28),
  response = c(0.67, NA, NA)
)
lt(d) |>
  lt_style(test = "v => v == null", class = "na") |>
  lt_css(.na = list(background = "#fee"))
```

## Column widths

```{r}
tbl_cars |> lt_width(mpg = "8em", cyl = "5em", disp = "8em", hp = "6em")
```

## Column reordering

```{r}
tbl_iris |> lt_move(~ Petal.Length + Petal.Width, after = NULL)
```

## Row group ordering

```{r}
d = data.frame(
  arm = c("Placebo", "Placebo", "Treatment", "Treatment"),
  stat = c("n", "Mean", "n", "Mean"),
  value = c("30", "4.2", "31", "6.8")
)
lt(d) |>
  lt_group(~ arm, sep = TRUE) |>
  lt_group("Treatment", "Placebo")
```

## Combining formatting and structure

```{r}
d = data.frame(
  Group = c("Treatment", "Treatment", "Control", "Control"),
  Endpoint = c("Primary", "Secondary", "Primary", "Secondary"),
  Estimate = c(0.6123, 0.7891, 0.4567, 0.5432),
  CI_Lower = c(0.4012, 0.5678, 0.2345, 0.3210),
  CI_Upper = c(0.8234, 1.0104, 0.6789, 0.7654),
  P_Value = c(0.0012, 0.0456, 0.1234, 0.2345)
)
lt(d) |>
  lt_group(~ Group) |>
  lt_header(
    title = "Study Results",
    subtitle = "Primary and secondary endpoints"
  ) |>
  lt_spanner(`95% CI` ~ CI_Lower + CI_Upper) |>
  lt_format(~ Estimate + CI_Lower + CI_Upper, decimals = 3) |>
  lt_format(~ P_Value, decimals = 4) |>
  lt_footnote("Two-sided p-value from log-rank test.", "column", "P_Value")
```