The best way to handle style sheets in Miso application or component library
I would like to discuss the best way to handle style sheets in Miso application or component library. I think styles abstractions (whatever it is) should be:
- Composable with library/application Haskell code.
- Composable with each other.
- More or less typed (optional).
At the moment I see couple of options, but they all are kinda bad:
- Using the standard external CSS file. The problems are:
- I don't see the right way to export it as a part of the library (these are no
css-sourcesin cabal, unlikejs-sources) - CSS class names are just Text which should be the same across Haskell (
class_) and CSS codebase. Hard to maintain and refactor. Prone to errors.
- Using inline
<style>tags next to every small component. I think kinda bad idea, and I'm not sure how to scope styles locally to component this way. Not sure it is possible or easy (not an expert in CSS). - Using
style_attribute functions and different variants of compositions of it. It's a bit better, but:
- Can not define styles for pseudo-elements (::hover, ::before, ::after etc)
- Can not define advanced CSS animations like
@keyframes my-animation-name
- Doing some smart CSS code generation from some Haskell style sheet source (.hs files), exporting class names (functions, utilities) used in Miso lib/app codebase, but having additional Setup step for the app/lib which doing corresponding CSS file code generation somehow.
- Kinda sounds hard to do (maybe not, idk).
If I did miss some documentation or cool advanced CSS examples in Miso, please let me know. Best regards!
This is an important topic. There's a lot of different schools of thought on how to create and manage CSS in applications (CSS-in-JS, using preprocessors (sass, etc.), SMACSS, BEM, utility classes / tailwind). In an attempt to please everybody we did not make any hard decisions.
With that said, for preprocessors there is a project called clay that could be integrated with miso, providing additional type safety. Also I believe there are attempts to wrap tailwind classes with miso's class attribute (otherwise wouldn't be difficult to do this on your own).
@functora http://fvisser.nl/clay/ from @sebastiaanvisser
Also I believe there are attempts to wrap tailwind classes with miso's class attribute (otherwise wouldn't be difficult to do this on your own).
Hi @dmjio, thanks so much for this project. Do you have any references for those Tailwind+Miso integration attempts?
@functora,
I have come up with a partial solution for CSS in this PR https://github.com/dmjio/miso/pull/859
It adds styles :: [CSS] to App, and it will guarantee that your styles are present in Document before the first draw occurs (so no rendering w/o styles will occur). It's not a full CSS-in-JS approach, but it does push us in that direction.
We also now have Component which encapsulates an App, and this opens up the door to use styles in sub Component in a way that resembles the CSS-in-JS technique. So I'm still pondering that, but will not force a solution until it arises naturally (like Component). Am studying other techniques and libraries to see which is best for us.
This PR (#859) does solve an important problem which is the ops / dev effort around updating an external stylesheet, that is now ameliorated. So Todo-MVC requires no fiddling with index.html post-build. cc @georgefst @brakubraku
For defining inline styles (and maybe even for CSS-in-JS), we might want something like Clay, or @intolerable's stitch library. Kind of leaning towards stitch because it's more stringly-typed and therefore harder for the LLMs to get it wrong (they still do sadly, but not as bad as they do for clay since the API has changed a bit the LLMs generate code that doesn't compile).
@dhess
I was able to get some of the LLMs to generate miso's View code with Tailwind classes. It doesn't fully compile, but getting the class names on there is 90% of the work. The miso-from-html package actually worked better for this imo.
For now my recommendation is to convert the Tailwind Component library HTML/CSS code using this tool:
- https://github.com/dmjio/miso-from-html
Here are some attempts by DeepSeek to generate miso code for Tailwind CSS
- https://gist.github.com/dmjio/0851afa4898253cfa9d67fe323bd0f3a
- https://gist.github.com/dmjio/a029c45b73ea00cc40c6ff075df223f7
If we do decide to expose a stitch like interface into miso (Miso.Style), then we could also create a miso-from-css tool as well to generate the DSL code (just like we do with lists for View action in miso-from-html). Since the LLMs do get confused at times when it comes to Haskell DSL syntax
Something /like/ this could be generated, if the user wanted something inline, otherwise we could bias towards class-based approaches like tailwind in API and documentation, but will always allow users to do inline styles.
view :: Model -> View Action
view model = a_
[ onClick GoHome
, id_ "button"
, style_ buttonStyle
]
[ "Click Me"
]
buttonStyle :: Css
buttonStyle = do
"color" .= "#448"
"&:hover" ?
"color" .= "#44F"
"font" -: do
"family" .= "Open Sans, sans"
"size" .= "1.4em"
"weight" .= "bold"
@functora let me address each point you made above
At the moment I see couple of options, but they all are kinda bad: Using the standard external CSS file. The problems are:
I don't see the right way to export it as a part of the library (these are no
css-sourcesin cabal, unlikejs-sources)CSS class names are just Text which should be the same across Haskell (
class_) and CSS codebase. Hard to maintain and refactor. Prone to errors.
This should now be solved in #859, the browser will also cache this too (for cache busting append hash, etc.).
So I'd recommend using a newer GHC (like the @terrorjack's WASM backend) since it supports TemplateHaskell, the CSS can be read into the WASM payload at compile time and be appended to <head> before the page renders (this avoids any need for css-sources).
Using inline
<style>tags next to every small component. I think kinda bad idea, and I'm not sure how to scope styles locally to component this way. Not sure it is possible or easy (not an expert in CSS).
I agree we do need to come up with some way to style and export Component for reuse. Like described in the CSS-in-JS wikipedia
import styled from 'styled-components';
// Create a component that renders a <p> element with blue text
const BlueText = styled.p`
color: blue;
`;
<BlueText>My blue text</BlueText>
We can create our own BlueText Component as well. Since Component do hold styles :: [CSS]. So miso could, behind the scenes append a <style> for the user. We might need to extend CSS to be specific that these styles are applied locally to a Component. So I think an extension
data CSS
= Style MisoString
| Href MisoString
| ComponentStyle MisoString -- new constructor
Could be something that we experiment with. This will let styles be local to a Component, we might consider also removing them during the unmount phase as well. But I think we want to look to React for some inspiration
as well. But they seem pretty hands-off with respect to styling as well.
Per their docs,
Note that this functionality is not a part of React, but provided by third-party libraries. React does not have an opinion about how styles are defined; if in doubt, a good starting point is to define your styles in a separate *.css file as usual and refer to them using className.
But it does seem industry is gravitating towards component-local styling, and again we should be able to support now with #859
Using
style_attribute functions and different variants of compositions of it. It's a bit better, but: Can not define styles for pseudo-elements (::hover, ::before, ::after etc) Can not define advanced CSS animations like@keyframes my-animation-name
That is definitely a problem, and one that can be solved with a DSL and Component-local styling.
Doing some smart CSS code generation from some Haskell style sheet source (.hs files), exporting class names (functions, utilities) used in Miso lib/app codebase, but having additional Setup step for the app/lib which doing corresponding CSS file code generation somehow.
I'm a little reticent to generate CSS on behalf of the user (which I think you're alluding to here - correct me if I'm wrong), solely because it's easy to get it wrong since the state space of what they could want is large.
But I do agree we need a way to encapsulate styling logic to specific Component. I'm not convinced attempting to provide strong types for it is also a benefit (yet). It's also difficult to do w/o introducing GADTs given our formulation (which we will never do 🤞🏼 to keep things simple).
Kinda sounds hard to do (maybe not, idk) If I did miss some documentation or cool advanced CSS examples in Miso, please let me know. Best regards!
@functora feel free to join the Matrix chat to discuss Component local styling, there's a link to it on https://haskell-miso.org.
I like the aesthetic of this approach as well https://styled-components.com/ , and this is considered CSS-in-JS
I think it might be a good idea to make a CSS lexer, parser, QQ. This way we can verify statically semantically correct CSS, and also provide interpolation
https://github.com/dmjio/miso/pull/984
Also @functora , @dhess , regarding components and tailwind, this is a nice style (Shad minus React), uses tailwind. We will use this for the new Components example
https://basecoatui.com/
Also, a miso-tailwind integration is probably not going to happen formally because
- There's many thousands of tailwind classes, so we can't express all of these as defined functions
- It's just classes so it should be fine for users to just use
class_orclassList_
So, tailwind is supported technically already, but just use class_ or classList_, I might make classes :: [MisoString] -> Attribute action as well
https://github.com/dmjio/miso/commit/c5ed9cb3bbbd152ebe0f47739337bddb3a1cb681
@dhess @functora related to basecoat link above
https://tweakcn.com/
https://github.com/dmjio/miso/issues/1113
https://ui.haskell-miso.org