Skip to content

Commit 81e742c

Browse files
committed
feat(lit-dev-content): add lit-with-tailwind guide
This adds a guide for using tailwind (and similar libraries) with lit components. For now, using the older import assertions proposal since esbuild doesn't yet support import attributes out of the box it seems.
1 parent 9587499 commit 81e742c

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

packages/lit-dev-content/site/_data/authors.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,15 @@
5959
"image": {
6060
"url": "authors/elliott-marquez.webp"
6161
}
62+
},
63+
"james-garbutt": {
64+
"name": "James Garbutt",
65+
"links": {
66+
"twitter": "43081j",
67+
"github": "43081j"
68+
},
69+
"image": {
70+
"url": "authors/james-garbutt.jpg"
71+
}
6272
}
6373
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
---
2+
title: Lit components with tailwind styles
3+
publishDate: 2023-12-10
4+
lastUpdated: 2023-12-10
5+
summary: Using tailwind styles inside Lit components
6+
tags:
7+
- web-components
8+
- tailwind
9+
- css
10+
eleventyNavigation:
11+
parent: Articles
12+
key: Lit with tailwind
13+
order: 0
14+
author:
15+
- james-garbutt
16+
---
17+
18+
Tailwind and many other CSS libraries were not designed with web components in
19+
mind, and come with some non-obvious difficulties when trying to use them in
20+
such a codebase.
21+
22+
This is a brief guide on how to use such a library with your Lit components.
23+
24+
## Why tailwind doesn't work out of the box
25+
26+
Out of the box, tailwind basically provides some global CSS classes and injects
27+
the associated CSS at build time (via postcss).
28+
29+
This conflicts with how web components work, since each web component has its
30+
own natively scoped stylesheet rather than inherting any global styles. This
31+
is why Tailwind will not be much use to us without further setup.
32+
33+
## Overview of solution
34+
35+
To solve the gaps tailwind comes with, we will:
36+
37+
- Inject tailwind styles into regular CSS files
38+
- Use [import attributes](https://github.com/tc39/proposal-import-attributes)
39+
to import those CSS files
40+
- Use [esbuild](https://github.com/evanw/esbuild) to pull those CSS files
41+
into the bundle or same output directory
42+
43+
## Initial setup
44+
45+
To begin, we need the following dependencies for our build:
46+
47+
- `esbuild` for bundling our code and carry our CSS files across (via imports)
48+
- `postcss` for injecting tailwind styles
49+
- `tailwindcss` for tailwind itself (postcss plugin)
50+
51+
We can install these like so:
52+
53+
```sh
54+
npm install -D esbuild postcss postcss-cli tailwindcss
55+
```
56+
57+
In our case, we are going to use `postcss-cli` for processing our CSS files. If
58+
you use rollup or another bundler already, you may be able to use a postcss
59+
plugin instead.
60+
61+
## Using CSS imports
62+
63+
When creating components, we want to use
64+
[import attributes](https://github.com/tc39/proposal-import-attributes) to
65+
import our CSS files rather than embedding CSS in our sources.
66+
67+
**However, at the time of writing this article, the new `with` syntax is not
68+
fully supported**. Temporarily, we still have to use the old `assert` syntax,
69+
until esbuild implements full support for the new standard.
70+
71+
We can do this like so:
72+
73+
```ts
74+
import myElementStyles from './my-element.css' assert {type: 'css'}
75+
76+
class MyElement extends LitElement {
77+
static styles = myElementStyles;
78+
79+
render() {
80+
return html`
81+
<span class="italic">I am italic tailwind text</span>
82+
`;
83+
}
84+
}
85+
```
86+
87+
As you can see, we want to our `my-element.css` file to contain tailwind
88+
mixins, and we want to use the resulting classes in our element's `render`
89+
method.
90+
91+
The CSS file (`my-element.css`) would look like this:
92+
93+
```css
94+
@tailwind base;
95+
@tailwind utilities;
96+
97+
/* other custom CSS here */
98+
```
99+
100+
We will then use tailwind to replace those mixins with CSS, including only
101+
the styles our `render` method referenced.
102+
103+
## Configuring tailwind and postcss
104+
105+
Before we run our build, we need to configure tailwind and postcss.
106+
107+
In our `tailwind.config.js`, we can write:
108+
109+
```ts
110+
/** @type {import('tailwindcss').Config} */
111+
export default {
112+
content: [
113+
'./src/**/*.ts'
114+
],
115+
theme: {
116+
extend: {},
117+
},
118+
plugins: [],
119+
}
120+
```
121+
122+
In our case, our sources are TypeScript, so we have the initial `@type` comment
123+
to give us auto-completion for the object's properties.
124+
125+
The rest of this file is straight forward. The important part is `content`,
126+
specifying where our sources are so tailwind can detect which classes we have
127+
used.
128+
129+
In our `postcss.config.js`, we simply want to tell postcss to use tailwind
130+
as a plugin:
131+
132+
```ts
133+
export default {
134+
plugins: {
135+
tailwindcss: {}
136+
}
137+
};
138+
```
139+
140+
## Build scripts
141+
142+
We're going to do a few steps in our build:
143+
144+
1. Bundle our sources (TypeScript in this case, including our CSS imports)
145+
2. Apply tailwind styles to the build output in-place
146+
147+
To do this, we can use an npm script:
148+
149+
```json
150+
{
151+
"scripts": {
152+
"build:js": "esbuild --format=esm --bundle --loader:.css=copy --outfile=bundle.js src/main.ts",
153+
"build:css": "postcss -r \"./*.css\"",
154+
"build": "npm run build:js && npm run build:css"
155+
}
156+
}
157+
```
158+
159+
You can see the magic here in our `build:js` script is esbuild's `--loader`
160+
option, which we have set to `.css=copy`. This basically means any CSS files we
161+
import via an ESM import statement will be copied across to the same directory
162+
as the esbuild JS output (`bundle.js` in our case).
163+
164+
So if we import `my-element.css`, we will expect two files in our directory:
165+
166+
- `my-element.css` (probably under another name, using esbuild's chunk naming)
167+
- `bundle.js`
168+
169+
Finally, the `build:css` script then _replaces_ (via the `-r` flag) those CSS
170+
files with copies which now have the tailwind styles injected.
171+
172+
## Run it!
173+
174+
We now have a `build` script! You can run this via `npm run build` and should
175+
see two new files appear as mentioned before.
176+
177+
## Expressions in styles (dynamic styles)
178+
179+
You may have noticed one thing we have lost by having our CSS in external
180+
files: we can no longer template variables into our CSS like so:
181+
182+
```ts
183+
static styles = css`
184+
.foo {
185+
color: ${dynamicColourDefinedInJs};
186+
}
187+
`;
188+
```
189+
190+
There are many challenges in supporting this usage and still passing it through
191+
tailwind. For this reason, **it is not recommended**.
192+
193+
Instead, in places we really need to do this, we should use CSS variables or
194+
do the computation statically at build-time (replace values in our CSS at
195+
build time).
196+
197+
To solve this with CSS variables, we can do the following:
198+
199+
`my-element.css`:
200+
201+
```css
202+
.foo {
203+
color: var(--foo-color);
204+
}
205+
```
206+
207+
`my-element.ts`:
208+
209+
```ts
210+
static styles = [
211+
css`
212+
:host {
213+
--foo-color: ${dynamicColour};
214+
}
215+
`,
216+
myElementStyles
217+
];
218+
```
219+
220+
The initial `styles` entry in this case is only being used for setting the
221+
values of some CSS variables, and never used for actual styling. We can then
222+
reference those variables in our external CSS files.
223+
224+
## Example
225+
226+
An example repository very similar to this guide is available here:
227+
228+
https://github.com/43081j/tailwind-lit-example
Loading

0 commit comments

Comments
 (0)