In Elm 0.19, they removed the ability for third party packages to provide native bindings for JavaScript APIs that the language or its official packages didn't yet support.[0] The only alternative proposed by Evan is to use ports, which are about as ergonomic as calling a function in a web worker.
there is also web components (best way to do a "copy this to clipboard" button in my experience) and secret back door synchronous FFI through overloading the Object prototype. In all cases the communication is gated through decoders/encoders that can fail; you can only pass serialized data across the boundary from JS to Elm.
The compiler has hard coded domains that control who can use certain features. For example, custom operators aren’t allowed, but Evan is able to use them.
I encourage you to read the whole thing, but the standout quote for me is near the end:
> One thing I learned from discovering The Elm Architecture is that it is really lovely to be able to show up in any codebase and know what is going on. I think custom operators detract from that enough that they are not worth it for the whole ecosystem, even if they are great for specific individuals.
Having talked to people at work who had to remove custom operators when they upgraded from 0.18 to 0.19, they initially didn't love dropping them, but getting rid of the syntax cliff for new hires at the expense of terseness for the old hands was a decent tradeoff. Personally, I find it can be easy in an ML family language to get a bit wrapped around the axle trying to write the most terse, pointfree implementation of something.