Using tailwind at run-time with web components
Tags: tailwindcss, webcomponents, webdev
Published: 2020-12-27 17:08:36

I've seen Tailwind mentioned a fair amount lately but still not had the chance to use it so far. I figured I'd finally give it a go, using some of that Christmas "vacation time".

## My setup

When I play around with a new tool or library, I generally throw up the same thing:

- A single web component (vanilla or lit-element)
- rollup or esbuild (if needed)
- TypeScript

In this case, I threw this together:

```ts
class MyElement extends LitElement {
  static styles = css`
    :host { display: block; }
    /*
     * Somehow we want tailwind's CSS to ultimately
     * exist here
     */
  `;

  render() {
    // We want these tailwind CSS classes to exist
    return html`<div class="text-xl text-black">
      I am a test.
    </div>`;
  }
}
```

Where I want to get to is commented in the code. Essentially, the ability to use Tailwind classes.

## The problem/gaps

Tailwind seems to have pretty poor or non-existent support for web components. It doesn't support most of the web component APIs/concepts out of the box.

By default, its styles will simply be added to the `<head>` of our document. This, of course, means that they will be unavailable to any web components which use shadow DOM (intentionally).

Remember, shadow DOM does not inherit styles from the document. It has its own scoped stylesheets.

So what we somehow need to do is detect which Tailwind classes we have used and populate the correct CSS for those classes into our component's stylesheet.

## Possible solution #1 (build-time)

The first potential solution is a build-time process.

Tailwind offloads most of its work to PostCSS and follows this kind of process:

- Detects any directives (`@tailwind` in CSS)
- Replaces those with the actual Tailwind CSS

This only works when transforming a single CSS file via PostCSS out of the box. You cannot transform CSS this way from within a JS or HTML file (without extra plugins).

So the ideal build-time solution would be a way to allow PostCSS to transform inline CSS from within tagged templates, in-place.

Basically, this is the result I want:

```ts
class MyElement extends LitElement {
  // Build tool somehow figured out which classes we
  // referenced and added the associated CSS to a css
  // tag of our choosing.
  static styles = css`
    .text-x1 { /* ... */ }
    .text-black { /* ... */ }
  `;

  render() {
    return html`<div class="text-xl text-black">
      I am a test.
    </div>`;
  }
}
```

I have yet to find an existing tool to solve this problem. If anyone knows a solution to this, please do let me know!

## Possible solution #2 (run-time)

By pure coincidence, while trying to figure out the previous solution, the following package was tweeted and released:

https://github.com/tw-in-js/twind/

This library changes the whole thing to be a run-time process. 

The idea is:

- Anytime we want to use a tailwind class, we use a helper called `tw`
- This tracks which classes we've used
- All the classes we've used have their CSS generated and appended to a stylesheet we specify

Here's our example from before, but now using twind:

```ts
import { create, cssomSheet } from 'twind'

// This creates a twind "sheet" which is a wrapper
// around a CSS stylesheet
const sheet = cssomSheet({ target: new CSSStyleSheet() });

// This gets an instance of the tw helper for our
// stylesheet above. Meaning any time we use `tw`, it will
// append the right CSS to our custom stylesheet.
const { tw } = create({ sheet });

class MyElement extends LitElement {
  // We tell lit that we have already made a CSS
  // stylesheet and to use that directly.
  // At this point, it will be an empty sheet, but will
  // be updated after our first render by `tw`
  static styles = [sheet.target];

  render() {
    // Any time we use a tailwind class, we use the `tw`
    // helper instead of using it directly. This allows
    // twind to track the classes and generate the correct
    // CSS.
    return html`<div class=${tw`text-xl text-black`}>
      I am a test.
    </div>`;
  }
}
```

As you can see, the following happens:

- We create a stylesheet and assign it to our element
- When we render, we call `tw` which simply returns the string we passed it but meanwhile generates the associated CSS and appends it to the stylesheet

This is a cool little library since we never have to ship the tailwind CSS, all of what we need is generated at run-time. This actually means we don't ship much code to the client and we only generate the CSS we make use of rather than including Tailwind's whole stylesheet.

## Wrap up

I don't think either of these options is a "winner". Different use cases need different solutions.

A build-time solution would be so nice as we could statically generate the CSS once and ship that to the client.

A run-time solution is super nice too as we can generate any tailwind CSS we need at any time. We don't need to package up the whole Tailwind CSS.

If anyone does know of an existing solution to the build-time option, I'd love to hear about it before I go off and write my own :joy:

Meanwhile, do check out Twind as its a nice lightweight solution and fits in nicely with web components either way.