dtwrks / elm-ui-book / UIBook

A book that tells the story of the UI elements of your Elm application.

Start with a chapter.

You can create one chapter for each one of your UI elements and split it in sections to showcase all of their possible variants.

buttonsChapter : UIChapter x
buttonsChapter =
    chapter "Buttons"
        |> withSections
            [ ( "Default", button [] [] )
            , ( "Disabled", button [ disabled True ] [] )
            ]

Don't be limited by this pattern though. A chapter and its sections may be used however you want. For instance, if it's useful to have a catalog of possible colors or typographic styles in your documentation, why not dedicate a chapter to it?

chapter : String -> UIChapterBuilder state html

Creates a chapter with some title.

withSection : html -> UIChapterBuilder state html -> UIChapterCustom state html

Used for chapters with a single section.

inputChapter : UIChapter x
inputChapter =
    chapter "Input"
        |> withSection (input [] [])

withSections : List ( String, html ) -> UIChapterBuilder state html -> UIChapterCustom state html

Used for chapters with multiple sections.

buttonsChapter : UIChapter x
buttonsChapter =
    chapter "Buttons"
        |> withSections
            [ ( "Default", button [] [] )
            , ( "Disabled", button [ disabled True ] [] )
            ]

withBackgroundColor : String -> UIChapterBuilder state html -> UIChapterBuilder state html

Used for customizing the background color of a chapter's sections.

buttonsChapter : UIChapter x
buttonsChapter =
    chapter "Buttons"
        |> withBackgroundColor "#F0F"
        |> withSections
            [ ( "Default", button [] [] )
            , ( "Disabled", button [ disabled True ] [] )
            ]

withDescription : String -> UIChapterBuilder state html -> UIChapterBuilder state html

Used for adding a markdown description to your chapter.

withTwoColumns : UIChapterBuilder state html -> UIChapterBuilder state html

Used to customize your chapter with a two column layout.


type alias UIChapter state =
UIChapterCustom state (Html (UIBookMsg state))

Then, create your book.

Your UIBook is a collection of chapters.

book : UIBook ()
book =
    book "MyApp" ()
        |> withChapters
            [ colorsChapter
            , buttonsChapter
            , inputsChapter
            , chartsChapter
            ]

Important: Please note that you always need to use the withChapters functions as the final step of your setup.

This returns a standard Browser.application. You can choose to use it just as you would any Elm application – however, this package can also be added as a NPM dependency to be used as zero-config dev server to get things started.

If you want to use our zero-config dev server, just install elm-ui-book as a devDependency then run npx elm-ui-book {MyBookModule}.elm and you should see your brand new Book running on your browser.

book : String -> state -> UIBookBuilder state (Html (Msg state))

Kickoff the creation of an UIBook application.

withChapters : List (UIChapterCustom state html) -> UIBookBuilder state html -> UIBookCustom state html

List the chapters that should be displayed on your book.

Should be used as the final step on your setup.

withChapterGroups : List ( String, List (UIChapterCustom state html) ) -> UIBookBuilder state html -> UIBookCustom state html

List the chapters, divided by groups, that should be displayed on your book.

book "MyApp"
    |> withChapterGroups
        [ ( "Guides"
          , [ gettingStartedChapter
            , sendingRequestsChapter
            ]
          )
        , ( "UI Widgets"
          , [ buttonsChapter
            , formsChapter
            , ...
            ]
          )
        ]

Should be used as the final step on your setup.


type alias UIBook state =
UIBookCustom state (Html (UIBookMsg state))

Customize the book's style.

You can configure your book with a few extra settings to make it more personalized. Want to change the theme color so it's more fitting to your brand? Sure. Want to use your app's logo as the header? Go crazy.

book "MyApp" ()
    |> withColor "#007"
    |> withSubtitle "Design System"
    |> withChapters [ ... ]

withLogo : html -> UIBookBuilder state html -> UIBookBuilder state html

Customize the header logo to match your brand.

withSubtitle : String -> UIBookBuilder state html -> UIBookBuilder state html

Replace the default "UI Docs" subtitle with a custom one.

withHeader : html -> UIBookBuilder state html -> UIBookBuilder state html

Replace the entire header with a custom one.

book "MyApp"
    |> withHeader (h1 [ style "color" "crimson" ] [ text "My App" ])
    |> withChapters []

Note that your header must use the same type of html as your chapters. So if you're using elm-ui, then your header would need to be typed as Element msg.

withGlobals : List html -> UIBookBuilder state html -> UIBookBuilder state html

Add global elements to your book. This can be helpful for things like CSS resets.

For instance, if you're using elm-tailwind-modules, this would be really helpful:

import Css.Global exposing (global)
import Tailwind.Utilities exposing (globalStyles)
import UIBook.ElmCSS exposing (book)

book "MyApp"
    |> withGlobals [
        global globalStyles
    ]

withThemeBackground : String -> UIBookBuilder state html -> UIBookBuilder state html

Customize your book's background color. Any valid CSS background value can be used.

withThemeBackgroundAlt : String -> UIBookBuilder state html -> UIBookBuilder state html

Customize your book's background alt color. Any valid CSS background value can be used.

withThemeAccent : String -> UIBookBuilder state html -> UIBookBuilder state html

Customize your book's accent color. Any valid CSS color value can be used.

withThemeAccentAlt : String -> UIBookBuilder state html -> UIBookBuilder state html

Customize your book's accent alt color. Any valid CSS color value can be used.

themeBackground : String

Use your theme background color on other parts of your book.

chapter : UIChapter x
chapter
    |> withSection
        (p
            [ style "background" themeBackground ]
            [ text "Hello." ]
        )

