Input elements have a lot of constraints!
We want all of our input elements to:
While these three goals may seem pretty obvious, Html and CSS have made it surprisingly difficult to achieve!
And incredibly difficult for developers to remember all the tricks necessary to make things work. If you've every tried to make a <textarea>
be the height of it's content or restyle a radio button while maintaining accessibility, you may be familiar.
This module is intended to be accessible by default. You shouldn't have to wade through docs, articles, and books to find out exactly how accessible your html actually is.
All Elements can be styled on focus by using Element.focusStyle
to set a global focus style or Element.focused
to set a focus style individually for an element.
focusedOnLoad : Element.WithContext.Attribute context msg
Attach this attribute to any Input
that you would like to be automatically focused when the page loads.
You should only have a maximum of one per page.
button : List (Element.WithContext.Attribute context msg) -> { onPress : Maybe msg, label : Element.WithContext.Element context msg } -> Element.WithContext.Element context msg
A standard button.
The onPress
handler will be fired either onClick
or when the element is focused and the Enter
key has been pressed.
import Element exposing (rgb255, text)
import Element.Background as Background
import Element.Input as Input
blue =
Element.rgb255 238 238 238
myButton =
Input.button
[ Background.color blue
, Element.focused
[ Background.color purple ]
]
{ onPress = Just ClickMsg
, label = text "My Button"
}
Note If you have an icon button but want it to be accessible, consider adding a Region.description
, which will describe the button to screen readers.
A checkbox requires you to store a Bool
in your model.
This is also the first input element that has a required label
.
import Element exposing (text)
import Element.Input as Input
type Msg
= GuacamoleChecked Bool
view model =
Input.checkbox []
{ onChange = GuacamoleChecked
, icon = Input.defaultCheckbox
, checked = model.guacamole
, label =
Input.labelRight []
(text "Do you want Guacamole?")
}
checkbox : List (Element.WithContext.Attribute context msg) -> { onChange : Basics.Bool -> msg, icon : Basics.Bool -> Element.WithContext.Element context msg, checked : Basics.Bool, label : Label context msg } -> Element.WithContext.Element context msg
Msg
to send.Input.defaultCheckbox
is included to get you started.Label
for this checkboxdefaultCheckbox : Basics.Bool -> Element.WithContext.Element context msg
The blue default checked box icon.
You'll likely want to make your own checkbox at some point that fits your design.
text : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg } -> Element.WithContext.Element context msg
multiline : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg, spellcheck : Basics.Bool } -> Element.WithContext.Element context msg
A multiline text input.
By default it will have a minimum height of one line and resize based on it's contents.
placeholder : List (Element.WithContext.Attribute context msg) -> Element.WithContext.Element context msg -> Placeholder context msg
If we want to play nicely with a browser's ability to autofill a form, we need to be able to give it a hint about what we're expecting.
The following inputs are very similar to Input.text
, but they give the browser a hint to allow autofill to work correctly.
username : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg } -> Element.WithContext.Element context msg
newPassword : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg, show : Basics.Bool } -> Element.WithContext.Element context msg
A password input that allows the browser to autofill.
It's newPassword
instead of just password
because it gives the browser a hint on what type of password input it is.
A password takes all the arguments a normal Input.text
would, and also show, which will remove the password mask (e.g. ****
vs pass1234
)
currentPassword : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg, show : Basics.Bool } -> Element.WithContext.Element context msg
email : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg } -> Element.WithContext.Element context msg
search : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg } -> Element.WithContext.Element context msg
spellChecked : List (Element.WithContext.Attribute context msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder context msg), label : Label context msg } -> Element.WithContext.Element context msg
If spell checking is available, this input will be spellchecked.
A slider is great for choosing between a range of numerical values.
slider : List (Element.WithContext.Attribute context msg) -> { onChange : Basics.Float -> msg, label : Label context msg, min : Basics.Float, max : Basics.Float, value : Basics.Float, thumb : Thumb context, step : Maybe Basics.Float } -> Element.WithContext.Element context msg
A slider input, good for capturing float values.
Input.slider
[ Element.height (Element.px 30)
-- Here is where we're creating/styling the "track"
, Element.behindContent
(Element.el
[ Element.width Element.fill
, Element.height (Element.px 2)
, Element.centerY
, Background.color grey
, Border.rounded 2
]
Element.none
)
]
{ onChange = AdjustValue
, label =
Input.labelAbove []
(text "My Slider Value")
, min = 0
, max = 75
, step = Nothing
, value = model.sliderValue
, thumb =
Input.defaultThumb
}
Element.behindContent
is used to render the track of the slider. Without it, no track would be rendered. The thumb
is the icon that you can move around.
The slider can be vertical or horizontal depending on the width/height of the slider.
height fill
and width (px someWidth)
will cause the slider to be vertical.height (px someHeight)
and width (px someWidth)
where someHeight
> someWidth
will also do it.Note If you want a slider for an Int
value:
step
to be Just 1
, or some other whole valuevalue = toFloat model.myInt
onChange = round >> AdjustValue
thumb : List (Element.WithContext.Attribute context Basics.Never) -> Thumb context
defaultThumb : Thumb context
The fact that we still call this a radio selection is fascinating. I can't remember the last time I actually used an honest-to-goodness button on a radio. Chalk it up along with the floppy disk save icon or the word Dashboard.
Perhaps a better name would be Input.chooseOne
, because this allows you to select one of a set of options!
Nevertheless, here we are. Here's how you put one together
Input.radio
[ padding 10
, spacing 20
]
{ onChange = ChooseLunch
, selected = Just model.lunch
, label = Input.labelAbove [] (text "Lunch")
, options =
[ Input.option Burrito (text "Burrito")
, Input.option Taco (text "Taco!")
, Input.option Gyro (text "Gyro")
]
}
Note we're using Input.option
, which will render the default radio icon you're probably used to. If you want compeltely custom styling, use Input.optionWith
!
radio : List (Element.WithContext.Attribute context msg) -> { onChange : option -> msg, options : List (Option context option msg), selected : Maybe option, label : Label context msg } -> Element.WithContext.Element context msg
radioRow : List (Element.WithContext.Attribute context msg) -> { onChange : option -> msg, options : List (Option context option msg), selected : Maybe option, label : Label context msg } -> Element.WithContext.Element context msg
Same as radio, but displayed as a row
option : value -> Element.WithContext.Element context msg -> Option context value msg
Add a choice to your radio element. This will be rendered with the default radio icon.
optionWith : value -> (OptionState -> Element.WithContext.Element context msg) -> Option context value msg
Customize exactly what your radio option should look like in different states.
Every input has a required Label
.
labelAbove : List (Element.WithContext.Attribute context msg) -> Element.WithContext.Element context msg -> Label context msg
labelBelow : List (Element.WithContext.Attribute context msg) -> Element.WithContext.Element context msg -> Label context msg
labelLeft : List (Element.WithContext.Attribute context msg) -> Element.WithContext.Element context msg -> Label context msg
labelRight : List (Element.WithContext.Attribute context msg) -> Element.WithContext.Element context msg -> Label context msg
labelHidden : String -> Label context msg
Sometimes you may need to have a label which is not visible, but is still accessible to screen readers.
Seriously consider a visible label before using this.
The situations where a hidden label makes sense:
search
button right next to it.table
of inputs where the header gives the label.Basically, a hidden label works when there are other contextual clues that sighted people can pick up on.
You might be wondering where something like <form>
is.
What I've found is that most people who want <form>
usually want it for the implicit submission behavior or to be clearer, they want to do something when the Enter
key is pressed.
Instead of implicit submission behavior, try making an onEnter
event handler like in this Ellie Example. Then everything is explicit!
And no one has to look up obtuse html documentation to understand the behavior of their code :).
Presently, elm-ui does not expose a replacement for <input type="file">
; in the meantime, an Input.button
and elm/file
's File.Select
may meet your needs.
You also might be wondering how to disable an input.
Disabled inputs can be a little problematic for user experience, and doubly so for accessibility. This is because it's now your priority to inform the user why some field is disabled.
If an input is truly disabled, meaning it's not focusable or doesn't send off a Msg
, you actually lose your ability to help the user out! For those wary about accessibility this is a big problem.
Here are some alternatives to think about that don't involve explicitly disabling an input.
Disabled Buttons - Change the Msg
it fires, the text that is rendered, and optionally set a Region.description
which will be available to screen readers.
import Element.Input as Input
import Element.Region as Region
myButton ready =
if ready then
Input.button
[ Background.color blue
]
{ onPress =
Just SaveButtonPressed
, label =
text "Save blog post"
}
else
Input.button
[ Background.color grey
, Region.description
"A publish date is required before saving a blogpost."
]
{ onPress =
Just DisabledSaveButtonPressed
, label =
text "Save Blog "
}
Consider showing a hint if DisabledSaveButtonPressed
is sent.
For other inputs such as Input.text
, consider simply rendering it in a normal paragraph
or el
if it's not editable.
Alternatively, see if it's reasonable to not display an input if you'd normally disable it. Is there an option where it's only visible when it's editable?