CSS in Elm
Back in 2003, I got my first freelance programming job, which involved slicing up a PSD. I landed it because I was specifically writing XHTML and CSS, a vast departure from the "old school" method of writing 800 lines of nested tables. If you told me back then that I'd be convincing people to try writing CSS in a functional, typed language, instead of in a stylesheet, well... I wouldn't have really understood what that meant but I would have definitely been against it.
Now, I'm not advocating for writing all of your CSS in JS or Elm; in most cases, using a preprocessor like Sass, Less, or Stylus, combined with a linter, like stylelint, will get you a lot of mileage. But if you want your styles to share the same type system as the rest of your code, elm-css is very cool.
So, first, if you check out the Elm Css module's documentation, you'll notice that every CSS property and attribute is defined in Elm. This was definitely a heroic effort, as evidenced by the sheer number of properties that had to be translated. The result is amazing though: instead of writing inline styles as text (style "background-color" "#fff"
), you can now write type-checked CSS directly.
This is both powerful and convenient, because instead of needing Sass and Stylus, you get composability and validation with the same toolchain that you're already using to write Elm. And it's super easy to isolate your styles into a separate module: for example, in a teensy RPG engine I'm making, I have a Styles/Tileset.elm
file for isolating my tile-related CSS. Check out the code below to get an idea of just how simple elm-css
is.
module Styles.Tileset exposing (tile)
import Css exposing (..)
import Html.Styled exposing (..)
assetPath : String
assetPath =
-- This is hardcoded but would likely come from `model.flags`
"/assets/tileset.png"
{-| The tiles are 16x16, but we want to display at 32x32.
-}
scale : Float
scale =
2
bgSize : ( Float, Float )
bgSize =
-- This is hardcoded for the sake of this example.
( 256, 480 )
scaledBgSize : ( Float, Float )
scaledBgSize =
Tuple.mapBoth ((*) scale) ((*) scale) bgSize
tileSize : Float
tileSize =
16
scaledTileSize : Float
scaledTileSize =
tileSize * scale
tile : Int -> Int -> List Style
tile x y =
let
offsetX =
toFloat x * scaledTileSize
offsetY =
toFloat y * scaledTileSize
( bgSizeWidth, bgSizeHeight ) =
scaledBgSize
in
[ backgroundImage (url assetPath)
, backgroundSize2 (px bgSizeWidth) (px bgSizeHeight)
, height (px scaledTileSize)
, width (px scaledTileSize)
, backgroundPosition2 (px -offsetX) (px -offsetY)
]