seanhess/atomic-css

Type-safe Atomic CSS with intuitive layouts and composable css utility classes. Inspired by Tailwindcss and Elm-UI

Haskell

56

207 commits

updated May 14, 2026

See the code

README

Atomic CSS

Hackage

Type-safe, composable CSS utility functions. Inspired by Tailwindcss and Elm-UI

Write Haskell instead of CSS

Style your html with composable CSS utility functions:

el ~ bold . pad 8 $ "Hello World"

This renders as the following HTML with embedded CSS utility classes:

<style type='text/css'>
.bold { font-weight:bold }
.p-8 { padding:0.500rem }
</style>

<div class='bold p-8'>Hello World</div>

Instead of relying on the fickle cascade, factor and compose styles with the full power of Haskell functions!

header = bold
h1 = header . fontSize 32
h2 = header . fontSize 24
page = flexCol . gap 10 . pad 10

example = el ~ page $ do
  el ~ h1 $ "My Page"
  el ~ h2 $ "Introduction"
  el "lorem ipsum..."

This approach is inspired by Tailwindcss' Utility Classes

Intuitive Flexbox Layouts

Create complex layouts with row, col, grow, and space

holygrail = do
  col ~ grow $ do
    row "Top Bar"
    row ~ grow $ do
      col "Left Sidebar"
      col ~ grow $ "Main Content"
      col "Right Sidebar"
    row "Bottom Bar"

Stateful Styles

We can apply utilities when certain states apply. For example, to change the background on hover:

button ~ bg Primary . hover (bg PrimaryLight) $ "Hover Me"

Media states allow us to create responsive designs

el ~ width 100 . media (MinWidth 800) (width 400) $ do
  "Big if window > 800"

Embedded CSS

Only the utilities used in a given html fragment are rendered:

>>> renderText $ el ~ bold $ "Hello"

<style type='text/css'>.bold { font-weight:bold }</style>
<div class='bold'>Hello</div>

Try Example Project with Nix

If you want to get a feel for atomic-css without cloning the project run nix run github:seanhess/atomic-css to run the example webserver locally

Import Flake

You can import this flake's overlay to add atomic-css to overriddenHaskellPackages and which provides a ghc967 and ghc984 package set that satisfy atomic-css's dependencies.

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    atomic-css.url = "github:seanhess/atomic-css"; # or "path:/path/to/cloned/atomic-css";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, atomic-css, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ atomic-css.overlays.default ];
        };
        haskellPackagesOverride = pkgs.overriddenHaskellPackages.ghc967.override (old: {
          overrides = pkgs.lib.composeExtensions (old.overrides or (_: _: { })) (hfinal: hprev: {
            # your overrides here
          });
        });
      in
      {
        devShells.default = haskellPackagesOverride.shellFor {
          packages = p: [ p.atomic-css ];
        };
      }
    );
}

Local Development

If you want to work on both the atomic-css library and example code, this ghcid command will run and reload the examples server as you change any non-testing code.

ghcid --command="cabal repl exe:example lib:atomic-css" --run=Main.main --warnings --reload=./embed/preflight.css

If you want to work on the test suite, this will run the tests each time any library code is changed.

ghcid --command="cabal repl test lib:atomic-css" --run=Main.main --warnings --reload=./embed/preflight.css

Nix

  • nix flake check will build the library, example executable and devShell with ghc-9.8.4 and ghc-9.6.7
    • This is what the CI on GitHub runs
  • nix run or nix run .#ghc984-example to start the example project with GHC 9.8.4
    • nix run .#ghc967-example to start the example project with GHC 9.6.7
  • nix develop or nix develop .#ghc984-shell to get a shell with all dependencies installed for GHC 9.8.4.
    • nix develop .#ghc967-shell to get a shell with all dependencies installed for GHC 9.6.7.
  • nix build, nix build .#ghc984-atomic-css and nix build .#ghc967-atomic-css builds the library with the overriddenHaskellPackages
    • If you want to import this flake, use the overlay
  • nix flake update nixpkgs will update the Haskell package sets and development tools

Common Nix Issues

Not Allowed to Refer to GHC

If you get an error like:

error: output '/nix/store/64k8iw0ryz76qpijsnl9v87fb26v28z8-my-haskell-package-1.0.0.0' is not allowed to refer to the following paths:
         /nix/store/5q5s4a07gaz50h04zpfbda8xjs8wrnhg-ghc-9.6.3

Follow these instructions

Dependencies Incorrect

You will need to update the overlay, look for where it says "${packageName}" = hfinal.callCabal2nix packageName src { }; and add a line like Diff = hfinal.callHackage "Diff" "0.5" { }; with the package and version you need.

Missing Files

Check the include inside the nix-filter.lib to see if all files needed by cabal are there.

Learn More

View Documentation on Hackage

View on Github

View Examples

Contributors

Contributors

seanhess

178 commits

Skyfold

23 commits

kfigiela

4 commits

kschweppe

1 commits

seanhess/atomic-css

