A Handsome configurable select inspired by Culture Amp's Kaizen select.

elm-package install Confidenceman02/elm-select 1.0.2



Select things in style! Inspired and built on top of Culture Amp's Kaizen design system select component.

Why not just use the Kaizen select?

  1. The Kaizen design system is a wonderful project with both react and elm components/views. Because the Kaizen elm select is being styled in scss, your project would need some extra tooling and bundling for things to work correctly. The elm-select package converts all the non elm code to the elm we all know and love. This is achieved largely due to the very excellent rtfeldman/elm-css.

  2. The Kaizen elm select is largely a port of the react select project which is a widely used piece of work. Whilst this package matches most of the functionality of react select and Kaizen select, it endeavours to implement the WAI-ARIA best practices for selects, in particular, the combobox pattern.


A lot of effort has been put into making the elm-select package accessible. Heavy focus on automated end to end testing using playwright allows for progressive improvement over time and avoid any regressions to accessibility. Tests have been modelled after the WAI-ARIA recommendations, however, accessibility of elm-select is an ongoing comittment.


Set an initial state in your model.

init : Model
init = 
    {  selectState = initState
    ,  items = []

Set up an update Msg in your update function.

    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        SelectMsg selectMsg ->
                (action, selectState, selectCmds) = update selectMsg model.selectState
            ({ model | selectState = selectState }, SelectMsg selectCmds)

Handle the Action in your update function.

    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        SelectMsg selectMsg ->
                (maybeAction, selectState, selectCmds) = update selectMsg model.selectState

                handledAction =
                    case maybeAction of 
                        -- an item has been selected
                        Just Select item ->
                            -- handle selected item
                        -- Clear single select selection
                        Just ClearSingleSelectItem item ->
                            -- handle cleared item
                        -- Multi item has been deselected
                        Just DeselectMultiItem ->
                            -- handle deselected multi item
                        -- Input value has changed
                        Just InputChange value ->
                            -- handle changed input
                        _ ->
                          -- no action
            -- (model, Cmd Msg)

Render your view.

view : Model -> Msg
view model =
      ((Select.single Nothing)
          |> Select.state model.selectState
          |> Select.menuItems model.items
          |> Select.placeholder "Placeholder"
      (selectIdentifier "SingleSelectExample")

Opt in JS optimizations

The @confidenceman02/elm-select project has some JS performance optimizations that dynamically size the input element. There are some sensible reasons why this optimization makes sense.

Lets think about how we would dynamically resize an input element as someone types in elm.

  • We would handle some sort of "input" event.
  • We would query a hidden sizer node that contains the input text for its dimensions.
  • We would update the width of the input.

Resizing an input dynamically using the above method ends up being not very performant due to how slow it is to react to events and query the DOM. It is certain that someone will experience a lag between them typing and the input resizing. Not a great user experience!

When you opt in to JS optimization, elm-select uses a mutation observer which allows for a zero lag, performant, dynamically sized input.

If you don't want to use a JS optimization thats totally ok! Elm-select handles dynamic width via the size atribute by default. The size attribute is not an ideal solution despite the fact it mostly just works. Consider adding the very minimal JS in to your project to get the best performance.

Opt in to JS optimization

{ selectState = initState |> jsOptimize True }

Importing the JS

Via npm

npm install @confidenceman02/elm-select

Via github packages:

NOTE: Using the github package will require you to add the following line to your projects .npmrc.

// .npmrc 

install the package.

npm install @confidenceman02/elm-select

Using the JS

Import the script wherever you are initiating your Elm program.

import { Elm } from "./src/Main";
import "@confidenceman02/elm-select"

Elm.Main.init({node, flags})

Alternatively you can import a minified script directly into your html file.

<!DOCTYPE html>
    <meta charset="utf-8">

    <script src="/node_modules/@confidenceman02/elm-select/dist/dynamic.min.js"></script>
    <script src="index.js"></script>