Deku logo

Core concepts

Components

Learn how to make a static page with Deku.

In the Hello world example, we saw how text ("Hello world") got transformed into something your eyeballs oggled or screenreader scraggled in the DOM. In Deku, as in most other frameworks from which Deku's ideas are liberally borrowed, we call these Components.


A simple component

Let's start by making a simple comonent. It will result in a few different DOM elements being rendered, and we’ll build upon it throughout this page. Here's the code.

View on GithubVITE_START=ASimpleComponent pnpm example
module Examples.ASimpleComponent where

import Deku.Toplevel (runInBody)
import Effect (Effect)
import Prelude
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

import Deku.Control (text_)
import Deku.Core (Nut)
import Deku.DOM as D
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody mySimpleComponent
  where
  -- `Nut` is the type of Deku components
  mySimpleComponent :: Nut
  mySimpleComponent =
    D.div_
      [ D.span__ "I exist"
      , D.ul_ $ map D.li__ [ "A", "B", "C" ]
      , D.div_
          [ D.h3__ "foo"
          , D.i__ "bar"
          , text_ " "
          , D.b__ "baz"
          ]
      ]
I exist
  • A
  • B
  • C

foo

bar baz

Before we proceed, it's important to establish a bit of Deku terminology that will come in handy as you shred through this documentation.

  1. An element is a DOM element in the MDN Documentation on Elements sense of the term. We will learn how to hook into the raw DOM in the Accessing the DOM section.
  2. A component is a PureScript term with type Nut, named affectionately after Deku nuts. Nut is the type produced by D.div_, D.span_ and the other DOM-building instructions above. But the concept of a component is broader than a one-to-one relationship with DOM elements - it can also represent the absence of DOM elements using mempty (which we’ll learn about later) or multiple DOM elements using useDyn (which we’ll also learn about later).

What's in D?

Compared to the imports from Hello world, there's one additional import here worth noting.

import Deku.DOM as D
The Deku.DOM module is the kitchen sink into which the entire DOM is thrown. You can find your favorites like D.li_, D.h3_, and D.blockquote_. You can even find exotic ones like the oft-overlooked D.colgroup_ and the pugnacious D.noscript_. Basically, if it's in the DOM, it's in Deku, and if you can't find what you’re looking for, you’ll learn to add it in the Custom elements section.

Arrays all the way down

Let's revisit the previous example, but we’ll expand it array-by-array to show how the Deku DOM is organized. The example starts with a single enclosing div created by the instruction D.div_.

main :: Effect Unit
main = runInBody
  ( D.div_
    [ ...
    ]
  )

The div contains three elements, a span, a ul, and another div.

[ D.span_ [ ... ]
, D.ul_ $ map D.li__ [ ... ]
, D.div_
  [ ...
  ]
]

You can have as many nested elements as you want. Deku makes no effort to check that your DOM's sensible. While a span in a button in a div makes sense, a div in a button in a span does not. But we won't stop you from doing it!

Malformed DOM

Arrays all the way down means arrays all the way down, including elements that can't have children. For example, <br /> can never take any children, and yet in Deku it is written D.br_ []. This is done because there are rare cases where you want to emit a malformed DOM, like when teaching someone how not to write code. In general, though, make sure to leave arrays empty for elements like <br /> that don't take any elements.

Lastly, we saw that the ul is created from mapping over strings in order to create Deku li components.

D.ul_ $ map D.li__ [ "A", "B", "C" ]

Deku is designed to allow you to use garden-variety functional programming patterns, like mapping over arrays, to build your DOM. In fact, that's how this documentation is built: all of the text you’re reading is stored in structures that Deku marshalls into the DOM.

Adding text

Text in deku is created via the text function. Additionally, for elements that only contain text, you can use a double-underscore. For example, D.i__ "Pow!"Pow!


Adding attributes

What fun would HTML be without any attributes? Un-fun, like the example above. Let's fix that and make our DOM nice and stylish! We'll use the same example as before, but spiced up with attributes.

View on GithubVITE_START=AddingAttributes pnpm example
module Examples.AddingAttributes where

import Deku.Toplevel (runInBody)
import Effect (Effect)
import Prelude
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

import Deku.DOM.Attributes as DA
import Deku.Control (text_)
import Deku.DOM as D
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody
  ( D.div_
      [ D.span
          [ DA.style_ "color:teal;" ]
          [ text_ "I exist" ]
      , D.ul_ $ map D.li__ [ "A", "B", "C" ]
      , D.div_
          [ D.h3
              [ DA.id_ "my-id"
              , DA.style_ "background-color:silver;"
              ]

              [ text_ "foo" ]
          , D.i [ DA.klass_ "text-2xl" ] [ text_ "bar" ]
          , text_ " "
          , D.b__ "baz"
          ]
      ]
  )
