[042.4] Style Elements

Using the style-elements package to style our Elm applications in style.

Subscribe now

Style Elements [07.03.2017]

Today we're going to take a break from the Single Page App we've been working on to explore style-elements, which is a package for creating styles for elements, naturally enough. From the README, The Style Elements library is a new set of primitives for working with layout and style in Elm. We'll use it on the elm-emoji-picker I've been building for Firestorm. Let's get started.

Project

We're starting with the dailydrip/elm-emoji-picker repo tagged before this episode.

First, let's look at the app as it stands:

./run.sh
# Visit it in the browser

Here we have an emoji picker. It looks OK, but I'd like to iterate on the styles a bit and it seemed like an easy way to get started with style-elements.

We'll start by installing the package:

elm-package install -y mdgriffith/style-elements

Right now we're using some CSS in this project. Let's remove it and stop loading it:

git rm style.css
vim index.html
# Then remove the stylesheet link tag

If we run the project again, we can see that the styles have been removed.

./run.sh

style-elements consists of Styles and Elements. We'll make a Styles module of our own, creating a basic stylesheet with no applicable styles, as a good basic starting point. We'll give a name to all of the things we want to style eventually, in a union type.

vim Styles.elm
module Styles exposing (Styles(..), stylesheet)

import Style exposing (..)


{-| Here's where we define all of the named styles that exist in our app.

It's nice to have a blank style.

-}
type Styles
    = None
    | SelectedEmoji
    | EmojiList
    | Emoji


{-| Then, we define a stylesheet. This describes all of the properties that
aren't related to layout, position, or size.
-}
stylesheet : StyleSheet Styles variations
stylesheet =
    Style.stylesheet
        [ style None [] -- our None style adds no styles.
        , style SelectedEmoji
            []
        , style EmojiList
            []
        , style Emoji
            []
        ]

Next, let's wire this into Main and replace our view with style-elements layout functions to lay it out.

vim Main.elm
module Main exposing (..)

import Dict exposing (Dict)
-- Element is a module from style-elements, and the attributes and events
-- largely mirror those of elm-lang/html
import Element exposing (..)
import Element.Attributes exposing (..)
import Element.Events exposing (onClick, onInput)
import Emoji exposing (Emoji, emojis)
-- We only need the Html type itself now
-- And we don't need to import Html attributes or events
import Html exposing (Html)
import Ports
-- We'll also pull in our Styles so we can specify them on various elements
import Styles exposing (Styles(..))
-- ...
-- Then our view uses Element functions
view : Model -> Html Msg
view model =
    let
        -- ...
    in
    -- We use Element.root for the base of it, passing it the stylesheet that
    -- should apply inside this area
    Element.root Styles.stylesheet <|
        -- We'll just lay everything out in a column for now
        column None
            []
            -- We'll include an element that's 400px wide and centered
            [ el None [ center, width (px 400) ] <|
                -- And inside of that element we'll lay things out in columns
                -- again
                column None
                    []
                    -- We'll include an input...
                    [ inputText None
                        [ onInput UpdatePrefix
                        , placeholder "Search for an emoji"
                        ]
                        model.searchString
                    -- ... our selected emoji
                    , el SelectedEmoji [] <| text selectedEmojiString
                    -- ... and the list of emoji we've filtered to
                    , viewEmojiList model.searchString
                    ]
            ]


-- The list of emoji will just be a column for now
viewEmojiList : String -> Element Styles variation Msg
viewEmojiList searchPrefix =
    let
        -- ...
    in
    column EmojiList [] <|
        List.map viewEmoji filteredEmoji


-- And an emoji is an element with the Emoji style
viewEmoji : ( String, Emoji ) -> Element Styles variation Msg
viewEmoji ( key, ( emojiString, emojiName, commonNames ) as emoji ) =
    el Emoji [ onClick <| SelectEmoji emoji ] <| text emojiString

If we check out the app now, it's mostly workable. Next, I'd like to make the emoji have some padding, and change background color when hovered:

vim Styles.elm
module Styles exposing (Styles(..), stylesheet)

import Color exposing (rgba)
import Style exposing (..)
import Style.Color as Color
-- ...
colors =
    { lightGrey = rgba 230 230 230 1
    , transparent = rgba 0 0 0 0
    }


{-| Then, we define a stylesheet. This describes all of the properties that
aren't related to layout, position, or size.
-}
stylesheet : StyleSheet Styles variations
stylesheet =
    Style.stylesheet
        [ -- ...
        , style Emoji
            [ Color.background colors.transparent
            , hover
                [ Color.background colors.lightGrey
                ]
            ]
        ]

That takes care of the background color. Padding is a layout concern, so we add it on the element in the view:

vim Main.elm
-- ...
viewEmoji : ( String, Emoji ) -> Element Styles variation Msg
viewEmoji ( key, ( emojiString, emojiName, commonNames ) as emoji ) =
    el Emoji
        [ onClick <| SelectEmoji emoji
        , padding 6
        ]
    <|
        text emojiString

Now if we look, we can see that as we hover the emoji they get a background color. This is still a pretty ugly experience though, so let's make rows of emoji in a grid, by using flexbox.

We'll turn the emojiList into a wrappedRow element, which will make the children wrap as they outgrow the width of the box:

viewEmojiList : String -> Element Styles variation Msg
viewEmojiList searchPrefix =
    let
        -- ...
    in
    wrappedRow EmojiList [] <|
        List.map viewEmoji filteredEmoji

Then in viewEmoji we'll specify a width and height, and center the emoji inside:

viewEmoji : ( String, Emoji ) -> Element Styles variation Msg
viewEmoji ( key, ( emojiString, emojiName, commonNames ) as emoji ) =
    el Emoji
        [ onClick <| SelectEmoji emoji
        , width (px 40)
        , height (px 40)
        ]
    <|
        el None [ center, verticalCenter ] <|
            text emojiString

Now it shows up as a nice grid. We can click on the emoji, so let's modify the styles to make the cursor a pointer when we hover them:

module Styles exposing (Styles(..), stylesheet)
-- ...
stylesheet : StyleSheet Styles variations
stylesheet =
    Style.stylesheet
        [ -- ...
        , style Emoji
            [ Color.background colors.transparent
            , hover
                [ Color.background colors.lightGrey
                ]
            , cursor "pointer"
            ]
        ]

With that, things are looking alright. I think this is a fine place to stop.

Summary

In today's episode we implemented styles for our emoji picker using style-elements. There's a lot to like about this package. A thing I don't currently love is how wordy the resulting HTML is, as there are inline styles on each element. However, there's a lot of work going on on this package and it's very nice that it frees us from thinking of CSS directly in a lot of cases. Let me know if you use it and build something cool. See you soon!

Resources