An Argument for Elm/Html

I’ve just released a blog post presenting my arguments for using Elm/html over Elm-UI and Elm-CSS.

It’s a topic I have been thinking about a lot. This is also why I stopped working on elm-ui-widgets. I can’t continue using Elm-UI. I tried to use Elm-CSS for a while, but then got frustrated.

I like both of them a lot. But they also have downsides. Downsides that one should know when picking the correct library for the job.

My approach is to build small utility functions based on elm/html that are simple, boring, and easy to copy.

I publish my experients under Orasund/elm-layout. The library contains 2 files, Layout.elm is my old apprach, Html.Style my new one. Both are still experiments. But you can just copy and tweak what ever you like.

7 Likes

I don’t really see a significant argument for Elm/Html over Elm-UI here.

The Elm-UI → Html style cutoff that you point out is indeed an important gotcha (that I wasn’t aware of, thanks!), but when working with pure html those are a dime a dozen. I always prefer to use Elm-UI exactly because it lifts me from HTML, abstracting away irrelevant concepts and simplifying my job.

As in most programming-related tasks, sometimes you have to go low level, but I’d hardly say that’s a good reason to do everything low level. Taking this to the extreme since Elm ports are sometimes cumbersome should we all just stick to classic Javascript?

About CSS evolving faster than Elm-CSS, I believe that to be an issue of the Elm community/ecosystem rather than a problem with the library. Using bindings and ports will always keep you a step behind, but that’s not a reason to drop every tool and just code everything at the lowest possible level.

There are projects where Elm-UI is the perfect tool. If you don’t need web-components and don’t rely on any elm/html packages (like some fancy date picker) then it’s the perfect tool.

But it has it’s faws (I just showed the biggest one). I came to a point where I constantly had to work my way around Elm-UI and trying to understand how it works under the hood was really difficult.

Modern HTML/CSS seemed to me to be at the same difficulty level. But you actually find a lot of nice resources, guides and tutorial on how to do things.

Elm-CSS not being able to keep up with modern CSS is a direct downside from it being typed. I believe that with the speed at which CSS is currently progressing, you’re better off not using types all together.

A counter argument would be that it would take you 10 minutes to fork rtfeldman/elm-css and add Css.height Css.fitContent support and publish your fork. Don’t wait for rtfeldman/elm-css to add the feature, that (original) fork is unmaintained now anyway.

1 Like

You don’t even need to pay the cost of a fork. Just add

heightFitContent: Style
heightFitContent =
  property "height" "fit-content"

to your application code and you’re good.

3 Likes

Yes. One can fork elm-css and there are a couple of forks published that added some features (Shout out to dzuk-mutant/elm-css for rewriting elm-css using phantom types). The problem is, however, that there isn’t one CSS version. It’s split into multiple modules that get updated independently of each other. So you can expect to constantly have breaking changes. It’s not something that is sustainable to maintain.

Good Point. I hadn’t thought of that.

Edit:
Maybe a counterargument for both approaches would be that if Elm-CSS is constantly changing, then writing any libraries on top of them is nearly impossible. (as Elm does not support having multiple version of the same library or different libraries with the same modules).

I love Elm because it’s so easy to reason about.

You wouldn’t put your business logic in seperate JS files and use ports, so why put your ‘view logic’ in a bunch of seperate CSS files?

With Elm-UI I can look at my view code and easily visualize it. I’ve often created fairly complicated views without needing to look at it in a browser. It’s there, right in front of me; this is a column of rows, with this colour border, that animates into view when this part of the model changes etc.

I love the fact that I no longer need to think about CSS, and it would take an exceptional circumstance for me to consider ever using CSS directly again. I’m not saying your not right, just not right for me :man_shrugging: :grinning:

2 Likes

I still like elm-css. The way I tend to use it is through Css.Global to define classes then use the class names in Html. So I am mostly using Elm as a better sass/compass CSS compiler. I started with a CSS design scheme that used compass and converted it to Elm. Also nice because when you do need a little bit of inline dynamic CSS you are doing it with the same DSL you write the static CSS with. Helpful to be able to manipulate CSS with Elm code, adding functions to CSS itself will always be a less satisfying programming experience that having the full power of Elm to help you do it.

