npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

elm-tailwind-classes

v0.6.2

Published

Type-safe Tailwind CSS for Elm with static class extraction

Readme

elm-tailwind-classes

Type-safe Elm API for regular Tailwind CSS classes — no elm-css needed.

  • Uses regular Tailwind CSS classes under the hood — no elm-css runtime, no performance overhead
  • The Vite plugin generates a type-safe Elm API matching your Tailwind theme
  • At build time, the plugin analyzes your Elm code to find which classes to include in your CSS bundle
  • Just install and configure the Vite plugin and you're ready to go (see Quick Start)

Demo

See it in action:

Motivation

Elm developers using Tailwind CSS have historically faced a frustrating tradeoff:

The Problem with String Classes

Using Tailwind's class strings directly (class "flex p-4 text-gray-500") gives you small bundles and standard tooling, but no type safety. Typos like "flexz" or invalid values like "text-gray-550" silently fail with no compiler help.

The Problem with elm-css

Tools like elm-tailwind-modules solve type safety by generating Elm functions that produce elm-css styles. This gives you a nice parameterized API (Tw.p_4, Tw.text_color Theme.gray_500), but comes with significant costs:

  • Runtime overhead: elm-css must collect and hash styles at runtime, which is more expensive than parsing stylesheets
  • Bundle size: The elm-css runtime adds to your JavaScript bundle
  • Performance degradation: Real-world examples show style recomputation taking ~3 seconds for pages with ~900 elements, each with ~20 styles applied
  • Sequential bottleneck: CSS and JS parsing can be done in parallel by browsers, but computing styles in JS means that work happens after all JS has been parsed

These aren't theoretical concerns—the broader web development community has documented similar issues. In Why We're Breaking Up with CSS-in-JS, the Spot team found that switching from Emotion (a popular CSS-in-JS library) to build-time CSS reduced render times by 48%. The runtime serialization overhead of CSS-in-JS libraries creates measurable performance bottlenecks, especially as UI complexity grows.

This Solution: Best of Both Worlds

elm-tailwind-classes gives you the developer experience of elm-tailwind-modules (type-safe, parameterized API) with the runtime characteristics of plain Tailwind (zero JS overhead, build-time CSS):

String classes: No type safety, no parameterization, small bundle, build-time CSS

elm-tailwind-modules: Type-safe, parameterized, but includes elm-css runtime and generates CSS at runtime

elm-tailwind-classes: Type-safe, parameterized, small bundle, build-time CSS - the best of both worlds!

The key insight: generate class strings (not elm-css styles), then use static analysis to extract them for Tailwind's JIT compiler. You get compile-time errors for typos, a nice API for programmatic styling, and zero runtime cost.

Features

  • Type-safe API: Tw.bg_color (blue s500) not "bg-blue-500" strings - typos are compile errors
  • Parameterized values: Tw.p s4 not Tw.p_4 - use the same spacing value across padding, margin, gap, width
  • Composable variants: Nest responsive and state variants naturally: md [ hover [ Tw.bg_color (blue s600) ] ]
  • Smart static extraction: Even List.map and case expressions are analyzed - all possible classes are extracted
  • Auto-generated from your Tailwind config: Custom colors, spacing, etc. just work
  • Zero config: Just add the Vite plugin - no elm-review setup required
  • No runtime overhead: Generates class strings, not elm-css styles

Parameterized Example

-- Use the same spacing value everywhere
buttonPadding : Spacing
buttonPadding = s4

button [ classes [ Tw.px buttonPadding, Tw.py s2 ] ] [ text "Click" ]

-- Map over a list - all values are statically extracted!
List.map (\size -> div [ classes [ Tw.p size ] ] []) [ s2, s4, s8 ]

-- Conditional classes - both branches are extracted
classes [ if isLarge then Tw.p s8 else Tw.p s4 ]

Quick Start

Prerequisites: You need an existing Vite project with Elm. If you're starting fresh, check out vite-plugin-elm or elm-pages.

1. Install the packages

npm install elm-tailwind-classes @tailwindcss/vite elm-review

2. Add the Vite plugins

// vite.config.js
import { defineConfig } from 'vite'
import elmTailwind from 'elm-tailwind-classes/vite'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    elmTailwind(),
    tailwindcss(),
  ]
})

3. Add the generated directory to elm.json

The plugin generates Elm modules to .elm-tailwind/. Add it to your source directories:

{
  "source-directories": [
    "src",
    ".elm-tailwind"
  ]
}

4. Set up your CSS file

Create a CSS file (e.g., style.css) with:

@import "tailwindcss";

Then import it in your JavaScript/TypeScript entry point:

// index.js or index.ts
import "./style.css";

Note: Import CSS via JavaScript (not a <link> tag) for hot-reloading to work when you add new Tailwind classes.

5. Run Vite and start coding!

When you run npm run dev, the plugin generates the Elm modules automatically. Now you can write:

import Tailwind as Tw exposing (classes)
import Tailwind.Theme exposing (blue, gray, s4, s500, s600, s800, white)
import Tailwind.Breakpoints exposing (hover, md)

view =
    div
        [ classes
            [ Tw.flex
            , Tw.items_center
            , Tw.p s4
            , Tw.bg_color (blue s500)
            , Tw.text_simple white
            , hover [ Tw.bg_color (blue s600) ]
            , md [ Tw.p s8 ]
            ]
        ]
        [ text "Hello, Tailwind!" ]

The plugin handles everything:

  1. Generates Elm modules matching your Tailwind theme → .elm-tailwind/
  2. Analyzes your Elm code to find which Tailwind classes you're using
  3. Tells Tailwind which classes to include in your CSS bundle

