Skip to content

Getting Started with LitElement and Tailwind

Getting Started with LitElement and Tailwind

By taking advantage of LitElement's thin layer over Web Components combined with Tailwind's near infinite offering of CSS utility classes, you can create a beautiful, completely custom, and incredibly performant data-driven user-interface with ease.

Tooling

We will be using a combination of tools to give us as great of a developer experience as possible, as quickly as possible.

  • RollupJS - Rollup's approach to module bundling relies on the native JavaScript module standard, ES Modules, as opposed to Node's CommonJS module system.
  • Babel - Babel will allow us to use the latest JavaScript features by compiling down our code to ES5, something older browsers will understand.
  • TypeScript - Better to have silly errors during development than insanity-inducing errors in production. TypeScript's got our backs by reducing the bugs (accidental features?) we introduce in our code.
  • ESLint
  • Prettier

Installing RollupJS and initial configuration

Rollup has an excellent starter-app template that we can use to get going quickly. Clone their repo and we'll use that as our starting point.

Create/Edit/Remove these files:

  • LICENSE - This is just a prototype, so we don't need this file.
  • README.md - Again, this is just a prototype, so we don't need this either.
  • src/update.js - We can remove this.
  • public/index.html - We can remove the <h1> and the <p> tags. We'll start fresh.
  • package.json - Remove the date-fns dependency.
  • src/main.js - Update it so it has a single command to test our config, console.log('Hello, World!')

After that's done, let's test our initial Rollup setup:

  1. yarn install or npm install to install our dependencies
  2. yarn dev or npm dev to run our development environment
  3. Navigate to localhost:5000
  4. Open dev-tools to see our test message
Logging Hello, World!

Configuring Babel & TypeScript

To configure Babel and TypeScript, we'll take advantage of Rollup's Babel plugin, and we'll configure babel to compile our TypeScript code.

Install the following dependencies:

  • @babel/core
  • @rollup/plugin-babel
  • @babel/preset-env
  • typescript
  • @rollup/plugin-typescript
  • @babel/preset-typescript

Edit the following files:

  • src/main.js rename to src/main.ts
  • rollup.config.js

Import and use the Babel plugin, and set the input file as a .ts file (for TypeScript).

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import typescript from '@rollup/plugin-typescript';
import { babel } from '@rollup/plugin-babel'

// `npm run build` -> `production` is true
// `npm run dev` -> `production` is false
const production = !process.env.ROLLUP_WATCH;

export default {
  input: 'src/main.ts',
  output: {
    file: 'public/bundle.js',
    format: 'es',
    sourcemap: true
  },
  plugins: [
    resolve(),
    commonjs(),
    typescript({
      include: ['src/**/*.ts']
    }),
    babel({
      babelHelpers: 'bundled',
      exclude: "node_modules/**"
    }),
    production && terser({ format: { comments: false } }), // minify, but only  in production
 ]
};
  • .babelrc - Create and setup Babel configuration file to compile down to a minimum of ES5 code.
{
  "presets": ["@babel/preset-env"]
}
  • tsconfig/json - Create the TypeScript configuration file with the command yarn tsc --init

Configuring ESLint and Prettier

Install the following dependencies:

  • eslint
  • prettier
  • eslint-config-prettier
  • eslint-plugin-prettier

Run yarn eslint --init and choose the following options to create .eslintrc.js, ESLint's config file.

  • enforce code style
  • JavaScript modules
  • No Framework
  • TypeScript
  • Project runs in the browser
  • Use Airbnb style guide
  • config format JavaScript

After this, we can see that our IDE (in my scenario, VS Code) recognizes the ESLint configuration and is throwing some issues my way.

ESLint Errors

In .eslintrc.js

  • Add 'plugin:prettier/recommended' to the extends array
  • Add 'prettier' to the plugins array
  • Add "prettier/prettier": "error" to the rules object

Create and configure .prettierrc, Prettier's configuration file. This single line is for any Window's developers, preventing any weird end-of-line prettier issues.

{
  "endOfLine": "auto"
}
ESLint

Using LitElement to make a data-driven User Interface

Now that we have a solid tooling setup, lets code up some UI with LitElement.

LitElement is a thin wrapper around Web Components. It provides us a way to declaratively write our UI as a function of state. A difference though between LitElement and another framework like React or Vue is that LitElement is built on top of the native component model of the web.

Let's code the obligatory 'Hello, World!'

First, install lit-element as a dependency to the project:

yarn add lit-element

To use a web component, we'll define it in src/main.ts, and then we'll use the new component in public/index.html.

src/main.ts

import { LitElement, customElement, html } from "lit-element";

@customElement("app-component")
export default class AppComponent extends LitElement {
  render() {
    return html` <h1>Hello, World!</h1> `;
  }
}

public/index.html

<!doctype html>
<html>

<head lang='en'>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width'>
  <title>LitElement w/ Tailwind</title>

  <style>
    body {
      font-family: 'Helvetica Neue', Arial, sans-serif;
      color: #333;
      font-weight: 300;
    }
  </style>
</head>

<body>
  <app-component></app-component>
  <script type="module" src='bundle.js'></script>
</body>

</html>

You'll most likely run into several ESLint and TypeScript issues. A few to be wary of are:

  • TypeScript Decorators: The line with @customElement() is a TypeScript feature called a decorator. In order to use them, you have to explicitly turn the feature in in your tsconfig.json.
  • Expected 'this' to be used by class method 'render': This is an ESLint rule noticing that we aren't using this in the instance method render(). We can tell ESLint to ignore specific functions by adding a rule to our eslintrc.js

After resolving any linting issues you may have, you should have your Hello, World!

Hello, World w/ LitElement

Styling components with Tailwind

Now we are going to add Tailwind to our project.

Install the following dependencies:

  • rollup-plugin-postcss
  • tailwindcss
  • postcss
  • autoprefixer

Run the following command to generate a Tailwind configuration file:

npx tailwindcss init

tailwindcss.config.js

module.exports = {
  purge: [
    './src/**/*.html',
    './src/**/*.js',
  ],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Create/Edit the following files:

rollup.config.js

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import typescript from '@rollup/plugin-typescript';
import { babel } from '@rollup/plugin-babel'
import postcss from 'rollup-plugin-postcss' // import postcss plugin

const production = !process.env.ROLLUP_WATCH;

export default {
  input: 'src/main.ts',
  output: {
    file: 'public/bundle.js',
    format: 'es',
    sourcemap: true
  },
  plugins: [
    resolve(),
    commonjs(),
    postcss(), // use postcss plugin
    typescript({
      include: ['src/**/*.ts']
    }),
    babel({
      babelHelpers: 'bundled',
      exclude: "node_modules/**"
    }),
    production && terser({ format: { comments: false } }),
  ]
};

postcss.config.js

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

src/styles.css

@tailwind base;
@tailwind components;
@tailwind utilities;

src/main.ts

import './styles.css' // import the tailwind styles
import { LitElement, customElement, html } from "lit-element";

@customElement("app-component")
export default class AppComponent extends LitElement {
  createRenderRoot() {
    return this; // turn off shadow dom to access external styles
  }
  render() {
    return html` <h1 class="mx-auto my-4 py-4 text-center shadow-lg text-xl w-1/2">Hello, World!</h1> `; // use tailwind css utility classes
  }
}

After our configuration, we now have a working LitElement & Tailwind setup!

Styled with Tailwind

Conclusion

After our configuration, we now have a strong base to start creating a beautiful data-driven user interface with powered by both LitElement and Tailwind CSS.