css styling for select and numeric Input - css

I am using R shiny and i am trying to set the same heigth for selectInput and numericInput in my ui.
I found this line:
tags$style(type="text/css", ".selectize-input {line-height: 20px;}"),
which changes the heigth for all my selectInputs.
Is there any similar line to numericInput?
Thanks

Well, it does not have a specific class such as selectInput. But you can still get the same result using the more general class form-control. We need to use height instead of line-height though, which will work fine for selectInput anyway.
ui <- fluidPage(
tags$style(type = "text/css", ".form-control.shiny-bound-input,
.selectize-input {height: 70px;}"),
selectInput(inputId = "another_id",
label = "bar",
choices = c(1, 2)),
numericInput(inputId = "some_id",
label = "foo",
value = 1)
)
server <- function(input, output, session) {}
shinyApp(ui, server)
EDIT:
In case you want to apply some CSS to a specific input, you need to use its id, so the style does not apply to all elements sharing the same class. In the example above, if you only want to change the height of the numericInput whose id is some_id, you would use #some_id to select this element, leading to the following code:
tags$style(type = "text/css",
"#some_id.form-control.shiny-bound-input {height: 70px;}")

Related

How to control padding for value boxes created with bslib

I thought I could set a class for the value box to remove padding like this:
bslib::value_box("test", "test", class = "p-0")
Or in a minimal Shiny app:
library(shiny)
ui <- fluidPage(
theme = bslib::bs_theme(version = 5),
bslib::value_box("test", "test", class = "p-0")
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
But if I do that, it removes only the padding for the bootstrap card:
I want to remove all the blue padding around the content, including the bootstrap card body.
The problem appears to be that bslib::value_box() function wraps content with bslib::card_body_fill(), and it doesn't look like there is a way to pass arguments through to card_body_fill().
I can go into developer tools and search for "padding" and accomplish this:
Or I can add some CSS:
.card-body {
padding: 0;
}
But this will change the padding for all elements with class card_body. What if I want to target only a few value boxes?
Add a class (suppose nopad) to value_boxes for which you want to remove the padding of the content. Using developer tools, you would see that value_box contents are wrapped within a div with class .value-box-area. So Change the padding for css selector div.nopad .value-box-area.
library(shiny)
ui <- fluidPage(
theme = bslib::bs_theme(version = 5),
# css styles -------------------------------------------
tags$head(
# Note the wrapping of the string in HTML()
tags$style(HTML("
div.nopad .value-box-area {
padding: 0;
}
"))
),
# ------------------------------------------------------
bslib::value_box("test", "test", class = "p-0 nopad"), # with class nopad
bslib::value_box("test", "test", class = "p-0")
)
server <- function(input, output, session) {
}
shinyApp(ui, server)

How to fine-tune the positioning of objects rendered in r shiny?

I'm trying to adjust the positioning of conditionally-rendered objects in R shiny. When running the below skeleton code and clicking the "Delete" action button, I'd like to nudge the conditionally-rendered text ("Select series to delete >>") a bit to the right, and move the little selectInput() box that also conditionally appears on the far right a bit to the left, closer to "Select series to delete >>". I've fiddled with column widths, etc., and I've exhausted all the formatting options which I know of which are limited. Any suggestions for fine-tuning the positioning of these items? My guess is this would entail some CSS which I know almost nothing about.
Skeleton code:
library(dplyr)
library(shiny)
library(shinyjs)
toggleView <- function(input, output_name){
observeEvent(input$delSeries, {show(output_name)})
observeEvent(input$addSeries, {hide(output_name)})
}
ui <- fluidPage(br(),
useShinyjs(),
fluidRow(
column(1,actionButton("addSeries", "Add",width = '70px')),
column(1,actionButton("delSeries","Delete",width = '70px')),
column(3,h5((hidden((textOutput("delFlag")))))),
column(3,hidden(uiOutput("delSeries2")))
)
)
server <- function(input, output, session) {
output$delFlag <- renderText("Select series to delete >>")
output$delSeries2 <-
renderUI(
selectInput("delSeries3",
label = NULL,
choices = c(""),
selected = "",
width = '110px')
)
toggleView(input,"delSeries2")
toggleView(input,"delFlag")
}
shinyApp(ui,server)
You can add some styles to the 2 columns like so:
library(dplyr)
library(shiny)
library(shinyjs)
toggleView <- function(input, output_name){
observeEvent(input$delSeries, {hide(output_name)})
observeEvent(input$addSeries, {show(output_name)})
}
# (0)
css <- HTML("
.row .nudge-right {
padding-right:0;
}
.row .nudge-left {
padding-left:0;
}
")
ui <- fluidPage(
tags$head(tags$style(css)), # (1)
br(),
useShinyjs(),
fluidRow(
column(1,actionButton("addSeries", "Add",width = '70px')),
column(1,actionButton("delSeries","Delete",width = '70px')),
column(3,h5(hidden(textOutput("delFlag"))),
class = c("nudge-right", "text-right")), # (2)
column(3,hidden(uiOutput("delSeries2")), class = "nudge-left") # (2)
)
)
Explanation
The white space you see is partly due to the width of the column and partly due to the so called padding (an additional white space around the element). To bridge this gap you can:
Right align the text. Here you can rely on the already pre-defined (by the underlying bootstrap framework) class text-right.
Further decrease the gap by removing the right padding from the text column and the left padding from the input column. In order to so, you define new classes (I called them .nudge-right and .nudge-left respectively) where you deliberately set the padding to your liking (here I removed it completely, you may want to provide a small offset though - e.g. 5px).
Then all which is left is to
Create some css with the class definitions (#0)
Load the css (#1)
Assign the classes to the columns (#2)

Can I name or give an ID to a wellPanel in Shiny?

I have a Shiny app which has two types of well panels depending on what is contained within them, and I would like to set the background of one type to light green, and the other type to light blue. At the moment I do this by setting:
tags$head(tags$style(type = 'text/css',".well{background-color: #EFF8CD;}))
wellPanel("My first type of well panel")
which gives me the green background for all panels,
and then using
wellPanel(style = "background-color:#c9d7e8;")
for the second type of panel.
Is it possible to give these different well panels a name or ID so that I can set the colour in a central place?
You can pass named parameters to wellPanel, in your case it is better to use a class I think :
library(shiny)
ui <- fluidPage(
tags$head(
tags$style(type = 'text/css',".myclass1 {background-color: #EFF8CD;}"),
tags$style(type = 'text/css',".myclass2 {background-color: #c9d7e8;}")
),
wellPanel("My first type of well panel", class = "myclass1", id = "myid1"),
wellPanel("My second type of well panel", class = "myclass2", id = "myid2")
)
server <- function(input, output, session) {
}
shinyApp(ui, server)

R Shiny multiple Inputs in a row [duplicate]

I've got a shiny server version 0.4.0 and I want to have 4 small textInput boxes to look like this:
x-min x-max y-min y-max
[...] [...] [...] [...]
They now look like this:
x-min
[...................]
x-max
[...................]
y-min
[...................]
y-max
[...................]
With this code:
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5),
textInput(inputId="ylimitsmin", label="y-min", value = 0.5),
textInput(inputId="ylimitsmax", label="y-max", value = 1.0),
Any ideas how to achieve this?
EDITED: I've successfully changed things like this elsewhere in the code:
<style type="text/css">select#yaxis4 { height: 280px; width: 500px; }</style>
[... which links to this later on in the page...]
<label class="control-label" for="yaxis4">Y-Axis</label>
<select id="yaxis4" multiple="multiple">
And this is what it looks like for the ones that don't work:
<style type="text/css">select#xlimitsmax { display: inline-block; max-width: 50px; }</style>
[... which links to...]
<label>x-max</label>
<input id="xlimitsmax" type="text" value="0.5"/>
EDITED:
Here is a self contained example ui.R that doesn't work:
library(shiny)
shinyUI(
pageWithSidebar(
# application title
headerPanel("test01"),
sidebarPanel(
tags$head(
tags$style(type="text/css", "select { max-width: 360px; }"),
tags$style(type="text/css", ".span4 { max-width: 360px; }"),
tags$style(type="text/css", ".well { max-width: 360px; }")
),
wellPanel(
p(strong("Side Panel:"))
)
),
mainPanel(
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
tags$head(tags$style(type="text/css", "select#xlimitsmin { max-width: 50px }")),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5),
tags$head(tags$style(type="text/css", "select#xlimitsmax { display: inline-block; max-width: 50px; }"))
)
))
Resulting page:
to paraphrase (and to simplify to the case of 2 inputs), your problem is that:
runApp(list(
ui = bootstrapPage(
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5)
),
server = function(input, output) {}
))
shows
But you want side-by-side small inputs, like so:
The short answer
textInputRow<-function (inputId, label, value = "")
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,class="input-small"))
}
runApp(list(
ui = bootstrapPage(
textInputRow(inputId="xlimitsmin", label="x-min", value = 0.0),
textInputRow(inputId="xlimitsmax", label="x-max", value = 0.5)
),
server = function(input, output) {}
))
gives:
The long answer
Side-by-side inputs
Let's do side-by-side first:
Currently textInput generates two separate tags - the label, and the input, each of which is configured by CSS as display:block, which means it's a rectangle that will break to the left side of the container. We need to wrap each textInput's field in new container (div) and tell that container that the container that follows it (the next textInput) is allowed to be on the same horizontal row on the page, using CSS's display:inline-block.
So we add a div with a style around each textInput:
runApp(list(
ui = bootstrapPage(
div(style="display:inline-block",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
div(style="display:inline-block",textInput(inputId="xlimitsmax", label="x-max", value = 0.5))
),
server = function(input, output) {}
))
Small inputs
Now let's deal with small. There are a few ways to do small,
make the font smaller,
make the input box have fewer characters in it.
tell css or (here) bootstrap to draw a smaller box
Since bootstrap.js is really in control of layout when we use shiny, only 3 will reliably work, so let's use that.
Input sizes are documented in Bootstrap 2.3.2's CSS Forms documentation, under 'Control Sizing'. It includes a variety of sizes from mini, small, medium, large, xlarge, and xxlarge, and the default is probably medium. Let's try small, instead.
To set the size, we need to change the class of the input tag generated by textInput.
Now textInput is just a convenience function around the more powerful tags functions such as tags$label and tags$input. We can build a more powerful version of textInput that allows us to configure the elements, specifically the class of the input node:
textInput2<-function (inputId, label, value = "",...)
{
tagList(tags$label(label, `for` = inputId), tags$input(id = inputId,
type = "text", value = value,...))
}
runApp(list(
ui = bootstrapPage(
div(style="display:inline-block",textInput2(inputId="xlimitsmin", label="x-min", value = 0.0, class="input-small")),
div(style="display:inline-block",textInput2(inputId="xlimitsmax", label="x-max", value = 0.5, class="input-small"))
),
server = function(input, output) {}
))
And we are done - but we can roll some of this functionality up by having textInput3 generate the div tag. It could also set the class by itself, but I'll leave that for you to write.
Wrapping it up
textInput3<-function (inputId, label, value = "",...)
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,...))
}
runApp(list(
ui = bootstrapPage(
textInput3(inputId="xlimitsmin", label="x-min", value = 0.0, class="input-small"),
textInput3(inputId="xlimitsmax", label="x-max", value = 0.5, class="input-small")
),
server = function(input, output) {}
))
For interest's sake, here's the version using class input-mini:
Using Shiny (>= 0.11), you can accomplish this by putting the input calls within a splitLayout(). This will split the fluid row, box, etc. into the necessary columns required to show your input fields side-by-side.
The example below would give you three text inputs in a box, that will appear side-by-side in the fluidRow.
fluidRow(
box(width = 12, title = "A Box in a Fluid Row I want to Split",
splitLayout(
textInput("inputA", "The first input"),
textInput("inputB", "The second input"),
textInput("inputC", "The third input")
)
)
)
Maybe this solution wasn't around in 2013 but if you want to do this without writing HTML or CSS you can just use the column function within a fluidRow like so:
fluidRow(
column(3,
selectInput('pcat', 'Primary Category', c("ALL", "Some"))),
column(3,
selectInput('smodel', 'Statistical Model', c("NONE", "LINEAR REGRESSION", "LOWESS")))
)
And it will place things side by side.
EDIT: Now there is another very easy way to do this using the splitLayout() function. See Nadir Sidi's answer for more details.
I deleted the old answer - here is one that works:
ui.r:
library(shiny)
shinyUI(
pageWithSidebar(
# application title
headerPanel("test01"),
sidebarPanel(
tags$head(
tags$style(type="text/css", "select { max-width: 360px; }"),
tags$style(type="text/css", ".span4 { max-width: 360px; }"),
tags$style(type="text/css", ".well { max-width: 360px; }")
),
wellPanel(
p(strong("Side Panel:"))
)
),
mainPanel(
div(id="XXmin",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
tags$head(tags$style(type="text/css", "#XXmin {display: inline-block}")),
tags$head(tags$style(type="text/css", "#xlimitsmin {max-width: 50px}")),
div(id="XXmax",textInput(inputId="xlimitsmax", label="x-max", value = 0.5)),
tags$head(tags$style(type="text/css", "#XXmax {display: inline-block}"),
tags$head(tags$style(type="text/css", "#xlimitsmax {max-width: 50px}"))
))
))
Here are the changes I made:
1) I eliminated the select from select#xlimitsmax and select#xlimitsmin in your .css statements
2) I put your two controls each in their own div() and gave them the names XXmin and XXmax. I then added .css statements to make them inline-block.
If you have a bunch of these you might want to use a class statement - such as:
div(class="MyClass",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
tags$head(tags$style(type="text/css", ".MyClass {display: inline-block}")),
tags$head(tags$style(type="text/css", "#xlimitsmin {max-width: 50px}")),
then you can tag each of the control div()'s as class="MyClass" and use only one .css statement.
Edited to add: Thanks for posting the example code - that made it much easier.
2nd Edit: Just to clarify. The point of putting the textInput commands inside a div() is to unite the input box and its label into a single object so that styles (in this case the display style) can be applied. If you don't do this, the label and the box act as two separate entities and it is harder to manipulate them in cases like this.
As an alternative to putting verbose style declarations in a class, it seems you can easily extend the shiny tags functions to your liking. This particular one would be convenient to have around by default.
(this is with shiny shiny_0.14.1 ). Thought I was going to need to write a closure but this seems to work.
inline = function (x) {
tags$div(style="display:inline-block;", x)
}
inline(textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
inline(textInput(inputId="xlimitsmax", label="x-max", value = 0.5)),
inline(textInput(inputId="ylimitsmin", label="y-min", value = 0.5)),
inline(textInput(inputId="ylimitsmax", label="y-max", value = 1.0)),
I wasn't happy with splitLayout() because it introduces scroll bars when space is limited.
I found that, at least for input widgets like buttons or text boxes, a quite easy solution with better responsive behaviour is using flex-box: (see this great guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
div(
style = "display: flex; flex-wrap: wrap;",
div(
style = "flex: 1;",
textInput("inputA", "The first input")
),
div(
style = "flex: 1;",
textInput("inputB", "The second input")
),
div(
style = "flex: 1;",
textInput("inputC", "The third input")
)
)
It is possible to adjust relative widths. Corresponding to splitLayout(cellWidths = c("25%", "75%"), ...):
div(
style = "display: flex; flex-wrap: wrap;",
div(
style = "flex: 1;",
textInput("inputA", "The first input")
),
div(
style = "flex: 3;", # second item 3 times as wide as first one
textInput("inputB", "The second input")
)
)
If you want the inputs in mainPanel you can use the following:
div(class="row-fluid",
div(class="span1",textInput("xlimitsmin", label = "x-min", value = 0.0)),
div(class="span1",textInput("xlimitsmax", label = "x-max", value = 0.5)),
div(class="span1",textInput("ylimitsmin", label = "y-min", value = 0.5)),
div(class="span1",textInput("ylimitsmax", label = "y-max", value = 1.0))
)
Add:
#xlimitsmin, #xlimitsmax, #ylimitsmin, #ylimitsmax {
max-width: 25px;
}
in a css file (e.g., style.css in the www/ directory) in your app and source it from ui.R with:
includeCSS('www/style.R')
I am not sure why you need a textInput rather than a numericInput since the input you seem to be looking for is numeric. If you choose numericInput you can simply replace textInput with numericInput in the above. If you want the inputs in the sidebarPanel you could use the code below. The same css file mentioned above would be needed.
div(class="row-fluid",
div(class="span3",numericInput("xlimitsmin", label = "x-min", value = 0.0)),
div(class="span3",numericInput("xlimitsmax", label = "x-max", value = 0.5)),
div(class="span3",numericInput("ylimitsmin", label = "y-min", value = 0.5)),
div(class="span3",numericInput("ylimitsmax", label = "y-max", value = 1.0))
)
Sgrubsmyon's approach was almost perfect for me however I ran into a new problem with the flex-box approach in that there was no padding between the inputs. Apparently this has something to do with "display: flex" being a wrapper for "flex-grow 1" which uses up all available space. I dove into the rabbit hole and couldnt get this to work but learnt about a similar approach which uses "CSS - Grid" and is even easier (relevant SO question where I learnt this from):
div(
style = "display: grid;
grid-template-columns: 20% repeat(3, 20%); ## same as repeat(4, 20%)
grid-gap: 10px;",
textInput("inputA", "The first input"),
textInput("inputB", "The second input"),
textInput("inputC", "The third input"),
textInput("inputD", "The fourth input")
)
A similar great guide exists for the CSS - Grid approach located here where you can learn about all the different arguments and customisability that you can use. Note that I have never touched CSS until 2 hours before writing this answer so any corrections are welcome =)

shiny 4 small textInput boxes side-by-side

I've got a shiny server version 0.4.0 and I want to have 4 small textInput boxes to look like this:
x-min x-max y-min y-max
[...] [...] [...] [...]
They now look like this:
x-min
[...................]
x-max
[...................]
y-min
[...................]
y-max
[...................]
With this code:
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5),
textInput(inputId="ylimitsmin", label="y-min", value = 0.5),
textInput(inputId="ylimitsmax", label="y-max", value = 1.0),
Any ideas how to achieve this?
EDITED: I've successfully changed things like this elsewhere in the code:
<style type="text/css">select#yaxis4 { height: 280px; width: 500px; }</style>
[... which links to this later on in the page...]
<label class="control-label" for="yaxis4">Y-Axis</label>
<select id="yaxis4" multiple="multiple">
And this is what it looks like for the ones that don't work:
<style type="text/css">select#xlimitsmax { display: inline-block; max-width: 50px; }</style>
[... which links to...]
<label>x-max</label>
<input id="xlimitsmax" type="text" value="0.5"/>
EDITED:
Here is a self contained example ui.R that doesn't work:
library(shiny)
shinyUI(
pageWithSidebar(
# application title
headerPanel("test01"),
sidebarPanel(
tags$head(
tags$style(type="text/css", "select { max-width: 360px; }"),
tags$style(type="text/css", ".span4 { max-width: 360px; }"),
tags$style(type="text/css", ".well { max-width: 360px; }")
),
wellPanel(
p(strong("Side Panel:"))
)
),
mainPanel(
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
tags$head(tags$style(type="text/css", "select#xlimitsmin { max-width: 50px }")),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5),
tags$head(tags$style(type="text/css", "select#xlimitsmax { display: inline-block; max-width: 50px; }"))
)
))
Resulting page:
to paraphrase (and to simplify to the case of 2 inputs), your problem is that:
runApp(list(
ui = bootstrapPage(
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5)
),
server = function(input, output) {}
))
shows
But you want side-by-side small inputs, like so:
The short answer
textInputRow<-function (inputId, label, value = "")
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,class="input-small"))
}
runApp(list(
ui = bootstrapPage(
textInputRow(inputId="xlimitsmin", label="x-min", value = 0.0),
textInputRow(inputId="xlimitsmax", label="x-max", value = 0.5)
),
server = function(input, output) {}
))
gives:
The long answer
Side-by-side inputs
Let's do side-by-side first:
Currently textInput generates two separate tags - the label, and the input, each of which is configured by CSS as display:block, which means it's a rectangle that will break to the left side of the container. We need to wrap each textInput's field in new container (div) and tell that container that the container that follows it (the next textInput) is allowed to be on the same horizontal row on the page, using CSS's display:inline-block.
So we add a div with a style around each textInput:
runApp(list(
ui = bootstrapPage(
div(style="display:inline-block",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
div(style="display:inline-block",textInput(inputId="xlimitsmax", label="x-max", value = 0.5))
),
server = function(input, output) {}
))
Small inputs
Now let's deal with small. There are a few ways to do small,
make the font smaller,
make the input box have fewer characters in it.
tell css or (here) bootstrap to draw a smaller box
Since bootstrap.js is really in control of layout when we use shiny, only 3 will reliably work, so let's use that.
Input sizes are documented in Bootstrap 2.3.2's CSS Forms documentation, under 'Control Sizing'. It includes a variety of sizes from mini, small, medium, large, xlarge, and xxlarge, and the default is probably medium. Let's try small, instead.
To set the size, we need to change the class of the input tag generated by textInput.
Now textInput is just a convenience function around the more powerful tags functions such as tags$label and tags$input. We can build a more powerful version of textInput that allows us to configure the elements, specifically the class of the input node:
textInput2<-function (inputId, label, value = "",...)
{
tagList(tags$label(label, `for` = inputId), tags$input(id = inputId,
type = "text", value = value,...))
}
runApp(list(
ui = bootstrapPage(
div(style="display:inline-block",textInput2(inputId="xlimitsmin", label="x-min", value = 0.0, class="input-small")),
div(style="display:inline-block",textInput2(inputId="xlimitsmax", label="x-max", value = 0.5, class="input-small"))
),
server = function(input, output) {}
))
And we are done - but we can roll some of this functionality up by having textInput3 generate the div tag. It could also set the class by itself, but I'll leave that for you to write.
Wrapping it up
textInput3<-function (inputId, label, value = "",...)
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,...))
}
runApp(list(
ui = bootstrapPage(
textInput3(inputId="xlimitsmin", label="x-min", value = 0.0, class="input-small"),
textInput3(inputId="xlimitsmax", label="x-max", value = 0.5, class="input-small")
),
server = function(input, output) {}
))
For interest's sake, here's the version using class input-mini:
Using Shiny (>= 0.11), you can accomplish this by putting the input calls within a splitLayout(). This will split the fluid row, box, etc. into the necessary columns required to show your input fields side-by-side.
The example below would give you three text inputs in a box, that will appear side-by-side in the fluidRow.
fluidRow(
box(width = 12, title = "A Box in a Fluid Row I want to Split",
splitLayout(
textInput("inputA", "The first input"),
textInput("inputB", "The second input"),
textInput("inputC", "The third input")
)
)
)
Maybe this solution wasn't around in 2013 but if you want to do this without writing HTML or CSS you can just use the column function within a fluidRow like so:
fluidRow(
column(3,
selectInput('pcat', 'Primary Category', c("ALL", "Some"))),
column(3,
selectInput('smodel', 'Statistical Model', c("NONE", "LINEAR REGRESSION", "LOWESS")))
)
And it will place things side by side.
EDIT: Now there is another very easy way to do this using the splitLayout() function. See Nadir Sidi's answer for more details.
I deleted the old answer - here is one that works:
ui.r:
library(shiny)
shinyUI(
pageWithSidebar(
# application title
headerPanel("test01"),
sidebarPanel(
tags$head(
tags$style(type="text/css", "select { max-width: 360px; }"),
tags$style(type="text/css", ".span4 { max-width: 360px; }"),
tags$style(type="text/css", ".well { max-width: 360px; }")
),
wellPanel(
p(strong("Side Panel:"))
)
),
mainPanel(
div(id="XXmin",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
tags$head(tags$style(type="text/css", "#XXmin {display: inline-block}")),
tags$head(tags$style(type="text/css", "#xlimitsmin {max-width: 50px}")),
div(id="XXmax",textInput(inputId="xlimitsmax", label="x-max", value = 0.5)),
tags$head(tags$style(type="text/css", "#XXmax {display: inline-block}"),
tags$head(tags$style(type="text/css", "#xlimitsmax {max-width: 50px}"))
))
))
Here are the changes I made:
1) I eliminated the select from select#xlimitsmax and select#xlimitsmin in your .css statements
2) I put your two controls each in their own div() and gave them the names XXmin and XXmax. I then added .css statements to make them inline-block.
If you have a bunch of these you might want to use a class statement - such as:
div(class="MyClass",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
tags$head(tags$style(type="text/css", ".MyClass {display: inline-block}")),
tags$head(tags$style(type="text/css", "#xlimitsmin {max-width: 50px}")),
then you can tag each of the control div()'s as class="MyClass" and use only one .css statement.
Edited to add: Thanks for posting the example code - that made it much easier.
2nd Edit: Just to clarify. The point of putting the textInput commands inside a div() is to unite the input box and its label into a single object so that styles (in this case the display style) can be applied. If you don't do this, the label and the box act as two separate entities and it is harder to manipulate them in cases like this.
As an alternative to putting verbose style declarations in a class, it seems you can easily extend the shiny tags functions to your liking. This particular one would be convenient to have around by default.
(this is with shiny shiny_0.14.1 ). Thought I was going to need to write a closure but this seems to work.
inline = function (x) {
tags$div(style="display:inline-block;", x)
}
inline(textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
inline(textInput(inputId="xlimitsmax", label="x-max", value = 0.5)),
inline(textInput(inputId="ylimitsmin", label="y-min", value = 0.5)),
inline(textInput(inputId="ylimitsmax", label="y-max", value = 1.0)),
I wasn't happy with splitLayout() because it introduces scroll bars when space is limited.
I found that, at least for input widgets like buttons or text boxes, a quite easy solution with better responsive behaviour is using flex-box: (see this great guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
div(
style = "display: flex; flex-wrap: wrap;",
div(
style = "flex: 1;",
textInput("inputA", "The first input")
),
div(
style = "flex: 1;",
textInput("inputB", "The second input")
),
div(
style = "flex: 1;",
textInput("inputC", "The third input")
)
)
It is possible to adjust relative widths. Corresponding to splitLayout(cellWidths = c("25%", "75%"), ...):
div(
style = "display: flex; flex-wrap: wrap;",
div(
style = "flex: 1;",
textInput("inputA", "The first input")
),
div(
style = "flex: 3;", # second item 3 times as wide as first one
textInput("inputB", "The second input")
)
)
If you want the inputs in mainPanel you can use the following:
div(class="row-fluid",
div(class="span1",textInput("xlimitsmin", label = "x-min", value = 0.0)),
div(class="span1",textInput("xlimitsmax", label = "x-max", value = 0.5)),
div(class="span1",textInput("ylimitsmin", label = "y-min", value = 0.5)),
div(class="span1",textInput("ylimitsmax", label = "y-max", value = 1.0))
)
Add:
#xlimitsmin, #xlimitsmax, #ylimitsmin, #ylimitsmax {
max-width: 25px;
}
in a css file (e.g., style.css in the www/ directory) in your app and source it from ui.R with:
includeCSS('www/style.R')
I am not sure why you need a textInput rather than a numericInput since the input you seem to be looking for is numeric. If you choose numericInput you can simply replace textInput with numericInput in the above. If you want the inputs in the sidebarPanel you could use the code below. The same css file mentioned above would be needed.
div(class="row-fluid",
div(class="span3",numericInput("xlimitsmin", label = "x-min", value = 0.0)),
div(class="span3",numericInput("xlimitsmax", label = "x-max", value = 0.5)),
div(class="span3",numericInput("ylimitsmin", label = "y-min", value = 0.5)),
div(class="span3",numericInput("ylimitsmax", label = "y-max", value = 1.0))
)
Sgrubsmyon's approach was almost perfect for me however I ran into a new problem with the flex-box approach in that there was no padding between the inputs. Apparently this has something to do with "display: flex" being a wrapper for "flex-grow 1" which uses up all available space. I dove into the rabbit hole and couldnt get this to work but learnt about a similar approach which uses "CSS - Grid" and is even easier (relevant SO question where I learnt this from):
div(
style = "display: grid;
grid-template-columns: 20% repeat(3, 20%); ## same as repeat(4, 20%)
grid-gap: 10px;",
textInput("inputA", "The first input"),
textInput("inputB", "The second input"),
textInput("inputC", "The third input"),
textInput("inputD", "The fourth input")
)
A similar great guide exists for the CSS - Grid approach located here where you can learn about all the different arguments and customisability that you can use. Note that I have never touched CSS until 2 hours before writing this answer so any corrections are welcome =)

Resources