6. Add .elm-tailwind/ to .gitignore

The generated modules are specific to your Tailwind config, so don't commit them:

# Generated by elm-tailwind-classes
.elm-tailwind/

CLI

The elm-tailwind-classes gen command generates Elm modules from your Tailwind config without running Vite. This is useful for CI pipelines or any situation where Elm needs the generated API before Vite runs.

npx elm-tailwind-classes gen

Options:

--output <dir>    directory for generated Elm modules (default: .elm-tailwind)
--css <path>      path to CSS file with Tailwind config (auto-detected)
--debug           enable debug logging

elm-pages CI builds

In elm-pages projects, Elm compilation happens at the beginning of elm-pages build, before Vite runs. Since the Vite plugin normally generates the Elm modules, they won't exist yet when Elm tries to compile. Run gen first in your CI script:

npx elm-tailwind-classes gen
npx elm-pages build

Plugin Options

elmTailwind({
  debug: true,                    // Log detailed info
  generateDir: '.elm-tailwind',   // Where to generate Elm modules
  cssPath: './src/styles.css',    // CSS with Tailwind config (auto-detected)
  elmJson: './elm.json'           // elm.json path (auto-detected)
})

API

Spacing (parameterized)

Tw.p s4      -- "p-4"
Tw.mx s2     -- "mx-2"
Tw.gap s8    -- "gap-8"
Tw.w s64     -- "w-64"

Colors (parameterized)

-- Shaded colors: apply shade to color name
Tw.bg_color (blue s500)       -- "bg-blue-500"
Tw.text_color (gray s800)     -- "text-gray-800"
Tw.border_color (red s300)    -- "border-red-300"

-- Simple colors: SimpleColor -> Tailwind
Tw.bg_simple white            -- "bg-white"
Tw.text_simple black          -- "text-black"

Variants (composable)

hover [ Tw.bg_color (blue s600) ]           -- "hover:bg-blue-600"
md [ Tw.p s8 ]                              -- "md:p-8"
md [ hover [ Tw.text_color (gray s900) ] ]  -- "md:hover:text-gray-900"

Simple utilities

Tw.flex           -- "flex"
Tw.items_center   -- "items-center"
Tw.rounded_lg     -- "rounded-lg"
Tw.shadow_md      -- "shadow-md"

Reusable style groups

-- Define reusable styles with batch
buttonBase : Tailwind
buttonBase =
    batch
        [ Tw.px s4
        , Tw.py s2
        , Tw.rounded_lg
        , Tw.font_semibold
        , hover [ Tw.opacity_90 ]
        ]

-- Use in multiple places
primaryButton =
    button [ classes [ buttonBase, Tw.bg_color (blue s500), Tw.text_simple white ] ]

secondaryButton =
    button [ classes [ buttonBase, Tw.bg_color (gray s200), Tw.text_color (gray s800) ] ]

Escape hatch

Tw.raw "custom-plugin-class"  -- For classes not in the generated API

elm-pages Integration

Works with elm-pages out of the box:

// elm-pages.config.mjs
import { defineConfig } from 'vite'
import elmTailwind from 'elm-tailwind-classes/vite'
import tailwindcss from '@tailwindcss/vite'

export default {
  vite: defineConfig({
    plugins: [elmTailwind(), tailwindcss()]
  })
}

Make sure to import your CSS in your entry point (e.g., index.ts):

import "./style.css";

const config: ElmPagesInit = {
  // ...
};

export default config;

The plugin auto-detects elm.json location for elm-pages projects.

Hot Module Replacement (HMR)

When you change Elm files, the plugin automatically:

  1. Re-extracts used Tailwind classes via elm-review
  2. Updates the safelist
  3. Triggers Vite's CSS HMR to reload styles

HMR Requirements

For HMR to work properly:

  1. Import CSS via JavaScript - CSS must be imported in your JS/TS entry point (import "./style.css"), not via a <link> tag in HTML
  2. Don't use both - Remove any <link rel="stylesheet" href="./style.css"> from your HTML if you're using the JS import

Troubleshooting HMR

If CSS changes aren't hot-reloading:

  1. Enable debug mode to see what's happening:

    elmTailwind({ debug: true })
  2. Check the console for messages like:

    • [elm-tailwind] Elm file changed: Button.elm
    • [elm-tailwind] Re-extracted classes: 42
    • [elm-tailwind] Classes changed! New classes: ...
    • [elm-tailwind] Triggered Vite CSS HMR
  3. Verify CSS import method - Open browser DevTools Network tab, look for your CSS file. If loaded via <link> tag, switch to JS import.

  4. Check for errors - Look for [elm-tailwind] errors in the terminal where Vite is running.

Custom Tailwind Config

Your custom colors, spacing, etc. are automatically included in the generated Elm modules:

/* styles.css */
@import "tailwindcss";

@theme {
  --color-brand-light: #e0f2fe;
  --color-brand: #0ea5e9;
  --color-brand-dark: #0369a1;
}

After running Vite, the plugin generates Elm functions for your custom colors:

-- Custom colors become simple Color values (no shade parameter)
Tw.bg_color brand       -- "bg-brand"
Tw.bg_color brandLight  -- "bg-brand-light"
Tw.bg_color brandDark   -- "bg-brand-dark"

Note: Custom colors defined with --color-name become simple colors. If you want shaded colors (50-950 scale), define them with the full shade scale in your CSS.

Local Development

# Install locally from path
npm install /path/to/elm-tailwind-classes

# Run tests
npm test