Test-Driving the Elm Style Elements Library

Life Without CSS… Is it Possible?

billperegoy
im-becoming-functional
5 min readNov 28, 2017

--

CSS is Not My Friend

I was very intrigued when I watched Matthew Griffith’s talk at Elm Europe 2017. I’ve always steered away from diving deeply into CSS. It’s just a huge mental load for me. The language is complex, there are no guarantees that you are writing valid code and the confusion between layout and style is just overwhelming. It’s almost impossible to make a layout change without needing to touch both your HTML and CSS files. In addition, it’s very likely that a small layout change in one portion of your design will affect many other parts of your design. In general, I cringe anytime I need to make style changes to a website.

What is Elm Style Elements?

Elm Style Elements is a library that provides an alternative approach to managing layout and styles in Elm. The main idea behind the library is that style (color, font size, etc.) should be separate from layout (block spacing, sizes, wrap, etc.). Layout attributes are all handled within the view in a type-safe way and style attributes are handled outside the view in a way that feels a lot like we currently use CSS (but is type-safe).

On the surface, this may not sound like a major breakthrough but in practice, this simple shift can lower the cognitive overload of dealing with an intertwined mass of HTML and CSS.

There are a couple of key layout principles enforced by this package that make layouts more bullet-proof and less fragile.

  1. The core design element, el, can only have a single child element. If you want to represent multiple elements in a container, you must explicitly use a row or column element. This means that you cannot design with ambiguous design elements.
  2. There is no concept of margin in Style Elements. This ends up being a big win because without margin, it’s much harder for size changes to one sub-element to cause layout changes that adversely affect containing elements.

Here is a simple example of an element.

row MyRowStyle [ padding 10, spacing 7 ] 
[ el MyStyle [] empty
, el MyStyle [] empty
, el MyStyle [] empty
]

This creates a row of three elements with 7 pixels of spacing between elements and 10 pixels of padding outside of the block of three elements. This is easy to see with this diagram I’ve borrowed from the Style Elements Documentation (which is excellent and a must-read).

Simple Style Elements Row Example

The simplicity of this model makes life easier from the start. You have two simple concepts instead of the incredible complexity of margin, padding and border each individual element.

These elements can be nested in any logical way so you build larger components out of smaller ones while maintaining simple, modular Elm layout code.

You’ll note that the row element also references a style. Styles are maintained separately and feel a lot like a set of styles you’d apply to a single CSS class. Here’s a small example.

type MyStyles
= Title

stylesheet =
Style.syleSheet
[ Style.style Title
[ Color.text darkGrey
, Color.background white
, Font.size 5 -- all units given as px
]
]

So any element given the style of Title will get these color and font styles. These styles are type-checked and converted to inline styles as part of the compilation process. So the Elm compiler will find many problems before you test in the browser and there’s no need to deal with separate CSS files at all.

Test Driving the Library

This all looks great in theory, but how difficult would it be to build a simple page layout with these tools? I decided to build a very simple layout and check out the results. I wanted a basic page with a header, footer, sidebar and main content area. I also wanted a grid in the main content area that would wrap at different screen widths. I was looking to build this.

Simple Page Layout Example

Creating the Layout

The code to create this layout is shown below. You’ll see that I’ve wrapped several layers of containers with the pageArea element consisting of a column containing a header, content and footer.

view model =
Element.layout AppStyles.stylesheet <|
pageWrapper
pageWrapper =
Element.row AppStyles.PageStyle
[ padding 20
, paddingTop 0
, paddingBottom 0
]
[ pageArea ]
pageArea =
Element.column AppStyles.PageStyle
[ width (percent 100) ]
[ headerArea
, contentArea
, footerArea
]
headerArea =
el AppStyles.HeaderStyle
[]
(Element.text "Header")
footerArea =
el AppStyles.FooterStyle
[]
(Element.text "Footer")

The contentArea element is further broken down into the sidebar and main content as shown below.

contentArea =
Element.row AppStyles.ContentStyle
[]
[ sidebarArea
, mainContentArea
]
sidebarArea =
Element.column AppStyles.SidebarStyle
[ paddingLeft 20
, paddingTop 50
, width (percent 20)
]
[ Element.text "Sidebar"
, Element.text "Sidebar"
, Element.text "Sidebar"
, Element.text "Sidebar"
, Element.text "Sidebar"
]
mainContentArea model =
Element.wrappedRow AppStyles.BodyStyle
[ padding 10, spacing 7, width (percent 80)]
(blocks model)
blocks =
List.map (\elem -> (singleBlock elem))
[ "1", "2", "3", "4", "5", "6","7", "8"
, "9", "A", "B", "C", "D", "E", "F" ]
singleBlock value =
el AppStyles.BlockStyle
[ width (percent 33), height (px 100) ]
(Element.text value)

Again, we use the familiar Elm function composition to compose containers which include other containers. Note that all of the layout requirements are captured right here. The row and column elements describe the block relationships and padding, spacing and width describe the sizing and spacing.

Adding Styles

You’ll note that the code in the view contains no definition of styles. Each of the blocks does reference a style. I’ve defined all of the styles for this page in a separate Elm module that looks like this.

module AppStyles exposing (..)import Style exposing (..)
import Style.Font as Font
import Style.Color as Color
import Color exposing (..)
type MyStyles
= BlockStyle
| BodyStyle
| PageStyle
| ContentStyle
| HeaderStyle
| FooterStyle
| SidebarStyle
sansSerif =
Font.typeface [ Font.font "Helvetica" ]
stylesheet : StyleSheet MyStyles variation
stylesheet =
Style.styleSheet
[ Style.style BodyStyle
[ Color.background grey
]
, Style.style BlockStyle
[ sansSerif
, Color.text black
, Color.background yellow
, Font.size 50
]
, Style.style HeaderStyle
[ sansSerif
, Font.size 50
, Color.background darkGrey
]
, Style.style FooterStyle
[ sansSerif
, Font.size 50
, Color.background darkGrey
]
, Style.style SidebarStyle
[ sansSerif
, Font.size 20
, Color.background grey
]
, Style.style PageStyle []
, Style.style ContentStyle []
]

I import this module into my view and now I’ve achieved complete separation between styles and layout. Plus these styles are all type-checked and require no external CSS.

Summary

Elm Style Elements is a fairly new library but my brief test drive left me pretty impressed. It moves the layout right inside the view where I feel like it belongs and allows the styles to be separate and totally type-checked. If you want to see the example from this post, fully-fleshed out (including responsiveness), you can check out the git repository at: https://github.com/billperegoy/elm-page-layout.

You may be wondering how responsive pages are handled with Style Elements. Using Elm subscriptions, you can use the page size to dynamically change the view. I’ll be covering this in my next post.

--

--

billperegoy
im-becoming-functional

Polyglot programmer exploring the possibilities of functional programming.