themeBackgroundAlt : String

Use your theme background alt color on other parts of your book.

themeAccent : String

Use your theme accent color on other parts of your book.

chapter : UIChapter x
chapter
    |> withSection
        (p
            [ style "color" themeAccent ]
            [ text "Hello." ]
        )

themeAccentAlt : String

Use your theme accent alt color on other parts of your book.

withColor : String -> UIBookBuilder state html -> UIBookBuilder state html

[DEPRECATED] This has the same effect as withThemeBackground.

Integrate it with elm-css, elm-ui and others.

If you're using one of these two common ways of styling your Elm app, just import the proper definitions and you're good to go.

import UIBook exposing (withChapters)
import UIBook.ElmCSS exposing (UIBook, book)

main : UIBook ()
main =
    book "MyElmCSSApp" ()
        |> withChapters []

If you're using other packages that also work with a custom html, don't worry , defining a custom setup is pretty simple as well:

module UIBookCustom exposing (UIBook, UIChapter, book)

import MyCustomHtmlLibrary exposing (CustomHtml, toHtml)
import UIBook

type alias UIBookHtml state =
    CustomHtml (UIBook.UIBookMsg state)

type alias UIChapter state =
    UIBook.UIChapterCustom state (UIBookHtml state)

type alias UIBook state =
    UIBook.UIBookCustom state (UIBookHtml state)

book : String -> state -> UIBook.UIBookBuilder state (UIBookHtml state)
book title state =
    UIBook.customBook
        { title = title
        , state = state
        , toHtml = toHtml
        }

Then you can import UIBookCustom exposing (UIBook, UIChapter, book) just as you would with UIBook.ElmCSS.


type UIChapterCustom state html


type alias UIBookCustom state html =
Platform.Program () (Model state html) (Msg state)


type UIBookBuilder state html


type alias UIBookMsg state =
Msg state

customBook : { title : String, state : state, toHtml : html -> Html (Msg state) } -> UIBookBuilder state html

Interact with it.

Log your action intents to showcase how your components would react to interactions.

logAction : String -> Msg state

Logs an action that takes no inputs.

-- Will log "Clicked!" after pressing the button
button [ onClick <| logAction "Clicked!" ] []

logActionWithString : String -> String -> Msg state

Logs an action that takes one String input.

-- Will log "Input: x" after pressing the "x" key
input [ onInput <| logActionWithString "Input: " ] []

logActionWithInt : String -> String -> Msg state

Logs an action that takes one Int input.

logActionWithFloat : String -> String -> Msg state

Logs an action that takes one Float input.

logActionMap : String -> (value -> String) -> value -> Msg state

Logs an action that takes one generic input that can be transformed into a String.

eventToString : Event -> String
eventToString event =
    case event of
        Start ->
            "Start"

        Finish ->
            "Finish"

myCustomElement {
    onEvent =
        logActionMap "My Custom Element: " eventToString
}

Showcase stateful widgets

Sometimes it's useful to display a complex component so people can understand how it works on an isolated environment, not only see their possible static states. But how to accomplish this with Elm's static typing? Simply provide your own custom "state" that can be used and updated by your own elements.

type alias MyState =
    { input : String, counter : Int }

initialState : MyState
initialState =
    { input = "", counter = 0 }

main : UIBook MyState
main =
    book "MyStatefulApp" initialState
        |> withChapters
            [ inputChapter
            , counterChapter
            ]

counterChapter : UIChapter { x | counter : Int }
counterChapter =
    let
        updateCounter state =
            { state | counter = state.counter + 1 }
    in
    chapter "Counter"
        |> withStatefulSection
            (\state ->
                button
                    [ onClick (updateState updateCounter) ]
                    [ text <| String.fromInt state.counter ]
            )

inputChapter : UIChapter { x | input : String }
inputChapter =
    let
        updateInput value state =
            { state | input = value }
    in
    chapter "Input"
        |> withStatefulSection
            (\state ->
                input
                    [ value state.input
                    , onInput (updateState1 updateInput)
                    ]
                    []
            )

withStatefulSection : (state -> html) -> UIChapterBuilder state html -> UIChapterCustom state html

Used for chapters with a single stateful section.

withStatefulSections : List ( String, state -> html ) -> UIChapterBuilder state html -> UIChapterCustom state html

Used for chapters with multiple stateful sections.

This is often used for displaying one interactive section and then multiple sections showcasing static states. Check toStateful if you are instered in this setup.

toStateful : ( String, html ) -> UIChapterSection state html

Use this to make your life easier when mixing stateful and static sections.

chapter "ComplexWidget"
    |> withStatefulSections
        [ ( "Interactive", (\state -> ... ) )
        , toStateful ( "State1", widgetInState1 )
        , toStateful ( "State2", widgetInState1 )
        ]

updateState : (state -> state) -> Msg state

Updates the state of your stateful book.

counterChapter : UIChapter { x | counter : Int }
counterChapter =
    let
        update state =
            { state | counter = state.counter + 1 }
    in
    chapter "Counter"
        |> withStatefulSection
            (\state ->
                button
                    [ onClick (updateState update) ]
                    [ text <| String.fromInt state.counter ]
            )

updateState1 : (a -> state -> state) -> a -> Msg state

Used when updating the state based on an argument.

inputChapter : UIChapter { x | input : String }
inputChapter =
    let
        updateInput value state =
            { state | input = value }
    in
    chapter "Input"
        |> withStatefulSection
            (\state ->
                input
                    [ value state.input
                    , onInput (updateState1 updateInput)
                    ]
                    []
            )