This package exposes a Haskell-generated template Digital Subscriber Line (DSL) of HTML tags, as well as properties. While not complete, this package is intended to eventually house utilities for web APIs.

Named Tags

HTML tags each get four named functions, with the following semantic:

Variant Properties Required Children Required










Prime + Underscore



or you can see the semantic as follows:

x [] = x_
flip x [] = x'
x [] [] = x'_
h "x" = x

You also supply some overload strings for convenience:

div "foo" [ "hiya" ]
> <div class="foo">hiya</div>

Event Listeners

Event listeners each get named functions:

  • A plain one, for pure event handlers (synchronous)

  • An M version for monadic handlers (asynchronous)

  • A C variant for working with Continuation s. (asynchronous)

onClick :: a -> (Text, Prop m a)
onClickM :: Monad m => m (a -> a) -> (Text, Prop m a)
onClickM_ :: Monad m => m () -> (Text, Prop m a)
onClickC :: Continuation m a -> (Text, Prop m a)

Each of these functions is named the same as in JavaScript (sans Camel-case). See that the following are morally equivalent:

div.addEventListener("click", () => model++);
div' [ onClick (model + 1) ]

You will also note that synchronous handlers take an a and asynchronous handlers are m (a → a). This is because your Monadic action may be asynchronous and the state of the application may have changed during that time. The returned function (a → a) will be applied as a state update to whatever that state is once the action completes. Should you need to query the state of the running application in the middle of asynchronous processing, use a Continuation m a.


Some listeners provide extra information. For example:

onInput :: (Text -> a) -> (Text, Prop m a)

where the Text will be the current value of the input element, preventing us from having to write low-level code to extract the value.


Instead of orienting around attributes, we orient around properties in Shpadoinkle. This is largely because you can set any attribute by properties, but not vice-versa. Credit due to alkali which introduced the author to this observation.

In most cases the named function will have the same name it would have in JavaScript. For example, the following are morally equivalent:

const div = document.createElement("div");
div.className = "foo";
div' [ className "foo" ]

Not all properties have the same type. While we try to provide semantic types for the properties involved, this is not yet always the case. Please feel free to submit MR’s if you encounter impedances, or would like to suggest a function.

CSS Class

This is by far the most common property to set. And so extra affordances are offered here. These are provide in outlaw fashion with typeclasses.

div' "foo bar"
div' [ class' "foo bar" ]
div' [ class' ("foo bar", True) ]
div' [ class' ["foo", "bar"] ]
div' [ class' [("foo", True), ("bar", True), ("baz", False)] ]
div' [ class' $ "foo" <> ("bar", True) ]

will all render as

<div class="foo bar" ></div>

You can also use the ClassList newtype, to build up classes for an element.


The memo function remembers the last argument passed and the last result. When a new value is passed, it compares it with the previous value, and if they match, the previous result is returned from memory. This is useful when computing Html is expensive and benefits from memoization:

memo = id

Think of memo as a way of changing the evaluation strategy:

view :: Text -> Html m a
view = memo $ \t ->
  div_ . take (length t) . repeat . text $ expensiveFilter t