Composing Styles in Elm (Beginners)
I’ve recently started learning Elm and I’ve been excited to find this to be a very powerful language. I thought I’d share some tips as I learn. This post will be about structuring Elm-defined CSS styles in a way that they can be easily composed together.
My target audience for this post is users new to Elm. If you’re already familiar with Elm or Haskell, this will be pretty trivial, but if you’re new to functional programming, the syntax may confuse you!
To see what we’re going to build, you can open up the demo here.
Problems Setting Styles
Styles can be easily applied to elements in Elm, but modifying an existing style is a little bit trickier. The trouble lies in the generation of the style Attribute object; once this is set, it doesn’t appear that it can be modified. I may just be misreading the APIs and have missed something obvious, but so far I haven’t found an explicit way to do this. Partly, this is due to immutability of objects in Elm, but this particular API also doesn’t appear to easily allow extraction of data from Attribute objects.
Building composable styles to build up into the Attribute object is simple and easy. I’ll walk you through how to do this.
Why would this even be necessary? Just like with CSS classes, it’s helpful to have predetermined styles that you may want to apply across many different elements. Rather than redefining the same styles over and over again, you can define style segments that you can then compose into the final element’s style attribute.
Set Up and Run
(First, make sure you have elm installed.)
I’ve published the code in this post to a github repo.
You can run the example as-is with the following commands (I’ve included the command output for reference):
> git clone https://github.com/stormont/elm-minimal-styles.git Cloning into 'elm-minimal-styles'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (9/9), done. remote: Total 11 (delta 0), reused 7 (delta 0), pack-reused 0 Unpacking objects: 100% (11/11), done. Checking connectivity... done. > cd elm-minimal-styles > git checkout 2015.10.24.01 > elm-make src/Main.elm --output=demo.html Some new packages are needed. Here is the upgrade plan. Install: elm-lang/core 2.1.0 evancz/elm-html 4.0.1 evancz/virtual-dom 2.1.0 Do you approve of this plan? (y/n) y Downloading elm-lang/core Downloading evancz/elm-html Downloading evancz/virtual-dom Packages configured successfully! Success! Compiled 37 modules. Successfully generated demo.html
At this point, you’ll have generated a demo.html file, which you can open in your browser and observe the result. (This is the same output I linked to from the beginning of the post.)
As mentioned earlier, it’s pretty trivial. All we’ve done is:
- Generate an h3 tag.
- Set the cursor style to “pointer”.
- Made the text non-selectable.
Now, let’s walk through the code segments! (If you’re following along using the sample code from the Github repo, we’ll mainly be walking backwards through the Main.elm file, from bottom to top.)
Creating Some Styles
Use a pointer style
First, we’ll generate the style definition for the pointer cursor:
pointerCursorStyle : List (String, String) pointerCursorStyle = [ ("cursor", "pointer") ]
The first line is our function type declaration. Note that Elm doesn’t require function type declarations; it can infer them automatically. But, it’s good practice to include them, as they aid developer understanding.
We’re defining a function named pointerCursorStyle that returns a List of tuples of type (String, String). The definition being used in our function here is that the first String is the CSS style key, while the second String is the value for that style key. If you’re unfamiliar with tuples, you can think of them like this: Lists contain a potentially infinite set of values, but the values must all be the same type; a tuple will contain a set of values of a specific size, but the values don’t have to have the same types.
The second and third lines are our function definition. Following standard Elm convention, we put the left-hand side of the definition on the first line and the remainder on subsequent lines, but you can put this on one line (it just isn’t encouraged).
The last line is our actual CSS style definition. This is equivalent to something like the following CSS class definition:
.pointerCursorStyle { cursor: pointer; }
Non-selectable text
To make our text non-selectable, we define the following:
noTextSelectionStyle : List (String, String) noTextSelectionStyle = [ ("-webkit-touch-callout", "none") , ("-webkit-user-select", "none") , ("-khtml-user-select", "none") , ("-moz-user-select", "none") , ("-ms-user-select", "none") , ("-user-select", "none") ]
Again, we’re using the same key/value pair tuple type for our List type definition.
For the actual style definitions, I’ve used the top result (at the time of this writing) from this StackOverflow post. In case this changes in the future, it’s the equivalent of this CSS class definition:
.noTextSelectionStyle { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }
Minor changes
This code works as-is, but I prefer a somewhat different style that Evan introduced in his Elm Architecture Tutorial. It gets rid of some of the extra parentheses and makes the code read a bit more obviously, if we write a new function definition on a separate line (make sure it’s all on its own, with no leading spaces!):
(=>) = (,)
This looks a little obscure, but it basically works like this: Using a tuple is actually applying a function definition (,), which basically says “create a two-value tuple”, such as ( “x” , “y” ). We’ve simply defined a new operator, =>, which does the same thing without the parentheses or the comma.
With this definition, we can re-write the above definitions like this:
pointerCursorStyle : List (String, String) pointerCursorStyle = [ "cursor" => "pointer" ] noTextSelectionStyle : List (String, String) noTextSelectionStyle = [ "-webkit-touch-callout" => "none" , "-webkit-user-select" => "none" , "-khtml-user-select" => "none" , "-moz-user-select" => "none" , "-ms-user-select" => "none" , "-user-select" => "none" ]
This reads a little more clearly to me, but your mileage may vary.
Composing the Styles Together
Now that we’ve defined a couple of different styles, we can build a simple helper function to compose them together:
buildStyle : List (List (String, String)) -> Html.Attribute buildStyle styleLists = Html.Attributes.style <| List.concat styleLists
The function type declaration on the first line tells us that we’re taking a List of a List of (String, String) objects and generating an Attribute object out of them.
The Attribute type is defined in the Html module; you can set up your module imports to use shorthand that allows removing the “Html.” prefixes, but I’m leaving them in here to explicitly show which module these definitions are coming from.
In order to include the Html module, you’ll need to add a couple of import statements to the top of your file:
import Html import Html.Attributes
We also import the Html.Attributes module, because it contains the definition of the style function. We’ll get to that in a minute.
You may be wondering, why does our function take a List of Lists? Well, recall the style functions that we wrote; these returned a List (String, String). In order to compose more than one of these definitions together, we’re going to use a List of that inner List type.
The second line of our buildStyles function begins the definition. We need to specify that we’re expecting an input variable for our List type, which we call styleLists.
To break down the function definition in the third line, we’ll walk from the right to the left:
List.concat styleLists
This tells us to concatenate the List (List (String, String)) type down into a single List (String, String) type. concat comes from the List module, which I’ve again explicitly notated here. We don’t have to import List because it’s part of the base “prelude” set of imports that every Elm module gets by default.
Html.Attributes.style <|
This funny-looking <| operator says “take whatever I’m given on the right hand side and apply it to the function on the left hand side”. So, we’re applying our newly concatenated List (String, String) to the style function, which outputs our final Attribute object.
While Elm can define these kinds of unusual-looking operators (unlike many languages), they’re kept to a minimum by convention, because they can be difficult for beginners to understand and difficult to search for. An easy rule of thumb if you see an operator the an angle bracket is to remember that data flows in the direction of the bracket; in this case, we’re moving from right to left.
To define the same thing flowing left to right, we could also write:
List.concat styleLists |> Html.Attributes.style
Using our Work
Now that we’ve defined a few style definitions and a helper function to compose them together, let’s use what we’ve built!
main = Html.h3 [ buildStyle [ pointerCursorStyle , noTextSelectionStyle ] ] [ Html.text "Hello world!" ]
I’ve omitted the function type for main here, but the compiler infers it to be:
main : Html.Html
I’ve left it off mainly because our main function, which is required by Elm, will never really be defined like this in practice. We could just as easily declare a different function with the exact same implementation.
In our implementation, we’re building an h3 element, using Html.h3. The convention of the Html module is that each DOM element expects a List of Attributes, followed by a List of inner Html elements.
[ buildStyle [ pointerCursorStyle , noTextSelectionStyle ] ]
This builds a List of Attributes, using our previously written definitions. We pass buildStyle a List containing the various styles that we want to apply.
Why the funny multi-line syntax? It’s partly a matter of taste, but you’ll likely find that lists become a lot easier to understand when each element is put on a new line, particularly as the list becomes very long. I tend to use the multi-line style anytime I have more than one element in my list. You could also write the above like this:
[ buildStyle [pointerCursorStyle, noTextSelectionStyle] ]
The final list in our input to Html.h3 is the inner DOM elements to render. In this case, we just want to display simple text:
[ Html.text "Hello world!" ]
And we’re done! If you compile (following the steps in the Set Up and Run section) and view demo.html in your browser, you should see a “Hello world!” h3 element that uses a pointer cursor and doesn’t allow you to select the text.
Conclusion
We’ve done a walkthrough of a beginner-friendly introduction to Elm, but we’ve also introduced something useful in practice: A handy function for composing style definitions. We’ve done this by separating our data generators from our data consumers; if you run into trouble modifying styles in Elm, it’s likely because you aren’t leveraging that separation of responsibility.
I’m just starting to learn Elm myself, but I hope to share more tidbits as I discover them for myself. This post was targeted to beginners, but Elm has a number of very interesting and non-trivial tools that give it a lot of power. So far, it’s been a fascinating journey!
One comment