For example, a scheme for making use of CSS media queries for responsive design, is the sort of the thing where compass is used to generate CSS classes. The code for something like that can get pretty complicated, but with elm-css you can write all that CSS logic in Elm :chefs_kiss:

I try to keep the dynamic stuff to a minimum, so animate with CSS transitions where possible. I wonder if elm-css could be extended to provide better support for doing this. You do have to keep in mind that elm-ui is more performant than elm-css, which is partly why I chose the approach above.

1 Like

Good article!

Yeah, I personally decided against Elm UI myself because I remember looking at it once and remembering it lacked really fine controls I want to use (like for instance, forcing browsers to style inputs and render graphics the exact way I want them to) and also because I wanted to keep my CSS knowledge fresh (I am hesitant about Tailwind for this reason too). But for simpler applications or folks who are not like, extremely deep into CSS, it makes sense.

And yeah, maintaining elm-css is very much a constantly moving target. I spent many hours writing tens of thousands of lines of code to update functionality of the original elm-css package to phantom types and it’s still not a complete successor. I’m personally fine writing property "height" "fit-content" when something doesn’t fit but I understand if it feels like it starts defeating the point, especially if you find you have to do that a lot.

After many years of personal experience with CSS (either by itself or with a preprocessor), I personally decided I did not want to write CSS again without some serious guardrails, so elm-css works well enough for me for now. It allows me to get closer to the Elm ideal of writing a web application and walking away.

My main qualm with elm-css right now is that stylesheet compilation happens at runtime instead of build time (I know there has been some talk about fixing this, but only ideas and proof of concepts so far.)

Yeah, this is a pretty tough problem with elm-css. I maintain a package for handling screen size metrics that can’t help but depend on it. And I inevitably I forget to update until I realise I can’t update elm-css without pushing an update to this package. -.- I think this is made even worse by the fact that elm-css depends on forking elm/html, which means Html stuff is in a bad place too. (I wonder if perhaps this situation could be improved by splitting the Html.Styled and the Css stuff into separate packages…)

FWIW, I am very much open for PRs on my fork of elm-css, it might take me a couple of weeks to check it, but it will get seen and merged.

2 Likes

I have a crazy idea: Generate the functions for you.

MDN actually generates their CSS docs from an open source repos containing json files.

So my wild idea would be to create a job that regularly checks if that repos has any new commits and if so get the new json files and build Elm files from it. No need to manually maintain the library.

Now, this is definitely no minor task. And surely im missing something major. But maybe it’s worth exploring?

2 Likes

I’m not sure that would be feasible. CSS is not a strongly typed language so there’s a lot of things that have to have oversight and be redesigned so that they work as typechecked things in Elm. A good example is my implementation of grid properties. In order to add type guarantees and prevent name clashes I basically had to come up with a different way of writing them:

gridColumn auto

gridColumn inherit

gridColumn <| gridLineIdent "main-grid" (Just 3)
-- grid-column: main-grid 3

gridColumn2 auto ( gridLineSpanIdent "grid-thing") (Just 5) )
-- grid-column: auto / span grid-thing 5

-- (it gets even more divergent with more complicated examples)

It’s also fairly simple to add properties and values to the elm-css package now because of phantom types (especially with the height: fit-content; example).

For me the problems with elm-css being updated is:

  • Knowing when CSS standards change.
  • Having folks who have the time to push small updates to keep it going.
  • (Also finishing areas missing from the original package so folks don’t have to decide between the original and my fork.)

I think more in-depth programming tasks around it would be better spent on things like performance.

2 Likes

Yes, this would be ideal. Codegen an elm-css that can easily be refreshed in order to remain constantly up to date.

(turns out in my fork, height does support fit-content values, I just forgot :sweat_smile:, not that that fixes the overall issues with updating, but I just wanted to correct the record)

height fitContent

height <| fitContentTo (px 20)

Hi all. I’m still struggling to understand why anyone would want to bundle their CSS within their Elm files — other than adding css classes.

