What is the 'msg' in 'HTML msg' in Elm actually?

tl;dr: as long as every function agrees on the type of msg, and model, they can be anything you want.

Just like the type parameter of List, msg can be anything. There is no restriction. To demonstrate, here's the classic increment/decrement example with the msg being just an Int:

-- These type aliases aren't needed, but makes it easier to distinguish
-- their roles in later type signatures
type alias Model = Int
type alias Msg = Int


update : Msg -> Model -> Model
update msg model =
    model + msg


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


main : Program () Model Msg
main =
    Browser.sandbox { init = 0, view = view, update = update }

So how does that work? The key is in the Browser.sandbox function, as that's what connects everything together. Its type signature is:

sandbox :
    { init : model
    , view : model -> Html msg
    , update : msg -> model -> model
    }
    -> Program () model msg

where model and msg are type variables. Both of these can be replaced with any concrete type, as long as it matches up with the constraints given in this signature, which is that they must be the same across the init, view and update functions. Otherwise we'll get a type error here. If, for example, update requires a String instead of an Int, we'll get the error:

This argument is a record of type:

    { init : Model, update : String -> Model -> Model, view : Model -> Html Msg
    }

But `sandbox` needs the 1st argument to be:

    { init : Model
    , update : String -> Model -> Model
    , view : Model -> Html String
    }

It doesn't really know which is right or wrong, of course, just that there's a mismatch. But in an effort to be helpful it assumes String is right and expects view to return Html String instead.

I hope that sufficiently answers your question. If not, comment and subscribe!


Short and Simple Explanation:

I have an xmas function like this:

wrapPresents : presents -> String

But what is the type, presents? It can be anything that you want! You can substitute it with your own 'concrete type'. presents is just a place holder!

wrapPresents : Boxes -> String

Now we have the "type" - we know that the first parameter must be a Box type.

wrapPresents boxes = "All the boxes are now wrapped!"

Summary: msg (lowercase) is simply a placeholder