Author: NEST CoreDev Team
Date: 2026-07-30

# teal.picks without teal (standalone Shiny)

#### NEST CoreDev Team

#### 2026-07-30

## Introduction

You can use `picks_ui()` and `picks_srv()` in a plain Shiny app: pass a reactive `teal.data::teal_data()` object to `picks_srv()` and combine results with `merge_srv()` when you need merged analysis data. This mirrors what `tm_merge()` does inside `teal`, without `teal::init()`.

Run the `shinyApp` chunk interactively.

    library(shiny)
    library(teal.data)
    library(teal.picks)

    data <- teal_data()
    data <- within(data, {
      ADSL <- data.frame(
        USUBJID = sprintf("S%03d", 1:8),
        AGE = sample(35:70, 8, replace = TRUE),
        stringsAsFactors = FALSE
      )
      ADLB <- data.frame(
        USUBJID = rep(sprintf("S%03d", 1:8), each = 3),
        PARAM = rep(c("ALT", "AST", "BILI"), 8),
        AVAL = round(rnorm(24, 42, 6), 1),
        stringsAsFactors = FALSE
      )
    })

    join_keys(data) <- join_keys(teal.data::join_key("ADSL", "ADLB", keys = "USUBJID"))

    selector_default <- picks(
      datasets(choices = c("ADSL", "ADLB"), selected = "ADLB"),
      variables(
        choices = tidyselect::everything(),
        selected = c(1L, 2L),
        multiple = TRUE
      )
    )

## Minimal Shiny app

    ui <- fluidPage(
      titlePanel("Standalone picks + merge"),
      fluidRow(
        column(
          width = 4,
          picks_ui("sel", picks = selector_default)
        ),
        column(
          width = 8,
          tags$h4("Mapped variables"),
          verbatimTextOutput("mapped"),
          tags$h4("Merge preview"),
          tableOutput("merged")
        )
      )
    )

    server <- function(input, output, session) {
      data_r <- reactive(data)

      selectors <- list(sel = picks_srv("sel", picks = selector_default, data = data_r))

      merged <- merge_srv(
        id = "merge",
        data = data_r,
        selectors = selectors,
        output_name = "anl",
        join_fun = "dplyr::left_join"
      )

      output$mapped <- renderPrint({
        yaml::as.yaml(merged$variables())
      })

      output$merged <- renderTable({
        merged$data()[["anl"]]
      })
    }

    if (interactive()) {
      shinyApp(ui, server)
    }

## Notes

- `merge_srv()` expects `selectors` to be a named list of reactives (as returned by `picks_srv()` for each selector).
- Define `join_keys()` on your `teal_data` before merging across datasets. One relationship between two datasets is enough: `join_keys(join_key("ADSL", "ADLB", keys = "USUBJID"))` is expanded by `teal.data` into a symmetric map so both names exist. Extra `join_key("DS", "DS", …)` self-keys are optional; they record primary-key / row grain (for example `USUBJID` + `PARAM` on long lab rows), which matters in full CDISC-style setups more than in this minimal example.
- For bookmarking, `picks_srv()` stores resolved picks when `enableBookmarking = "server"` is used on `shinyApp()`.
- `values()` filters the column(s) content chosen in `variables()`. If `multiple = TRUE` variables are selected, values are derived from a combined representation of those columns—so do not pair `PARAM`-only level choices with a selection that also includes `AVAL`. Use `values()` with a single categorical column, or omit `values()` when taking several columns (as in this example).