After seeing Richard Feldman’s “Make Data Structures” talk it seems pretty clear that thinking in components (like you might do with TypeScript or Nuxt) in Elm isn’t wise — but that’s how most JS frameworks do it.

I’ve done a lot of work with CSS in the past (often in component style) and it’s often frustrating to work with, but mixing your code logic with styling doesn’t make sense to me — perhaps others could enlighten me!

The two extremes I know of are ECSS and Tailwind. I created my own framework for typography a while back too.

With ECSS your component CSS lives with your component JS — so this mightn’t work well with Elm — I’m new to it so haven’t tried that approach yet. With ECSS you’d add the component classes with HTML.Attributes and I guess use classList where necessary to manage state.

I’m still not 100% comfortable with the way Elm does HTML (it still feels a bit unnatural) but perhaps using Elm Watch and a simple css compiler is all you need. (I generally use .less only for breaking up files into manageable chunks)

So your Html.Style approach looks interesting, but what’s the benefit of using that over plain old CSS?


TL;DR CSS is not, and probably never will be a proper programming language and is full of quirks, so trying to make it so (even with simple compilers like .less) I’ve found to be a mistake.

I realized with Elm that styling is not any special programming situation. instead styling has the same problems that any programming problem has (duplication etc), but css doesn’t give you the tools to solve them, because it’s not a powerful programming language.

functions and variables are all you need, and nothing less. in Elm, I can always easily replicate the simple reference-based style reuse that css does. but I could never add a simple if statement to css. in css, I always have to use classes and then swap those in a JavaScript if.

furthermore, I don’t think it makes sense to separate the Dom from the style. in practice, when changing the style, I practically always have to add or modify some container div in the HTML anyways. what purpose is the separation, if layout and colors are so tightly intertwined? I like the idea of using inline attributes only, but re-using them with functions and variables.

I agree with your post.

I haven’t fully made up my mind about typed CSS but I tend to lean towards YAGNI.

Regarding elm-ui, I find that it makes trying/debugging CSS rules on the fly via the browser’s dev tools much harder so for that reason it’s not my cup of tea :slightly_smiling_face:

Also I suspect one finds it annoying if one already knows his way around CSS.

This subject of code organization (CSS) isn’t tied to type safety IMO.

Also, the impact of a CSS mistake is tiny in comparison to app logic which is where the hard stuff happens.

2 Likes

I found myself facing the opposite issue. A lot of my issues when it comes to complicated views is cognitive load, and from my own experiences working with web stuff that splits code kinda like ECSS does (eg. Ember.js, not the same but there are definite similarities) I find splitting code across different files increases cognitive load for me:

  • I have to constantly switch contexts (and languages/syntaxes) to work on different parts of the same thing.
  • I have to have two tabs/panels open in order to work on one thing
  • Having multiple references (folder and multiple files) for the name of something makes refactoring difficult and increases cognitive load, especially without specialised plugins.
  • Having multiple formats of naming the same thing increases cognitive load for me and makes refactoring annoying (eg. in Ember, a component could be called label-list in filenames, but then LabelList as a custom HTML component).

This is compared to a local elm-css approach where encapsulating HTML structure and styling logic in one function in one module makes refactoring and changing names trivial because the naming and filename format in Elm is almost entirely consistent and also the compiler will complain if a name reference is broken whereas in a pure CSS approach it will just happen quietly.

Combining HTML structure and styles also helps me scan my views and recognise what the holistic graphical context is (maybe this is important to me because I come from a purely graphical design background). There’s also the bonus of being able to integrate Elm interactivity with the CSS, making the CSS change based on the Model instead of having to change a class reference and writing the class representing those graphical changes somewhere else (increasing the amount of loose references you have to keep in mind when refactoring, and increasing the chance of broken references causing confusing bugs).

I will also say in my own practice I don’t immediately reach for a component structure, I create views (with embedded elm-css styling) in the same module of the data structures they represent, but there are also situations where views don’t represent any particular data (eg. if you’re making reusable form inputs), and that’s where components do make sense for me personally.

4 Likes

