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.Attribute 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.Attribute msg) -> { onPress : Maybe msg, label : Element msg } -> Element 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.Attribute msg) -> { onChange : Basics.Bool -> msg, icon : Basics.Bool -> Element msg, checked : Basics.Bool, label : Label msg } -> Element msg
Msg
to send.Input.defaultCheckbox
is included to get you started.Label
for this checkboxdefaultCheckbox : Basics.Bool -> Element msg
The blue default checked box icon.
You'll likely want to make your own checkbox at some point that fits your design.
{ start : Basics.Int
, end : Basics.Int
, direction : String
}
selectionDecoder : Json.Decode.Decoder Selection
text : List (Element.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, selection : Maybe Selection } -> Element msg
multiline : List (Element.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, spellcheck : Basics.Bool, selection : Maybe Selection } -> Element msg
A multiline text input.
By default it will have a minimum height of one line and resize based on it's contents.
Use Element.spacing
to change its line-height.
placeholder : List (Element.Attribute msg) -> Element msg -> Placeholder 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.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, selection : Maybe Selection } -> Element msg
newPassword : List (Element.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, show : Basics.Bool } -> Element 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.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, show : Basics.Bool } -> Element msg
email : List (Element.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, selection : Maybe Selection } -> Element msg
search : List (Element.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, selection : Maybe Selection } -> Element msg
spellChecked : List (Element.Attribute msg) -> { onChange : String -> msg, text : String, placeholder : Maybe (Placeholder msg), label : Label msg, selection : Maybe Selection } -> Element 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.Attribute msg) -> { onChange : Basics.Float -> msg, label : Label msg, min : Basics.Float, max : Basics.Float, value : Basics.Float, thumb : Thumb, step : Maybe Basics.Float } -> Element 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.Attribute Basics.Never) -> Thumb
defaultThumb : Thumb
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.Attribute msg) -> { onChange : option -> msg, options : List (Option option msg), selected : Maybe option, label : Label msg } -> Element msg
radioRow : List (Element.Attribute msg) -> { onChange : option -> msg, options : List (Option option msg), selected : Maybe option, label : Label msg } -> Element msg
Same as radio, but displayed as a row
option : value -> Element msg -> Option value msg
Add a choice to your radio element. This will be rendered with the default radio icon.
optionWith : value -> (OptionState -> Element msg) -> Option value msg
Customize exactly what your radio option should look like in different states.
Every input has a required Label
.
labelAbove : List (Element.Attribute msg) -> Element msg -> Label msg
labelBelow : List (Element.Attribute msg) -> Element msg -> Label msg
labelLeft : List (Element.Attribute msg) -> Element msg -> Label msg
labelRight : List (Element.Attribute msg) -> Element msg -> Label msg
labelHidden : String -> Label 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?