Skip to content
Back to skills

Elm Development

ASecurity

Comprehensive Elm functional programming for web development including The Elm Architecture, type system, JavaScript interop, and production-ready patterns

  • 666 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 8, 2026
developmentjavascriptgojavabashreactnodeexpresstestingrefactoringapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill elm-development --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Elm Development?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Elm Development
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-elm-development/badge)](https://www.skillsdirectory.com/skills/majiayu000-elm-development)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: elm-development
description: Comprehensive Elm functional programming for web development including The Elm Architecture, type system, JavaScript interop, and production-ready patterns
---

# Elm Development

A comprehensive skill for building reliable web applications using Elm, a functional programming language that compiles to JavaScript. Elm offers no runtime errors in practice, friendly compiler messages, and a delightful development experience through strong typing and pure functional programming.

## When to Use This Skill

Use this skill when:

- Building web applications that require high reliability and zero runtime errors
- Creating SPAs (Single Page Applications) with predictable state management
- Developing user interfaces with strong type safety guarantees
- Working on projects where refactoring safety is critical
- Building applications with complex state that needs to be managed cleanly
- Creating web apps that benefit from functional programming patterns
- Developing projects where friendly error messages aid rapid development
- Integrating Elm components into existing JavaScript applications
- Building interactive web applications with real-time features
- Creating maintainable, long-lived codebases with clear contracts

## Core Concepts

### The Elm Architecture (TEA)

The Elm Architecture is a pattern for building interactive applications that emerged naturally from Elm development. It inspired Redux and similar state management patterns.

**Core Components:**

1. **Model**: The complete state of your application
2. **View**: A pure function that renders the model as HTML
3. **Update**: A pure function that updates the model based on messages

**Data Flow:**
```
Model → View → HTML
   ↑             ↓
   └── Update ←──┘
        Messages
```

**Basic Pattern:**
```elm
type alias Model =
    { count : Int }

type Msg
    = Increment
    | Decrement

init : Model
init =
    { count = 0 }

update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 1 }

        Decrement ->
            { model | count = model.count - 1 }

view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , div [] [ text (String.fromInt model.count) ]
        , button [ onClick Increment ] [ text "+" ]
        ]
```

### Extended Architecture with Effects

For real applications, you need side effects (HTTP, random, etc.):

**Enhanced Components:**
- **init**: Returns `(Model, Cmd Msg)` - initial state plus commands to run
- **update**: Returns `(Model, Cmd Msg)` - new state plus commands to run
- **subscriptions**: Listens to external events (time, websockets, etc.)
- **view**: Same pure function rendering HTML

**Program Types:**
- `Browser.sandbox`: Simple apps without side effects
- `Browser.element`: Apps with side effects (HTTP, etc.)
- `Browser.document`: Control over `<title>` and `<body>`
- `Browser.application`: Full SPA with URL routing

### Type System Fundamentals

Elm's type system prevents runtime errors through static analysis and type inference.

**Type Annotations:**
```elm
-- Function type annotations
add : Int -> Int -> Int
add x y = x + y

-- Record type alias
type alias User =
    { id : Int
    , name : String
    , email : String
    }

-- Custom types (sum types / tagged unions)
type UserStatus
    = Active
    | Suspended
    | Deleted

-- Parametric types
type Result error value
    = Ok value
    | Err error
```

**Type Inference:**
Elm infers types automatically, but annotations are recommended for top-level functions:

```elm
-- Compiler infers: String -> String
greet name =
    "Hello, " ++ name

-- Better: Add explicit annotation
greet : String -> String
greet name =
    "Hello, " ++ name
```

**Benefits:**
- Catches type errors at compile time
- Provides excellent error messages
- Enables fearless refactoring
- Documents function signatures
- No null/undefined errors

### Maybe and Result Types

Elm eliminates null/undefined errors using Maybe and Result.

**Maybe Type:**
```elm
type Maybe a
    = Just a
    | Nothing

-- Example: Finding a user
findUser : Int -> Maybe User
findUser id =
    -- Returns Just user if found, Nothing otherwise
    ...

-- Pattern matching on Maybe
case findUser 123 of
    Just user ->
        "Found: " ++ user.name

    Nothing ->
        "User not found"

-- Helper functions
Maybe.withDefault : a -> Maybe a -> a
Maybe.map : (a -> b) -> Maybe a -> Maybe b
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
```

**Result Type:**
```elm
type Result error value
    = Ok value
    | Err error

-- Example: Validating user input
validateAge : String -> Result String Int
validateAge input =
    case String.toInt input of
        Nothing ->
            Err "Not a valid number"

        Just age ->
            if age < 0 then
                Err "Age cannot be negative"
            else if age > 150 then
                Err "Age seems unrealistic"
            else
                Ok age

-- Using Result
case validateAge "25" of
    Ok age ->
        "Valid age: " ++ String.fromInt age

    Err error ->
        "Error: " ++ error
```

### Pattern Matching

Pattern matching is fundamental to Elm programming:

```elm
-- Matching on custom types
type Traffic
    = Red
    | Yellow
    | Green

describe : Traffic -> String
describe light =
    case light of
        Red ->
            "Stop"
        Yellow ->
            "Prepare to stop"
        Green ->
            "Go"

-- Matching on lists
describeList : List a -> String
describeList list =
    case list of
        [] ->
            "Empty list"

        [single] ->
            "One item"

        [first, second] ->
            "Two items"

        _ ->
            "Many items"

-- Matching with extraction
type User
    = Anonymous
    | LoggedIn String Int

getUserName : User -> String
getUserName user =
    case user of
        Anonymous ->
            "Guest"

        LoggedIn name _ ->
            name
```

## Language Features

### Functions

**Function Definition:**
```elm
-- Simple function
double : Int -> Int
double x =
    x * 2

-- Multiple parameters
add : Int -> Int -> Int
add x y =
    x + y

-- Partial application
add5 : Int -> Int
add5 =
    add 5

-- Anonymous functions (lambdas)
doubleList : List Int -> List Int
doubleList numbers =
    List.map (\n -> n * 2) numbers

-- Pipe operator
result : Int
result =
    [1, 2, 3, 4, 5]
        |> List.map double
        |> List.filter (\n -> n > 5)
        |> List.sum

-- Function composition
addThenDouble : Int -> Int
addThenDouble =
    add 3 >> double
```

**Let Expressions:**
```elm
calculateArea : Float -> Float -> Float
calculateArea width height =
    let
        perimeter =
            2 * (width + height)

        area =
            width * height
    in
    area
```

### Records

**Record Syntax:**
```elm
-- Record type alias
type alias Point =
    { x : Float
    , y : Float
    }

-- Creating records
origin : Point
origin =
    { x = 0, y = 0 }

-- Accessing fields
getX : Point -> Float
getX point =
    point.x

-- Field access function
.x origin  -- Returns 0

-- Updating records (immutable)
moveRight : Point -> Point
moveRight point =
    { point | x = point.x + 1 }

-- Updating multiple fields
moveDiagonal : Point -> Point
moveDiagonal point =
    { point
        | x = point.x + 1
        , y = point.y + 1
    }

-- Pattern matching on records
distance : Point -> Float
distance { x, y } =
    sqrt (x * x + y * y)
```

### Lists and Arrays

**Lists (Linked Lists):**
```elm
-- List literals
numbers : List Int
numbers =
    [1, 2, 3, 4, 5]

-- Cons operator
moreNumbers : List Int
moreNumbers =
    0 :: numbers  -- [0, 1, 2, 3, 4, 5]

-- List functions
List.map : (a -> b) -> List a -> List b
List.filter : (a -> Bool) -> List a -> List a
List.foldl : (a -> b -> b) -> b -> List a -> b
List.length : List a -> Int
List.reverse : List a -> List a
List.sort : List comparable -> List comparable
List.concat : List (List a) -> List a

-- Example usage
processNumbers : List Int -> Int
processNumbers nums =
    nums
        |> List.filter (\n -> n > 0)
        |> List.map (\n -> n * 2)
        |> List.sum
```

**Arrays (Random Access):**
```elm
import Array exposing (Array)

-- Arrays for fast random access
numbers : Array Int
numbers =
    Array.fromList [1, 2, 3, 4, 5]

-- Array operations
Array.get : Int -> Array a -> Maybe a
Array.set : Int -> a -> Array a -> Array a
Array.push : a -> Array a -> Array a
Array.length : Array a -> Int
```

### Modules and Imports

**Module Definition:**
```elm
module Utils exposing (capitalize, truncate)

-- Private function (not exposed)
isBlank : String -> Bool
isBlank str =
    String.trim str == ""

-- Public function
capitalize : String -> String
capitalize str =
    if isBlank str then
        str
    else
        String.toUpper (String.left 1 str) ++ String.dropLeft 1 str

-- Public function
truncate : Int -> String -> String
truncate maxLength str =
    if String.length str > maxLength then
        String.left (maxLength - 3) str ++ "..."
    else
        str
```

**Import Syntax:**
```elm
-- Import entire module
import List
import Dict

-- Import with alias
import Json.Decode as Decode
import Json.Encode as Encode

-- Import specific functions
import List exposing (map, filter, foldl)

-- Import all exposed functions (use sparingly)
import Html exposing (..)
```

## The Elm Architecture in Depth

### Commands (Cmd)

Commands represent side effects to perform:

```elm
type Msg
    = FetchUser
    | GotUser (Result Http.Error User)

init : (Model, Cmd Msg)
init =
    ( { user = Nothing }
    , Cmd.none  -- No command initially
    )

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FetchUser ->
            ( model
            , Http.get
                { url = "/api/user"
                , expect = Http.expectJson GotUser userDecoder
                }
            )

        GotUser result ->
            case result of
                Ok user ->
                    ( { model | user = Just user }
                    , Cmd.none
                    )

                Err _ ->
                    ( model
                    , Cmd.none
                    )

-- Combining commands
Cmd.batch : List (Cmd msg) -> Cmd msg
Cmd.batch
    [ fetchUser
    , fetchPosts
    , logEvent
    ]
```

### Subscriptions (Sub)

Subscriptions listen to external events:

```elm
import Browser.Events
import Time

type Msg
    = Tick Time.Posix
    | KeyPressed String
    | MouseMoved Int Int

subscriptions : Model -> Sub Msg
subscriptions model =
    if model.isActive then
        Sub.batch
            [ Time.every 1000 Tick
            , Browser.Events.onKeyPress keyDecoder
            , Browser.Events.onMouseMove mouseDecoder
            ]
    else
        Sub.none

-- Subscription functions
Time.every : Float -> (Time.Posix -> msg) -> Sub msg
Browser.Events.onKeyPress : Decode.Decoder msg -> Sub msg
Browser.Events.onAnimationFrame : (Time.Posix -> msg) -> Sub msg
```

### View Functions

**HTML Generation:**
```elm
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)

view : Model -> Html Msg
view model =
    div [ class "container" ]
        [ header [ class "header" ]
            [ h1 [] [ text "My App" ]
            ]
        , main_ [ class "content" ]
            [ viewUserForm model.form
            , viewUserList model.users
            ]
        , footer [ class "footer" ]
            [ text "© 2025" ]
            ]

-- Conditional rendering
viewIf : Bool -> Html msg -> Html msg
viewIf condition content =
    if condition then
        content
    else
        text ""

-- List rendering
viewUserList : List User -> Html Msg
viewUserList users =
    div [ class "user-list" ]
        [ h2 [] [ text "Users" ]
        , ul [] (List.map viewUser users)
        ]

viewUser : User -> Html Msg
viewUser user =
    li [ class "user-item" ]
        [ text user.name
        , button [ onClick (DeleteUser user.id) ]
            [ text "Delete" ]
        ]
```

### State Management Patterns

**Nested Updates:**
```elm
type alias Model =
    { loginForm : LoginForm
    , settings : Settings
    }

type Msg
    = LoginFormMsg LoginFormMsg
    | SettingsMsg SettingsMsg

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        LoginFormMsg formMsg ->
            let
                (newForm, formCmd) =
                    LoginForm.update formMsg model.loginForm
            in
            ( { model | loginForm = newForm }
            , Cmd.map LoginFormMsg formCmd
            )

        SettingsMsg settingsMsg ->
            let
                (newSettings, settingsCmd) =
                    Settings.update settingsMsg model.settings
            in
            ( { model | settings = newSettings }
            , Cmd.map SettingsMsg settingsCmd
            )
```

## HTTP and JSON

### Making HTTP Requests

**GET Requests:**
```elm
import Http
import Json.Decode as Decode

type Msg
    = FetchData
    | GotData (Result Http.Error String)

fetchData : Cmd Msg
fetchData =
    Http.get
        { url = "https://api.example.com/data"
        , expect = Http.expectString GotData
        }

-- With JSON decoder
type alias User =
    { id : Int
    , name : String
    }

fetchUser : Int -> Cmd Msg
fetchUser userId =
    Http.get
        { url = "https://api.example.com/users/" ++ String.fromInt userId
        , expect = Http.expectJson GotUser userDecoder
        }

userDecoder : Decode.Decoder User
userDecoder =
    Decode.map2 User
        (Decode.field "id" Decode.int)
        (Decode.field "name" Decode.string)
```

**POST Requests:**
```elm
import Json.Encode as Encode

type Msg
    = CreateUser
    | UserCreated (Result Http.Error User)

createUser : String -> String -> Cmd Msg
createUser name email =
    Http.post
        { url = "https://api.example.com/users"
        , body = Http.jsonBody (encodeUser name email)
        , expect = Http.expectJson UserCreated userDecoder
        }

encodeUser : String -> String -> Encode.Value
encodeUser name email =
    Encode.object
        [ ("name", Encode.string name)
        , ("email", Encode.string email)
        ]
```

**Custom Requests:**
```elm
updateUser : Int -> User -> Cmd Msg
updateUser userId user =
    Http.request
        { method = "PUT"
        , headers = [ Http.header "Authorization" "Bearer token123" ]
        , url = "https://api.example.com/users/" ++ String.fromInt userId
        , body = Http.jsonBody (encodeUserUpdate user)
        , expect = Http.expectJson UserUpdated userDecoder
        , timeout = Just 10000
        , tracker = Nothing
        }
```

### JSON Decoding

**Basic Decoders:**
```elm
import Json.Decode as Decode exposing (Decoder)

-- Primitive decoders
stringDecoder : Decoder String
stringDecoder = Decode.string

intDecoder : Decoder Int
intDecoder = Decode.int

floatDecoder : Decoder Float
floatDecoder = Decode.float

boolDecoder : Decoder Bool
boolDecoder = Decode.bool

-- Field extraction
nameDecoder : Decoder String
nameDecoder =
    Decode.field "name" Decode.string

-- Nested fields
streetDecoder : Decoder String
streetDecoder =
    Decode.at ["address", "street"] Decode.string
```

**Complex Decoders:**
```elm
type alias User =
    { id : Int
    , name : String
    , email : String
    , age : Maybe Int
    , isActive : Bool
    }

userDecoder : Decoder User
userDecoder =
    Decode.map5 User
        (Decode.field "id" Decode.int)
        (Decode.field "name" Decode.string)
        (Decode.field "email" Decode.string)
        (Decode.maybe (Decode.field "age" Decode.int))
        (Decode.field "is_active" Decode.bool)

-- Alternative with pipeline style (requires elm-json-decode-pipeline)
userDecoderPipeline : Decoder User
userDecoderPipeline =
    Decode.succeed User
        |> required "id" Decode.int
        |> required "name" Decode.string
        |> required "email" Decode.string
        |> optional "age" (Decode.maybe Decode.int) Nothing
        |> required "is_active" Decode.bool
```

**List and Array Decoders:**
```elm
-- Decode list of users
usersDecoder : Decoder (List User)
usersDecoder =
    Decode.list userDecoder

-- Decode field containing list
postsDecoder : Decoder (List Post)
postsDecoder =
    Decode.field "posts" (Decode.list postDecoder)

-- Decode dictionary
userByIdDecoder : Decoder (Dict String User)
userByIdDecoder =
    Decode.dict userDecoder
```

**Handling Decode Errors:**
```elm
type Msg
    = GotData (Result Http.Error Data)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        GotData result ->
            case result of
                Ok data ->
                    ( { model | data = Just data }
                    , Cmd.none
                    )

                Err httpError ->
                    case httpError of
                        Http.BadUrl url ->
                            ( { model | error = Just ("Bad URL: " ++ url) }
                            , Cmd.none
                            )

                        Http.Timeout ->
                            ( { model | error = Just "Request timeout" }
                            , Cmd.none
                            )

                        Http.NetworkError ->
                            ( { model | error = Just "Network error" }
                            , Cmd.none
                            )

                        Http.BadStatus code ->
                            ( { model | error = Just ("Bad status: " ++ String.fromInt code) }
                            , Cmd.none
                            )

                        Http.BadBody message ->
                            ( { model | error = Just ("Decode error: " ++ message) }
                            , Cmd.none
                            )
```

## JavaScript Interop

### Flags (Initial Data)

Pass data from JavaScript to Elm on initialization:

**Elm Side:**
```elm
type alias Flags =
    { apiKey : String
    , userId : Int
    , theme : String
    }

init : Flags -> (Model, Cmd Msg)
init flags =
    ( { apiKey = flags.apiKey
      , userId = flags.userId
      , theme = flags.theme
      , data = Nothing
      }
    , fetchUserData flags.userId
    )

main : Program Flags Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }
```

**JavaScript Side:**
```javascript
var app = Elm.Main.init({
    node: document.getElementById('app'),
    flags: {
        apiKey: 'your-api-key',
        userId: 12345,
        theme: 'dark'
    }
});
```

### Ports (Bidirectional Communication)

**Defining Ports:**
```elm
port module Main exposing (..)

-- Port for sending data to JavaScript
port saveToLocalStorage : String -> Cmd msg

-- Port for receiving data from JavaScript
port onStorageChange : (String -> msg) -> Sub msg

-- Using ports in update
type Msg
    = SaveData String
    | StorageChanged String

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        SaveData data ->
            ( model
            , saveToLocalStorage data
            )

        StorageChanged data ->
            ( { model | data = data }
            , Cmd.none
            )

-- Using ports in subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
    onStorageChange StorageChanged
```

**JavaScript Side:**
```javascript
var app = Elm.Main.init({
    node: document.getElementById('app')
});

// Receiving from Elm
app.ports.saveToLocalStorage.subscribe(function(data) {
    localStorage.setItem('app-data', data);
});

// Sending to Elm
window.addEventListener('storage', function(e) {
    if (e.key === 'app-data') {
        app.ports.onStorageChange.send(e.newValue);
    }
});
```

**Common Port Patterns:**
```elm
-- Local storage
port setStorage : String -> Cmd msg
port getStorage : (String -> msg) -> Sub msg

-- WebSocket
port sendSocketMessage : String -> Cmd msg
port receiveSocketMessage : (String -> msg) -> Sub msg

-- Analytics
port trackEvent : { category : String, action : String } -> Cmd msg

-- Third-party libraries
port initMap : { lat : Float, lng : Float } -> Cmd msg
port updateMarker : { lat : Float, lng : Float } -> Cmd msg
```

### Custom Elements

Embed Elm in existing applications using web components:

**JavaScript Side:**
```javascript
customElements.define('elm-widget', class extends HTMLElement {
    connectedCallback() {
        this.app = Elm.Widget.init({
            node: this,
            flags: {
                initialValue: this.getAttribute('value')
            }
        });

        // Listen to Elm events
        this.app.ports.valueChanged.subscribe(value => {
            this.dispatchEvent(new CustomEvent('change', { detail: value }));
        });
    }
});
```

**HTML Usage:**
```html
<elm-widget value="initial"></elm-widget>
```

## Tooling and Build System

### elm.json Configuration

**Package vs Application:**
```json
{
    "type": "application",
    "source-directories": [
        "src"
    ],
    "elm-version": "0.19.1",
    "dependencies": {
        "direct": {
            "elm/browser": "1.0.2",
            "elm/core": "1.0.5",
            "elm/html": "1.0.0",
            "elm/http": "2.0.0",
            "elm/json": "1.1.3"
        },
        "indirect": {}
    },
    "test-dependencies": {
        "direct": {
            "elm-explorations/test": "1.2.2"
        },
        "indirect": {}
    }
}
```

### Common Elm Commands

**Building:**
```bash
# Compile to JavaScript
elm make src/Main.elm --output=main.js

# Compile with optimization
elm make src/Main.elm --output=main.js --optimize

# Compile multiple files
elm make src/Main.elm src/Admin.elm --output=dist/

# Debug build
elm make src/Main.elm --output=main.js --debug
```

**Development:**
```bash
# Start development server
elm reactor

# Install packages
elm install elm/http
elm install elm/json
elm install elm/random

# Format code
elm-format src/ --yes

# Run tests
elm-test
```

**Optimization:**
```bash
# Build optimized
elm make src/Main.elm --output=main.js --optimize

# Minify with uglify-js
uglifyjs main.js --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle --output main.min.js
```

### Project Structure

**Recommended Structure:**
```
my-elm-app/
├── elm.json                 # Package configuration
├── src/
│   ├── Main.elm            # Entry point
│   ├── Models/
│   │   ├── User.elm
│   │   └── Post.elm
│   ├── Views/
│   │   ├── Home.elm
│   │   ├── Profile.elm
│   │   └── Common/
│   │       ├── Header.elm
│   │       └── Footer.elm
│   ├── Updates/
│   │   ├── User.elm
│   │   └── Post.elm
│   ├── Api/
│   │   ├── User.elm
│   │   └── Post.elm
│   ├── Utils/
│   │   ├── Validators.elm
│   │   └── Formatters.elm
│   └── Ports.elm
├── tests/
│   └── Tests.elm
├── public/
│   ├── index.html
│   ├── styles.css
│   └── assets/
└── README.md
```

## Common Patterns

### Form Handling

**Complete Form Pattern:**
```elm
type alias Form =
    { email : String
    , password : String
    , errors : List String
    , isSubmitting : Bool
    }

type FormMsg
    = UpdateEmail String
    | UpdatePassword String
    | SubmitForm
    | FormSubmitted (Result Http.Error User)

initForm : Form
initForm =
    { email = ""
    , password = ""
    , errors = []
    , isSubmitting = False
    }

updateForm : FormMsg -> Form -> (Form, Cmd FormMsg)
updateForm msg form =
    case msg of
        UpdateEmail email ->
            ( { form | email = email }
            , Cmd.none
            )

        UpdatePassword password ->
            ( { form | password = password }
            , Cmd.none
            )

        SubmitForm ->
            case validateForm form of
                Ok validForm ->
                    ( { form | isSubmitting = True, errors = [] }
                    , submitForm validForm
                    )

                Err errors ->
                    ( { form | errors = errors }
                    , Cmd.none
                    )

        FormSubmitted result ->
            case result of
                Ok user ->
                    ( initForm
                    , Cmd.none
                    )

                Err _ ->
                    ( { form
                        | isSubmitting = False
                        , errors = ["Submission failed"]
                      }
                    , Cmd.none
                    )

validateForm : Form -> Result (List String) Form
validateForm form =
    let
        errors =
            [ validateEmail form.email
            , validatePassword form.password
            ]
            |> List.filterMap identity
    in
    if List.isEmpty errors then
        Ok form
    else
        Err errors

viewForm : Form -> Html FormMsg
viewForm form =
    Html.form [ onSubmit SubmitForm ]
        [ input
            [ type_ "email"
            , value form.email
            , onInput UpdateEmail
            , disabled form.isSubmitting
            ]
            []
        , input
            [ type_ "password"
            , value form.password
            , onInput UpdatePassword
            , disabled form.isSubmitting
            ]
            []
        , div [ class "errors" ]
            (List.map viewError form.errors)
        , button
            [ type_ "submit"
            , disabled form.isSubmitting
            ]
            [ text (if form.isSubmitting then "Submitting..." else "Submit") ]
        ]
```

### Routing (SPA Navigation)

**URL Routing with Browser.application:**
```elm
import Browser
import Browser.Navigation as Nav
import Url
import Url.Parser as Parser exposing (Parser, oneOf, s, int, (</>))

type Route
    = Home
    | Profile Int
    | Settings
    | NotFound

routeParser : Parser (Route -> a) a
routeParser =
    oneOf
        [ Parser.map Home Parser.top
        , Parser.map Profile (s "profile" </> int)
        , Parser.map Settings (s "settings")
        ]

fromUrl : Url.Url -> Route
fromUrl url =
    Parser.parse routeParser url
        |> Maybe.withDefault NotFound

type alias Model =
    { key : Nav.Key
    , route : Route
    }

type Msg
    = LinkClicked Browser.UrlRequest
    | UrlChanged Url.Url

init : () -> Url.Url -> Nav.Key -> (Model, Cmd Msg)
init _ url key =
    ( { key = key
      , route = fromUrl url
      }
    , Cmd.none
    )

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        LinkClicked urlRequest ->
            case urlRequest of
                Browser.Internal url ->
                    ( model
                    , Nav.pushUrl model.key (Url.toString url)
                    )

                Browser.External href ->
                    ( model
                    , Nav.load href
                    )

        UrlChanged url ->
            ( { model | route = fromUrl url }
            , Cmd.none
            )

view : Model -> Browser.Document Msg
view model =
    { title = "My App"
    , body =
        [ viewNavigation
        , viewRoute model.route
        ]
    }

viewRoute : Route -> Html Msg
viewRoute route =
    case route of
        Home ->
            viewHome

        Profile userId ->
            viewProfile userId

        Settings ->
            viewSettings

        NotFound ->
            div [] [ text "404 Not Found" ]

main : Program () Model Msg
main =
    Browser.application
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        , onUrlChange = UrlChanged
        , onUrlRequest = LinkClicked
        }
```

### Debouncing User Input

**Search with Debounce:**
```elm
type alias Model =
    { searchQuery : String
    , searchDebounce : Int
    , results : List Result
    }

type Msg
    = UpdateSearch String
    | DebounceTick Time.Posix
    | PerformSearch
    | GotResults (Result Http.Error (List Result))

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        UpdateSearch query ->
            ( { model
                | searchQuery = query
                , searchDebounce = 3  -- Wait 3 ticks
              }
            , Cmd.none
            )

        DebounceTick _ ->
            if model.searchDebounce > 0 then
                ( { model | searchDebounce = model.searchDebounce - 1 }
                , Cmd.none
                )
            else if model.searchQuery /= "" then
                update PerformSearch model
            else
                ( model, Cmd.none )

        PerformSearch ->
            ( model
            , searchApi model.searchQuery
            )

        GotResults result ->
            case result of
                Ok results ->
                    ( { model | results = results }
                    , Cmd.none
                    )

                Err _ ->
                    ( model, Cmd.none )

subscriptions : Model -> Sub Msg
subscriptions model =
    Time.every 300 DebounceTick  -- Check every 300ms
```

### Loading States

**Remote Data Pattern:**
```elm
type RemoteData error value
    = NotAsked
    | Loading
    | Success value
    | Failure error

type alias Model =
    { userData : RemoteData Http.Error User
    }

type Msg
    = FetchUser
    | GotUser (Result Http.Error User)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FetchUser ->
            ( { model | userData = Loading }
            , fetchUser
            )

        GotUser result ->
            case result of
                Ok user ->
                    ( { model | userData = Success user }
                    , Cmd.none
                    )

                Err error ->
                    ( { model | userData = Failure error }
                    , Cmd.none
                    )

viewUser : RemoteData Http.Error User -> Html Msg
viewUser userData =
    case userData of
        NotAsked ->
            button [ onClick FetchUser ] [ text "Load User" ]

        Loading ->
            div [ class "spinner" ] [ text "Loading..." ]

        Success user ->
            div [ class "user" ]
                [ h2 [] [ text user.name ]
                , p [] [ text user.email ]
                ]

        Failure error ->
            div [ class "error" ]
                [ text "Failed to load user"
                , button [ onClick FetchUser ] [ text "Retry" ]
                ]
```

## Best Practices

### Code Organization

1. **Keep Modules Small**: Each module should have a single responsibility
2. **Use Type Aliases**: Document complex types with meaningful names
3. **Expose Minimally**: Only expose what's needed from modules
4. **Group Related Code**: Keep related types, functions, and views together
5. **Consistent Naming**: Use clear, consistent naming conventions

### Type Safety

1. **Always Add Type Annotations**: For top-level functions
2. **Use Custom Types**: Instead of strings/ints for states
3. **Make Impossible States Impossible**: Design types to prevent invalid states
4. **Leverage Maybe and Result**: Never use defaults that hide errors
5. **Pattern Match Exhaustively**: Cover all cases explicitly

### Performance

1. **Use Html.Lazy**: For expensive view functions that rarely change
2. **Optimize List Operations**: Consider using Array for random access
3. **Batch Commands**: Use Cmd.batch for multiple effects
4. **Minimize Subscriptions**: Only subscribe when needed
5. **Profile Before Optimizing**: Use elm reactor debugger

### Testing

```elm
import Test exposing (..)
import Expect

suite : Test
suite =
    describe "User Validation"
        [ test "validates correct email" <|
            \_ ->
                validateEmail "test@example.com"
                    |> Expect.equal (Ok "test@example.com")

        , test "rejects invalid email" <|
            \_ ->
                validateEmail "invalid"
                    |> Expect.err

        , describe "Password validation"
            [ test "requires minimum length" <|
                \_ ->
                    validatePassword "short"
                        |> Expect.err

            , test "accepts valid password" <|
                \_ ->
                    validatePassword "securePassword123"
                        |> Expect.ok
            ]
        ]
```

### Error Handling

1. **Use Result for Validation**: Return meaningful error messages
2. **Handle All Http.Error Cases**: Provide user-friendly messages
3. **Display Errors Clearly**: Show validation errors next to form fields
4. **Provide Recovery Options**: Offer retry buttons, alternative actions
5. **Log Errors via Ports**: Send to external logging services

### Accessibility

```elm
-- Use semantic HTML
viewButton : String -> msg -> Html msg
viewButton label msg =
    button
        [ onClick msg
        , ariaLabel label
        ]
        [ text label ]

-- Provide keyboard navigation
viewModal : Html msg
viewModal =
    div
        [ role "dialog"
        , ariaModal True
        , tabindex -1
        ]
        [ -- modal content
        ]

-- Use ARIA attributes
viewStatus : String -> Html msg
viewStatus message =
    div
        [ ariaLive "polite"
        , ariaAtomic True
        ]
        [ text message ]
```

## Resources and Learning

**Official Resources:**
- Elm Guide: https://guide.elm-lang.org
- Package Documentation: https://package.elm-lang.org
- Elm Discourse: https://discourse.elm-lang.org
- Elm Slack: https://elmlang.herokuapp.com

**Key Packages:**
- elm/browser - Browser-based programs
- elm/html - HTML generation
- elm/http - HTTP requests
- elm/json - JSON encoding/decoding
- elm/random - Random value generation
- elm/time - Time and dates
- elm/url - URL parsing and building
- elm-explorations/test - Testing framework

**Learning Resources:**
- Elm in Action (book)
- Elm Radio (podcast)
- Elm Weekly (newsletter)
- Exercism Elm Track

---

**Skill Version**: 1.0.0
**Last Updated**: November 2025
**Skill Category**: Functional Programming, Web Development, Type-Safe Programming
**Compatible With**: JavaScript, Web Components, SPA Frameworks

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.