# \[ANN\] css-class-bindings library generates Haskell bindings for CSS classes

**URL:** <https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857>\
**Category:** Announcements\
**Created:** [March 28, 2026, 7:52pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857 "2026-03-28T19:52:12Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![dyaitskov](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/dyaitskov/32/5142_2.png) [@dyaitskov](https://discourse.haskell.org/u/dyaitskov)\
**Post date:** [March 28, 2026, 7:52pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/1 "2026-03-28T19:52:12Z")

</div>

Recently I migrated a frontend to Miso library (WASM backend), and I noticed that DOM functions (e.g. `div_`) accept CSS class names as plain strings.

#### Quasi-quote input

```haskell
{-# LANGUAGE QuasiQuotes #-}
module Css where
import CssClassBindings ( css )

[css|
.foo-bar {
  color: #fc2c2c;
}
|]

```

```haskell
module Main where

import Css (fooBar, cssAsLiteralText)
import CssClassBindings qualified as C
import Miso
import Miso.Html.Element (div_, button_)
import Miso.Html.Property qualified as P

class_ :: C.CssClass MisoString -> Attribute action
class_ = P.class_ . C.class_

app :: App Model Action
app = (component emptyModel updateModel viewModel)
  { styles = [Style cssAsLiteralText]
  }

viewModel :: Model -> View Model Action
viewModel m = div_ [] [button_ [ class_ fooBar] ["Submit"] ]

```

#### File input

```haskell
import CssClassBindings ( includeCss )
includeCss "assets/style.css"

```

```haskell
module Main where
import Css (fooBar, style)
-- ...

```

> **[GitHub - yaitskov/css-class-bindings: library generates Haskell bindings for CSS classes](https://github.com/yaitskov/css-class-bindings)**
>
> library generates Haskell bindings for CSS classes

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [March 28, 2026, 8:03pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/2 "2026-03-28T20:03:18Z")

</div>

nice! Shpadoinkle has something similar. It was kind of unreasonably effective in practice lol.

---

<div class="post-metadata">

**Author:** ![dyaitskov](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/dyaitskov/32/5142_2.png) [@dyaitskov](https://discourse.haskell.org/u/dyaitskov)\
**Post date:** [March 29, 2026, 8:14am UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/3 "2026-03-29T08:14:25Z")

</div>

> [@Ambrose](#):
>
> Shpadoinkle has something similar

Could you share a link? I did researched on hackage before starting the library, but haven’t found anything suitable.

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [March 29, 2026, 3:20pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/4 "2026-03-29T15:20:48Z")

</div>

I can’t find the module we use in the codebase (we used a very early version since the author was my cofounder) but I think it evolved into shpadoinkle-html.

Yours being its own standalone thing is great though. Bookmarked!

---

<div class="post-metadata">

**Author:** ![george.fst](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/george.fst/32/4933_2.png) [@george.fst](https://discourse.haskell.org/u/george.fst)\
**Post date:** [March 29, 2026, 10:29pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/5 "2026-03-29T22:29:29Z")

</div>

This is cool. I’ve been using Miso with static stylesheets, but been uneasy about the “stringly-typed” nature of linking them together the naive way.

What stops me from using this straight away is that I’d want support for IDs as well as classes. I see no reason not to also generate bindings for those, except that it would ideally mean renaming the library (`css-bindgen`?), which would be a bit annoying at this stage.

---

<div class="post-metadata">

**Author:** ![george.fst](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/george.fst/32/4933_2.png) [@george.fst](https://discourse.haskell.org/u/george.fst)\
**Post date:** [March 30, 2026, 12:17am UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/6 "2026-03-30T00:17:51Z")

</div>

Also, `includeCss` should really be using `addDependentFile` so that bindings are regenerated when the file changes.

---

<div class="post-metadata">

**Author:** ![dyaitskov](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/dyaitskov/32/5142_2.png) [@dyaitskov](https://discourse.haskell.org/u/dyaitskov)\
**Post date:** [March 30, 2026, 8:18am UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/7 "2026-03-30T08:18:56Z")

</div>

> [@george.fst](#):
>
> What stops me from using this straight away is that I’d want support for IDs as well as classes. I see no reason not to also generate bindings for those,

Thanks for your feedback. This motivated me to release v0.0.3 with CSS id support and marking included files with addDependentFile.  
Id support didn’t get into initial version because my project don’t have any and e.g. whole bulma library (+600kb) dito.

I think of CSS id as a stricter version of CSS class (linear CSS class).

---

<div class="post-metadata">

**Author:** ![george.fst](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/george.fst/32/4933_2.png) [@george.fst](https://discourse.haskell.org/u/george.fst)\
**Post date:** [April 6, 2026, 9:36pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/8 "2026-04-06T21:36:09Z")

</div>

I’m curious why you chose to generate data constructors for IDs, instead of variables of a fixed type like you do for classes?

Also, what’s your approach to parsing the CSS? An ID appearing in a position like `*:not(#a) {}` isn’t picked up, even though a class in the same place is.

Anyway, I don’t think I can really use this library in practice for the foreseeable future, since the Wasm backend’s GHCI browser mode’s Template Haskell support isn’t able to read files.

---

<div class="post-metadata">

**Author:** ![dyaitskov](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/dyaitskov/32/5142_2.png) [@dyaitskov](https://discourse.haskell.org/u/dyaitskov)\
**Post date:** [April 7, 2026, 4:12pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/9 "2026-04-07T16:12:47Z")

</div>

> [@george.fst](#):
>
> I’m curious why you chose to generate data constructors for IDs, instead of variables of a fixed type like you do for classes?

Using variables was the first idea to pop in my head, but I decided to put it aside because ids and classes exist in separate namespaces and merging them into one would require providing disambiguation logic such as a suffixes to avoid compilation errors.

> [@george.fst](#):
>
> Also, what’s your approach to parsing the CSS? An ID appearing in a position like `*:not(#a) {}` isn’t picked up, even though a class in the same place is.

It is folding a list of CSS tokens from css-syntax package with pattern matching. Class syntax is easy to catch and using something like attoparsec here is definitely overkill.

> [@george.fst](#):
>
> Wasm backend’s GHCI browser mode’s Template Haskell support isn’t able to read files.

What version of GHC are you talking about?

TH in 9.12.2 with WASM Backend **works fine**.  
`includeCss` is used in [vpn-router](https://github.com/yaitskov/vpn-router/blob/3842de19a1947024d933ae4b298db2f3d6a7b0fd/ui/CssStyle.hs#L6). The project build is sealed with Nix. You should not have an issue with reproducing the results.

I fixed the bug you reported in v0.0.4.

I am curious how many ids do you expect on in an app you maintain?

As for me using id in CSS selectors seems as an anti-pattern, because:

- ids are global and make composition of HTML components harder (sort of global variable in a classic programming language)
- modern web development “single page app” do not need ids (the whole page is a view conditionally composed from other views). An exception is a migration of a legacy jquery-like project where injection places of “react” components are specified with ids, and this is not about styles. Modern CSS support [layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer) feature expanding priority spectrum.

---

<div class="post-metadata">

**Author:** ![george.fst](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/george.fst/32/4933_2.png) [@george.fst](https://discourse.haskell.org/u/george.fst)\
**Post date:** [April 7, 2026, 5:59pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/10 "2026-04-07T17:59:57Z")

</div>

> [@dyaitskov](#):
>
> ids and classes exist in separate namespaces

That’s a fair point. I’m not sure what the best solution would be. I think I’d probably want to be able to generate two separate Haskell modules, so that I could qualify the names in the (relatively unlikely) event I’d need to disambiguate. But this isn’t great while GHC doesn’t allow defining multiple modules in one file. It would require two separate TH invocations. I’ve actually been having the same annoyance recently while using `hs-bindgen` and wanting separate modules for safe and unsafe bindings.

> [@dyaitskov](#):
>
> It is folding a list of CSS tokens from css-syntax package with pattern matching

Is this an upstream issue with `css-syntax` then?

> [@dyaitskov](#):
>
> TH in 9.12.2 with WASM Backend **works fine**

_Compiled_ Wasm builds work fine, but are you using `-fghci-browser`, for [REPL-based live-reloading](https://www.tweag.io/blog/2025-04-17-wasm-ghci-browser/)?

---

<div class="post-metadata">

**Author:** ![george.fst](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/george.fst/32/4933_2.png) [@george.fst](https://discourse.haskell.org/u/george.fst)\
**Post date:** [April 7, 2026, 6:08pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/11 "2026-04-07T18:08:34Z")

</div>

> [@dyaitskov](#):
>
> how many ids do you expect on in an app you maintain?

I tend to automatically use IDs instead of classes for things that I know will only appear at one position in the DOM. In theory at least, this makes CSS selectors more efficient. It’s also a slight aid to reading the CSS as a human. This is the first time I’ve found any downside to it. I don’t quite see the analogy with global variables.

---

<div class="post-metadata">

**Author:** ![dyaitskov](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/dyaitskov/32/5142_2.png) [@dyaitskov](https://discourse.haskell.org/u/dyaitskov)\
**Post date:** [April 8, 2026, 4:53am UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/12 "2026-04-08T04:53:11Z")

</div>

> [@george.fst](#):
>
> Is this an upstream issue with `css-syntax` then?

No

> [@george.fst](#):
>
> _Compiled_ Wasm builds work fine, but are you using `-fghci-browser`, for [REPL-based live-reloading](https://www.tweag.io/blog/2025-04-17-wasm-ghci-browser/)?

No, I didn’t have to. Imho WASM backend is faster than native one. Rebuild takes about a second.

> [@george.fst](#):
>
> I tend to automatically use IDs instead of classes

So I imagine a TH function generating UUID id for an HTML component, extracting CSS classes from the component, discovering CSS selectors mentioning these classes and rewriting them with the UUID prefix, stripping a bigger chunk of unrelated CSS.  
That’s very cool, but there is no need for type enforcement, because it’s automatic algorithm. The algorithm would work fine with plain strings for ids and classes. The algorithm requires testing and ideally verification.

---

<div class="post-metadata">

**Author:** ![jackiedorland](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jackiedorland/32/5518_2.png) [@jackiedorland](https://discourse.haskell.org/u/jackiedorland)\
**Post date:** [April 8, 2026, 11:16pm UTC](https://discourse.haskell.org/t/ann-css-class-bindings-library-generates-haskell-bindings-for-css-classes/13857/13 "2026-04-08T23:16:05Z")

</div>

really neat project! i’ve often wondered about a Haskell DSL that compiles to css/html … it’d be sick to see something like that come to life