And here's the result.
I exist
  • A
  • B
  • C

foo

bar baz

Removing the underscore

To add attributes to a Deku DOM element, remove the underscore(s) from the element. Let's start with the spanfrom the example above.

D.span__ "I exist"
When we added the style attribute to make the color teal, we dropped the underscores. Furthermore, we added a text to typeset the text.
D.span
  [ DA.style_ "color:teal;" ]
  [ text_ "I exist" ]

Expand the text

When adding attributes to a component like the D.span above, make sure to also add an array with text_ followed by your text inside of it. This is because D.span__ "foo" is shorthand for D.span_ [ text_ "foo" ].

Setting attributes

Individual attributes like style or href are set with the DA.style_ or DA.href_ functions.

DA.style_ "color:teal;"

To feed an attribute to Deku, it needs to be in an array. Several attributes can be set by including them all in an array.

[ DA.id_ "my-id"
, DA.style_ "background-color:silver;"
]

¿Sì? Yes Yes!

PureScript has a delightful type-safe CSS library called purescript-css that you can use to author CSS, which is easier to debug than string-based styles.

import Deku.CSS as CSS
import CSS (color, red, display, block)

myStyleString = CSS.render do
  color red
  display block

Adding event handlers

The web would be quite uneventful without events. Be they clicks or scrolls or hovers, Deku is your one-stop-shop for event planning!

Using an effect

The most straightforward event is that which triggers an effectful action, like an alert or an audio snippet.

View on GithubVITE_START=UsingAnEffect pnpm example
module Examples.UsingAnEffect where

import Prelude
import Deku.Toplevel (runInBody)

import Deku.Control (text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Web.HTML (window)
import Web.HTML.Window (alert)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody do
  D.span
    [ DL.click_ \_ ->
        window >>= alert "Thanks for clicking!"
    , DA.klass_ "cursor-pointer"
    ]
    [ text_ "Click me!" ]
Click me!

Do be do be do

Functional programmers are notoriously lazy. In fact, there are entire languages, like Haskell, that are lazy. Part of our laziness is a disdain for parentheses, which not only requries two additional keystrokes, but forces us to press Shift at the same time. The gaul!

As a result, we often use do to separate blocks that would otherwise be in parentheses. do also pulls double duty (or doty if you’re into puns) and is used to write sequential computations. But when there's only one computation, like in the example above, it acts like parentheses. If you think it's unfair that one linguistic construct can do multiple things at the same time, try learning how many things the word です means in Japanese. It makes do look tame by comparison.

Using the DOM event

To use the original event emitted by the DOM, you can use the following syntax.

View on GithubVITE_START=UsingTheOriginalEvent pnpm example
module Examples.UsingTheOriginalEvent where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Newtype (unwrap)
import Deku.Control (text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Web.Event.Event (type_)
import Web.HTML (window)
import Web.HTML.Window (alert)
import Web.PointerEvent.PointerEvent (toEvent)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody do
  D.span
    [ DL.click_ \e -> do
        window >>= alert (unwrap (type_ $ toEvent e))
    , DA.klass_ "cursor-pointer"
    ]
    [ text_ "Click me!" ]
Click me!

Combinators, enums, and a third thing - oh my!

Often times, you won't want to use the full power of a listener but rather a shorthand version. For example, you may not care about the incoming event, or you may only want to grab a Number from said event. In these cases, Deku boasts a small and mighty library of listener cominators.

CombinatorMeaning
DL.runOnUse this listener with a simple effect
DL.numberOnGrab a number from this listener, and if there's no number, do nothing.
DL.checkedOnGrab a boolean from this listener, and if there's no boolean, do nothing.

In addition, there are also some attributes that act more like enums. This is especially true for the type attribute of the input element.

AttributeMeaning
DA.xtypeRange

Make an input's type range.

DA.xtypeButton

Make an input's type button.

The table above goes on for all valid input types.

I know this section heading promised a third thing, but there is no third thing. It feels like there should be, though, right? Like in this monologue.

As an example, consider the following slider. If you open your Developer Console and move the slider below, you’ll see it light up with numbers!

View on GithubVITE_START=ShorthandListeners pnpm example
module Examples.ShorthandListeners where

import Prelude
import Deku.Toplevel (runInBody)

import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import Effect.Class.Console (logShow)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody
  (D.input [ DA.xtypeRange, DL.numberOn_ DL.input logShow ] [])

While these events are fun, the true power of Deku lies in its ability to use events to change aspects of the DOM. The next section explores how that is done.