Responsive Design with Elm Style Elements

The Elm Architecture Has You Covered

billperegoy
im-becoming-functional
5 min readDec 5, 2017

--

About Style Elements

In my last article, I demonstrated how you could use the Elm Style Elements to build an Elm application with no CSS. Style Elements are used to separate styling of individual elements from the layout of your page. With Style Elements, all layout information is unambiguously represented right in your Elm view.

But How Do I Make Things Responsive?

I’ve been really happy with Style Elements. It removes so much of the ambiguity of CSS and also simplifies my thinking as all of the layout knowledge lives in one place — right in the view. But, in the real world, we still have more work to do. We need to make out page responsive so it works well with a number of screen sizes.

It turns out these capabilities are built into the Elm architecture. With Elm we can create a responsive page using these techniques.

  1. We use an Elm subscription to detect screen size changes and trigger an update event.
  2. This update event turns that screen size into a type representing one of a finite number of supported screen sizes.
  3. On each update, the screen size is stored in the model.
  4. With each model change, the view is recomputed and the screen size value can be used to change the way the page is rendered.

Planning Our Responsive Design

Looking back at our last, unresponsive layout, it looked like this.

Unresponsive Page

To make this responsive, we want to change the number of columns of tiles as the page shrinks horizontally like this.

Smaller Screen Size

Finally, as we shrink down to a phone-sized screen, we will use a single column and get rid of the sidebar completely.

Phone-sized Screen

Building the Subscription

The first step in adding responsiveness to our page is to set up an Elm subscription that creates an update event every time the screen size changes. This is very straightforward.

import Window exposing (resizes)
import Model exposing (..)
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [ Window.resizes SetScreenSize ]

This will send the screenSize as part of an update message named Set ScreenSize. We then need to define that message type.

type Msg
= NoOp
| SetScreenSize Window.Size

Processing the Update Message

Now that we have a subscription that will fire an event on each screen size change, we need to write the code that will process that event and store it in the model.

First, we will define a type that describes the screen sizes we’d like to represent.

type ScreenSize
= Phone
| Tablet
| Desktop
| BigDesktop

Then, we’ll write a function that can convert a Window.Size record into this screen size type. The code is once again very simple.

getScreenSize : Window.Size -> ScreenSize
getScreenSize size =
if size.width <= 600 then
Phone
else if size.width <= 1200 then
Tablet
else if size.width <= 1800 then
Desktop
else
BigDesktop

To continue, we will need a place to store the screen size in our model. Let’s redefine our model as follows.

type alias Model =
{ screenSize : ScreenSize
}

Finally, we write an update function that stores the screen size when the update function is called with the SetScreenSize message type.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
model ! []
SetScreenSize size ->
{ model | screenSize = getScreenSize size } ! []

With all of this work in place, we now have a model that will always contain the current screen size. With that in place, let’s make our model react to the model changes to add responsive behavior.

Making Our View Responsive

Looking back at our unresponsive view, we defined the number of columns in the grid with code like this.

singleBlock value =
el AppStyles.BlockStyle
[ width (percent 33), height (px 100) ]
(Element.text value)

Note that the width is defined as a percentage. All we need to do is make this width a function of the screen size. Let’s try out this function.

blockAttributes screenSize =
case screenSize of
Phone ->
[ width (percent 100), height (px 100) ]
Tablet ->
[ width (percent 50), height (px 100) ]
Desktop ->
[ width (percent 33), height (px 100) ]
BigDesktop ->
[ width (percent 25), height (px 100) ]

With this function in place, our element is displayed with this code.

singleBlock model value =
el AppStyles.BlockStyle
(blockAttributes model.screenSize)
(Element.text value)

Next, we’d like to make the sidebar go away if we are on a phone. Again, it’s a simple conditional.

sidebarArea : Model -> Element AppStyles.MyStyles variation Msg
sidebarArea model =
if model.screenSize == Phone then
Element.empty
else
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"
]

Finally, we want to make the main area change from using 80% of the page to 100% on a phone.

bodyWidth screenSize =
if screenSize == Phone then
width (percent 100)
else
width (percent 80)
mainContentArea : Model -> Element AppStyles.MyStyles variation Msg
mainContentArea model =
Element.wrappedRow AppStyles.BodyStyle
[ padding 10, spacing 7, (bodyWidth model.screenSize) ]
(blocks model)

That’s it! We have a fully responsive page using nothing but the standard Elm architecture.

Summary

Working with responsive pages in CSS usually drives me wild. We have code spread between the HTML and multiple places in the CSS files. Debugging responsive errors generally involves tedious use of your browser debugger and often a moderate amount to tears. By moving the responsive code right into your Elm model and view, you can use the Elm debugger to track down issues. I find that using the time-travel debugger allows me to track down issues quickly without jumping between different files. Using Elm Style elements and the Elm architecture makes creating responsive web pages as simple as I’ve ever seen it.

You can find a complete working example of the application described here at: https://github.com/billperegoy/elm-page-layout.

--

--

billperegoy
im-becoming-functional

Polyglot programmer exploring the possibilities of functional programming.