{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidwyrgrfdfyl7sid7wxheoha7zrggsrnvnfnmepblybqz36stsyd4",
    "uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mnaqebqwl7v2"
  },
  "path": "/t/ann-miso-css-verified-css-class-application-via-dependent-types/14187#post_1",
  "publishedAt": "2026-06-01T18:36:43.000Z",
  "site": "https://discourse.haskell.org",
  "tags": [
    "miso-css",
    "miso"
  ],
  "textContent": "Hi,\n\nmiso-css is an evolutionary step from **css-class-bindings**.\n\nCSS class of an atomic selector can be applied to any DOM element, but that is not true for classes used in composite selectors. Rules with partially matched selectors are silently ignored by browser and this opens door bugs during consequent changes. **css-class-binding** just cannot cope with such problem and **miso-css** uses dependent types to track what CSS classes can applied to HTML elements.\n\n### Composing tags\n\nBefore jumping straight to style application lets get familiar with syntax for tag composition because it is different in vanilla miso.\n\n#### Appending a child\n\n\n    div_ </ p_\n\n\n\n    <div>\n      <p></p>\n    </div>\n\n\n#### Adding a sibling\n\n\n    ul_ </ li_ </ li_\n\n\n\n    <ul>\n      <li></li>\n      <li></li>\n    </ul>\n\n\n#### Appending a child to child\n\n\n    body_ </ (section_ </ p_)\n\n\n\n    <body>\n      <section>\n        <p></p>\n      </section>\n    </body>\n\n\n#### Adding CDATA\n\n\n    a_ <@ \"click\"\n\n\n\n    <a>click</a>\n\n\n#### Adding a raw miso DOM chunk\n\n\n    import Miso.Html qualified as MH\n    import Miso.Html.Property qualified as MH\n\n    go = div_ =< MH.p_ [] [ \"h\" ]\n\n\n\n    <div>\n      <p>h</p>\n    </div>\n\n\n#### Adding tag attribute\n\n\n    a_ =<| atr @\"href\" \"http://link.com\"\n\n\n\n    <a href=\"http://link.com\"></a>\n\n\n#### Binding event handler\n\n\n    button_ =! onClick YourActionDc\n\n\n#### Applying CSS class\n\n\n    {-# LANGUAGE QuasiQuotes #-}\n    {-# OPTIONS_GHC -Wno-missing-signatures #-}\n    [css|.red { color: red; }|]\n\n    div_ =. red\n\n\n\n    <div class=\"red\"></div>\n\n\n#### Adding tag ID\n\nHandmade tag id:\n\n\n    div_ =# ElementId \"footer\"\n\n\nGenerated tag id:\n\n\n    {-# LANGUAGE QuasiQuotes #-}\n    {-# OPTIONS_GHC -Wno-missing-signatures #-}\n    [css|#footer { color: red; }|]\n\n    div_ =# Footer\n\n\n\n    <div id=\"footer\"></div>\n\n\n#### Mix all at once\n\n\n    {-# LANGUAGE QuasiQuotes #-}\n    {-# OPTIONS_GHC -Wno-missing-signatures #-}\n    [css|.form .red { color: red; }|]\n\n    div_ =. form =# ElementId \"footer\"\n      </ (a_ =. red =<| atr @\"href\" \"/click.php?x=1\"\n          </ (span_ <@ \"Click me\"))\n\n\n\n    <div class=\"form\" id=\"footer>\n      <a class=\"red\" href=\"/click.php?x=1\">\n        <span>Click me</span>\n      </a>\n    </div>\n\n\n### Breaking rules\n\nUntil now all above samples must be valid and should type\ncheck. This section enumerates HTML snippets with ill-applied\nclasses, expected errors and comments.\n\n#### There can be only one\n\nAn element ID can be used once in a HTML document.\n\n\n    div_ =# ElementId \"Duncan MacLeod\"\n      </ div_ =# ElementId \"Duncan MacLeod\"\n\n\n\n    Couldn't match type: '[DuplicatedId \"Duncan MacLeod\"]\n                   with: '[]\n\n\n#### Parent class is missing\n\n\n    [css|.a .b {}|]\n\n    div_ =. b\n\n\nThe error message is a list of triples where first element is a list of not\napplied classes, ids (hashes), tag names or attribute names.\n\n\n    [([C \"a\"], [], [])]\n\n\nClass **a** and **b** are missing:\n\n\n    [css|.a .b .c {}|]\n\n    div_ =. c\n\n\n\n    [ ([C \"b\"], [], [])\n    , ([C \"a\"], [], [])\n    ]\n\n\n#### B element\n\nWhen selector with a child relation is partially applied the triple\ncontains B element. It is a synthetic element preventing the failed\nrule from matching later somewhere upper in DOM by an accident.\n\n\n    [css|.a > .b {}|]\n\n    div_ </ div_ =. b\n\n\n\n    [([B, C \"a\"], [], [])]\n\n\n#### One of classes is missing\n\nSecond element of triple is a list of applied classes. It helps to\nunderstand what worked out and what didn’t in a composite selector.\n\n\n    [css|.a.b > .c {}|]\n\n    div_ =. a </ div_ =. c\n\n\n\n    [([B, C \"b\"], [C \"a\"], [])]\n\n\n##### Sibling is missing\n\nThe third element of triple explains sibling errors.\n\n\n    [css|.a + .b {}|]\n\n    div_ </ div_ =. b\n\n\nClass **a** is not applied:\n\n\n    [([B], [], [[ [B], [C \"a\"]]])]\n\n\n### Hello World\n\n\n    {-# LANGUAGE QuasiQuotes #-}\n    {-# OPTIONS_GHC -Wno-missing-signatures #-}\n    module Miso.Css.Test.HelloWorld where\n\n    import Miso ( component, App, CSS(Style), Component(styles), View )\n    import Miso.Css\n    import Prelude\n\n    type Model = ()\n    type Action = ()\n\n    -- default name is \"cssAsLiteralText\"\n    renameCssTextConst \"cssFromQq\"\n\n    [css|\n    .c .b .a {\n      color: #fc2c2c;\n    }\n    |]\n\n    -- instead of quasi-quoted CSS\n    -- the whole CSS file can be included with:\n    --   includeCss \"assets/style.css\"\n\n    app :: App Model Action\n    app = (component () pure viewModel)\n      { styles = [ Style cssFromQq ] }\n\n    {-\n    viewModel produce following HTML snippet:\n\n        <div class=\"c\">\n          <div class=\"b\">\n            <button class=\"a\">\n              Submit\n            </button>\n          </div>\n        </div>\n\n    html_ and body_ don't produce tags,\n    because miso mount cannot be higher than body tag.\n\n    they serve just for type checking purpose\n    (e.g. html_ satisfies :root pseudo class)\n\n    -}\n    viewModel :: Model -> View Model Action\n    viewModel () = toView . html_ . body_ $\n      div_ =. c\n      </ (div_ =. b\n           </ (button_ =. a\n                <@ \"Submit\"))\n",
  "title": "[ANN] miso-css: verified CSS class application via dependent types"
}