You're almost there. To display a different value to the selection of a selectInput
, you have to name the elements of the choices
vector. The padding doesn't show up inside the dropdown because the default behaviour of HTML is to collapse white space. So you need to tweak the CSS that formats the dropdown. That's what the tags$head(...)
is for.
I've tidied up your derivation of the choice list and added a textOutput
to demonstrate the difference between the items displayed by the selectInput
and its return value.
library(shiny)
library(stringr)
a <- c("Veronica", "Paul", "Elisabeth", "Mike", "Katy", "Tim")
b <- c(50015, 23010, 86812, 55497, 32309, 67631)
data <- data.frame(a, b)
maxWidth <- max(str_length(data[, 1]))
choiceList <- data[, 1]
names(choiceList) <- paste0(str_pad(data[, 1], width=maxWidth, side="right"), "|", data[, 2])
ui <- fluidPage(
tags$head(tags$style(".option {font-family: Monospace; white-space: pre; }")),
selectInput("selectID",
label = "Test Label:",
choices = choiceList
),
textOutput("selection")
)
server <- function(input, output, session) {
output$selection <- renderText({ input$selectID })
}
shinyApp(ui, server)

Note that the derivation of the choices for the selectInput
is static. If you want that to be dynamic, you need to move the code to populate it inside the server
function and wrap it in an observe
or observeEvent
. You'd probably need to make data
reactive as well.
Edit: dealing with proportional fonts
The link in my comment below gives the key to a solution using shinyWidgets
. My guess that using tags$span()
would help was wrong because all the arguments to tags$span()
are taken as the content of the tag rather than as arguments to the tag. So we need to construct the necessary HTML manually.
For convenience, I've added the a variable containing the necessary HTML to the data frame. The rowwise()
is necessary to limit the concatenation to the current row rather than the entire data frame. I assume that the "|" separator is no longer required.
Pick the width of the col1
class to be "long enough" or calculate it on the fly.
library(shiny)
library(shinyWidgets)
library(tidyverse)
a <- c("Veronica", "Paul", "Elisabeth", "Mike", "Katy", "Tim")
b <- c(50015, 23010, 86812, 55497, 32309, 67631)
data <- data.frame(a, b) %>%
rowwise() %>%
mutate(dropdownText=HTML(paste0("<span class='col1'>", a, "</span><span class='col2'>", b, "</span>"))) %>%
ungroup()
ui <- fluidPage(
tags$head(
tags$style(".col1 {min-width: 150px; display: inline-block; }"),
tags$style(".col2 {min-width: auto; display: inline-block; }")
),
pickerInput("selectID",
label = "Test Label:",
choices = data$a,
choicesOpt=list(content=data$dropdownText)
),
textOutput("selection")
)
server <- function(input, output, session) {
output$selection <- renderText({ input$selectID })
}
shinyApp(ui, server)
There are undoubtedly better (ie more precise) ways of specifying the CSS classes and selectors, but I don't know enough CSS to know what they are.

Edit 2
To display only the first column in the selectInput
but both columns in the dropdown, change the tags$head()
to
tags$head(
tags$style(".col1 {min-width: 150px; display: inline-block; }"),
tags$style(".col2 {min-width: auto; display: inline-block; }"),
tags$style(".filter-option-inner-inner .col2 {min-width: auto;
display: inline-block; visibility: hidden; }")
)
The third element overrides the visibility of elements styled with thecol2
style when they are children of elements with the filter-option-inner-inner
style. You can see how various elements of the UI are styled by opening the app in a browser, right-clicking anywhere on the page and selecting "Inspect" or similar.
