For all the Web Dev veterans the concept of aligning inputs is as old as time and there exits a number of ways to deal with this.

That may be all well and good for those who do it for a living, but those of us who started off in a statistical programming language and found ourselves swamped in HTML, CSS and JavaScript via Shiny, the way to implement the solution isn’t so clear.

I might add the more elegant solutions later, but since I find myself doing the same thing over and over I wanted to at least document the somewhat clunky go-to of markup tables.

Markup Tables

First, I’ll replicate the markup table example provided by the article above:

library("shiny")

ui <- fluidPage(
  tags$head(
    tags$style(
      HTML(
        "
        #actual-table {
          border-collapse: collapse;
        }

        #actual-table td {
          width: 20%;
          padding: 10px;
          vertical-align: top;
        }

        #actual-table td:nth-child(even) {
          background: #ccc;
        }

        #actual-table td:nth-child(odd) {
          background: #eee;
        }
        "
      ) #/ HTML
    ) #/ style
  ) #/ head

  , tags$div(
    tags$table(id = "actual-table"
      , tags$tr(
        tags$td(
          p("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.")
        )
        , tags$td(
          p("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.")
        )
        , tags$td(
          p("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.")
        )
        , tags$td(
          p("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.")
        )
        , tags$td(
          p("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.")
        )
      ) #/ tr
    ) #/ table
  ) #/ div
) #/ fluidPage

server <- function(input, output) {}

shinyApp(ui, server)

all columns have same height as longest column

Easy enough.

Motivating Example

So the problem arrives when you’re trying to construct an intuitive and pleasant UI, so you use your go-to grid layout and come up with:

library("shiny")

ui <- fluidPage(
  wellPanel(class = "col-md-6"
            , div(class = "row"
                  , div(class = "col-md-6"
                      , textInput("first_name", "First Name")
                  ) #/ column 1
                  , div(class = "col-md-6"
                        , actionButton("submit", "Submit")
                  ) #/ column 2
            ) #/ row
  ) #/ wellPanel
) #/ fluidPage

server <- function(input, output) {}

shinyApp(ui, server)

Resulting in:

bad alignment of inputs

No way getting around that, its just too ugly to be allowed.

Solution

We can adopt the markup tables for use in this regard, using the table as the framework for the inputs, adjust as we see fit within the cells, e.g adjusting individual column width and alignment.

A key step is to give the action button the same margins as the text input as we can see they have different classes:

textInput("first_name", "First Name")
# <div class="form-group shiny-input-container">
#   <label for="first_name">First Name</label>
#   <input id="first_name" type="text" class="form-control" value=""/>
# </div>

actionButton("submit", "Submit")
# <button id="submit" type="button" class="btn btn-default action-button">Submit</button>

So we can add the form-group and shiny-input-container classes to the submit button and then force everything to the bottom of the table row:

library("shiny")

ui <- fluidPage(
  tags$head(
    tags$style(
      HTML(
        "
        #inputs-table {
          border-collapse: collapse;
        }

        #inputs-table td {
          padding: 10px;
          vertical-align: bottom;
        }
        "
      ) #/ HTML
    ) #/ style
  ) #/ head

  , wellPanel(class = "col-md-5"
              , style = "margin: 15px 10px 10px 10px"
              , tags$table(id = "inputs-table"
                           , style = "width: 100%"
                           , tags$tr(
                             tags$td(style = "width: 70%"
                               , textInput("first_name", "First Name")
                             ) #/ column 1
                             , tags$td(style = "width: 30%; text-align: right"
                               , div(class = "form-group shiny-input-container"
                                   , actionButton("submit", "Submit", width = "100%")
                                   )
                             ) #/ column 2
                           ) #/ tr
              ) #/ table
  ) #/ wellPanel
) #/ fluidPage

server <- function(input, output) {}

shinyApp(ui, server)

And viola! Something much better than a sharp stick in the eye.

good alignment of inputs