This hits so close to home for me. 1.5M+ lines of Ember and having 2+ files open for N components was a nightmare to work in.

Svelte was slightly better because it was at least all in 1 file, but your file became separated horizontally into

code
––––––––
styles
––––––––
layout

and you have to know 3 different syntax for working in one file, 1 SvelteJS, 1 for Svelte css, and 1 for Svelte html.


I mentioned this on Slack, but now days my setup is usually https://picocss.com/ plus @ryannhg/css-in-elm - npm. Pico gives me a solid starting point without having to use any custom styling or layout. Then when I need something unique I use css-in-elm and I get dead code elimination with elm-review (it tells me when a class is unused so I can delete it everywhere) and I get the full power of CSS without having to figure out types. They also fit well for me because I’ve been writing HTML and CSS for 20+ years so I know roughly what I want.

This also means I never have to think about mapping between one layout package and another. I love elm-ui, and use that as a guide for my custom stuff sometimes, but I really disliked having to wrap & unwrap when moving between layout code from different packages, or even having to wrap my own code.

1 Like

I think you could put the pros and cons of different techniques on a chart (there are perhaps more pros and cons but off the top of my head):

elm-css

  • :white_check_mark: Using CSS without others making assumptions and decisions for you
  • :white_check_mark: Not needing to switch programming contexts or files for the same view
  • :white_check_mark: Enforced correctness of style declarations
  • :white_check_mark: Enforced correctness of style references (Optional but it is the default)
  • :x: Best performance
  • :x: Easy package dependencies
  • :x: CSS without intermediaries

elm-ui

  • :x: Using CSS without others making assumptions and decisions for you
  • :white_check_mark: Not needing to switch programming contexts or files for the same view
  • :white_check_mark: Enforced correctness of style declarations
  • :white_check_mark: Enforced correctness of style references
  • :wavy_dash: Best performance (better than elm-css but worse than static IIRC)
  • :wavy_dash: Easy package dependencies (probably better than elm-css)
  • :x: CSS without intermediaries

External CSS

  • :white_check_mark: Using CSS without others making assumptions and decisions for you
  • :x: Not needing to switch programming contexts or files for the same view
  • :x: Enforced correctness of style declarations
  • :x: Enforced correctness of style references
  • :white_check_mark: Best performance
  • :white_check_mark: Easy package dependencies
  • :white_check_mark: CSS without intermediaries

And some of these things not being met are a feature not a bug to its target audience - eg. the whole point of Elm UI is that it’s for folks who don’t want to have to deal with CSS, the whole point of elm-css is that you’re not writing bare CSS, etc.

I like that in Elm there are these different types of options available that try to approach the drawbacks of CSS in their own way and folks can take what they need and leave what they don’t.

4 Likes

Hmm. @dzuk @wolfadex I see your points. I come from a design background too, so historically I’d do a design->layout->template workflow, so I guess it depends on your preferred way of working. In general you’d be building design systems with a team.

With frameworks like twig it’s not too hard to work with either method I mentioned, as I’d generally get the layout looking right first and add in the template logic, but perhaps this is harder to do in Elm.

I think there’s a package somewhere that converts plain HTML to Elm syntax. So that could work too.

I do understand that it’s a bit of a pain to have multiple files that are essentially doing the same thing though (HTML and Elm HTML).

There’s also the bonus of being able to integrate Elm interactivity with the CSS, making the CSS change based on the Model instead of having to change a class reference and writing the class representing those graphical changes somewhere else

Do you have any examples of this?

I will also say in my own practice I don’t immediately reach for a component structure, I create views (with embedded elm-css styling) in the same module of the data structures they represent

Yeah, starting with the model seems to make sense and building out from there. I guess time will tell the best way of building things — interesting that you still build out some reusable components.

Pico looks pretty cool and lightweight. I’m not sure I’m sold on using CSS as functions though (although the ability to remove dead code sounds nice). So yeah, I dunno. I feel like I’d be leaning towards keeping my Elm code lean and CSS separate but each to their own!

One thing I’ve noticed though is the HTML/JS is sometimes getting cached, so you have to purge it — is there some flag you can set that does this for you?