DEV Community

Dwayne Crooks
Dwayne Crooks

Posted on

Change CSS Properties from Elm with CSS Custom Properties

CSS custom properties (sometimes called CSS variables) are like variables but not quite. The key distinguishing feature of a custom property is that it is tied to a selector and if its value changes, it affects all matching DOM elements just like any other CSS property. This cannot be achieved with preprocessor variables, JavaScript variables, or Elm variables.

Learn more: A Strategy Guide To CSS Custom Properties.

Suppose you want to draw some circles on a canvas. For e.g. like in the Circle Drawer task from 7GUIs. One way to do it would be to use inline styles. joakin takes this approach in viewCircle:

--
-- N.B. The unnecessary parts have been removed for clarity.
--
viewCircle : Circle -> Html msg
viewCircle { x, y, radius } =
    div
        [ style "position" "absolute"
        , style "left" <| px x
        , style "top" <| px y
        , style "width" <| px (radius * 2)
        , style "height" <| px (radius * 2)
        , style "transform" <|
            "translateX(-"
                ++ px radius
                ++ ")"
                ++ "translateY(-"
                ++ px radius
                ++ ")"
        , style "border-radius" "50%"
        , style "border" "1px solid black"
        , style "background-color" "white"
        ]
        []
    )
Enter fullscreen mode Exit fullscreen mode

It certainly gets the job done but it feels icky to me. I prefer to use an external CSS stylesheet and use Elm to control which styles get applied via CSS classes. I've found that in practice you get a cleaner separation between your business logic and design.

Let me show you what I mean.

In the external CSS stylesheet I'd put:

.circle {
    --circle-x: 0;
    --circle-y: 0;
    --circle-diameter: 30px;

    position: absolute;
    left: var(--circle-x);
    top: var(--circle-y);
    transform: translate(-50%, -50%);

    inline-size: var(--circle-diameter);
    aspect-ratio: 1;
    border-radius: 50%;

    border: 1px solid black;
    background-color: white;
}
Enter fullscreen mode Exit fullscreen mode

The CSS variables --circle-x, --circle-y, and --circle-diameter allow me to control the position and size of the circle.

Normally my next step would be to create a few modifier classes.

.circle--small {
    --circle-x: 5px;
    --circle-y: 5px;
    --circle-diameter: 25px;
}

.circle--medium {
    --circle-x: 10px;
    --circle-y: 10px;
    --circle-diameter: 50px;
}

.circle--large {
    --circle-x: 15px;
    --circle-y: 15px;
    --circle-diameter: 75px;
}
Enter fullscreen mode Exit fullscreen mode

But since there can be many positions and sizes of circles a finite predetermined collection of modifier classes won't do. Instead I'd have to set the CSS variables from Elm.

My viewCircle becomes:

viewCircle : Circle -> H.Html msg
viewCircle { position, diameter } =
    H.div
        [ HA.class "circle"
        , HA.customProperties
            [ ( "circle-x", String.fromInt position.x ++ "px" )
            , ( "circle-y", String.fromInt position.y ++ "px" )
            , ( "circle-diameter", Diameter.toString diameter ++ "px" )
            ]
        ]
        []
Enter fullscreen mode Exit fullscreen mode

customProperties is a helper function that uses one style attribute to set custom properties. It makes use of the inline property pattern.

customProperties : List ( String, String ) -> H.Attribute msg
customProperties =
    List.map (\( name, value ) -> "--" ++ name ++ ": " ++ value)
        >> String.join "; "
        >> HA.attribute "style"
Enter fullscreen mode Exit fullscreen mode

With this approach you get to cleanly expose the configurable parts of the .circle class.

Conclusion

In this article I showed you how to change CSS properties from Elm with CSS custom properties. In particular, I used the example of drawing circles on a canvas to illustrate how it could be done. We needed to change the CSS properties left, top, and inline-size in order to change the positions and sizes of our circles. To do so we used the CSS custom properties --circle-x, --circle-y, and --circle-diameter to expose select "knobs of control" in an otherwise black box of immutable circle styles. In Elm, we then used the inline property pattern to "turn the knobs" and customize the circles. This helped us achieve a better separation of concerns between our business logic and design.

To me that's a huge win for CSS custom properties. What do you think?

Further Reading

Here are some rough notes I collected on using custom properties in Elm.

And here are some quality resources on effectively making use of custom properties:

Top comments (0)