Type-safe Atomic CSS with intuitive layouts and composable css utility classes. Inspired by Tailwindcss and Elm-UI

Haskell

56

207 commits

updated May 14, 2026

See the code

README

Atomic CSS

Hackage

Type-safe, composable CSS utility functions. Inspired by Tailwindcss and Elm-UI

Write Haskell instead of CSS

Style your html with composable CSS utility functions:

el ~ bold . pad 8 $ "Hello World"

This renders as the following HTML with embedded CSS utility classes:

<style type='text/css'>
.bold { font-weight:bold }
.p-8 { padding:0.500rem }
</style>

<div class='bold p-8'>Hello World</div>

Instead of relying on the fickle cascade, factor and compose styles with the full power of Haskell functions!

header = bold
h1 = header . fontSize 32
h2 = header . fontSize 24
page = flexCol . gap 10 . pad 10

example = el ~ page $ do
  el ~ h1 $ "My Page"
  el ~ h2 $ "Introduction"
  el "lorem ipsum..."

This approach is inspired by Tailwindcss' Utility Classes

Intuitive Flexbox Layouts

Create complex layouts with row, col, grow, and space

holygrail = do
  col ~ grow $ do
    row "Top Bar"
    row ~ grow $ do
      col "Left Sidebar"
      col ~ grow $ "Main Content"
      col "Right Sidebar"
    row "Bottom Bar"

Stateful Styles

We can apply utilities when certain states apply. For example, to change the background on hover:

button ~ bg Primary . hover (bg PrimaryLight) $ "Hover Me"

Media states allow us to create responsive designs

el ~ width 100 . media (MinWidth 800) (width 400) $ do
  "Big if window > 800"

Embedded CSS

Only the utilities used in a given html fragment are rendered:

>>> renderText $ el ~ bold $ "Hello"

<style type='text/css'>.bold { font-weight:bold }</style>
<div class='bold'>Hello</div>

Try Example Project with Nix

If you want to get a feel for atomic-css without cloning the project run nix run github:seanhess/atomic-css to run the example webserver locally

Import Flake

You can import this flake's overlay to add atomic-css to overriddenHaskellPackages and which provides a ghc967 and ghc984 package set that satisfy atomic-css's dependencies.

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    atomic-css.url = "github:seanhess/atomic-css"; # or "path:/path/to/cloned/atomic-css";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, atomic-css, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ atomic-css.overlays.default ];
        };
        haskellPackagesOverride = pkgs.overriddenHaskellPackages.ghc967.override (old: {
          overrides = pkgs.lib.composeExtensions (old.overrides or (_: _: { })) (hfinal: hprev: {
            # your overrides here
          });
        });
      in
      {
        devShells.default = haskellPackagesOverride.shellFor {
          packages = p: [ p.atomic-css ];
        };
      }
    );
}

Local Development

If you want to work on both the atomic-css library and example code, this ghcid command will run and reload the examples server as you change any non-testing code.

ghcid --command="cabal repl exe:example lib:atomic-css" --run=Main.main --warnings --reload=./embed/preflight.css

If you want to work on the test suite, this will run the tests each time any library code is changed.

ghcid --command="cabal repl test lib:atomic-css" --run=Main.main --warnings --reload=./embed/preflight.css

Nix

  • nix flake check will build the library, example executable and devShell with ghc-9.8.4 and ghc-9.6.7
    • This is what the CI on GitHub runs
  • nix run or nix run .#ghc984-example to start the example project with GHC 9.8.4
    • nix run .#ghc967-example to start the example project with GHC 9.6.7
  • nix develop or nix develop .#ghc984-shell to get a shell with all dependencies installed for GHC 9.8.4.
    • nix develop .#ghc967-shell to get a shell with all dependencies installed for GHC 9.6.7.
  • nix build, nix build .#ghc984-atomic-css and nix build .#ghc967-atomic-css builds the library with the overriddenHaskellPackages
    • If you want to import this flake, use the overlay
  • nix flake update nixpkgs will update the Haskell package sets and development tools

Common Nix Issues

Not Allowed to Refer to GHC

If you get an error like:

error: output '/nix/store/64k8iw0ryz76qpijsnl9v87fb26v28z8-my-haskell-package-1.0.0.0' is not allowed to refer to the following paths:
         /nix/store/5q5s4a07gaz50h04zpfbda8xjs8wrnhg-ghc-9.6.3

Follow these instructions

Dependencies Incorrect

You will need to update the overlay, look for where it says "${packageName}" = hfinal.callCabal2nix packageName src { }; and add a line like Diff = hfinal.callHackage "Diff" "0.5" { }; with the package and version you need.

Missing Files

Check the include inside the nix-filter.lib to see if all files needed by cabal are there.

Learn More

View Documentation on Hackage

View on Github

View Examples

Contributors

Contributors

seanhess

178 commits

Skyfold

23 commits

kfigiela

4 commits

kschweppe

1 commits

Languages

Haskell

92.8%

Nix

6.6%