How to Use class instead of className with Preact and TypeScript
Tags: preact, typescript, dx
Published: 2020-04-05 06:08:05


## Bottom Line Up Front

If you are using TypeScript with Preact aliased as React, you can add an ambient declaration to use `class` instead of `className`:

```ts
// anyname.d.ts - place this anywhere in your project
declare namespace React {
  interface HTMLAttributes<T> {
    // Preact supports using "class" instead of "classname" - need to teach typescript
    class?: string;
  }
}
```

## My Context

Most React users aren't using every feature of React. For most usecases, using Preact is exactly equivalent. As point of proof: This blogpost you are reading is [written in a custom CMS in Preact](https://github.com/sw-yx/dev-to-cms), by me, a React dev who has never used Preact and only skimmed [the Differences page on their docs](https://preactjs.com/guide/v10/differences-to-react).

This is a good idea because of the limited downside - you can swap back to React in a single command if you end up needing its power - and the instant upside - A [Next.js + Preact page bundle will now come in as low as 20kb of JS](https://twitter.com/_developit/status/1234595685611208704) whereas a trip to [the Create Next App docs](https://create-next-app.github.io/) will download at least 100kb of JS off the bat (note: these aren't apples to apples comparisons). If you'd like to give it a try, I've been building [a small Preact + Next.js + TypeScript + TailwindCSS starter repo](https://github.com/sw-yx/smaller-safer-serverless-starter).

However I'm not here to write about anything as important as all that 😂. I'm here writing about a much smaller benefit of using Preact - [you can use `class` instead of `className`](https://preactjs.com/guide/v10/differences-to-react/#raw-html-attributeproperty-names)!

## Why `class` over `className`

This is mainly a pain point because I use [Tailwind UI](https://tailwindui.com/components), which involves a lot of copying and pasting mountains of code that looks like this:

```html
<div class="relative bg-gray-50 overflow-hidden">
  <div class="hidden sm:block sm:absolute sm:inset-y-0 sm:h-full sm:w-full">
    <div class="relative h-full max-w-screen-xl mx-auto">
      <svg class="absolute right-full transform translate-y-1/4 translate-x-1/4 lg:translate-x-1/2" width="404" height="784" fill="none" viewBox="0 0 404 784">
        <defs>
          <pattern id="f210dbf6-a58d-4871-961e-36d5016a0f49" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
            <rect x="0" y="0" width="4" height="4" class="text-gray-200" fill="currentColor" />
          </pattern>
        </defs>
        <rect width="404" height="784" fill="url(#f210dbf6-a58d-4871-961e-36d5016a0f49)" />
      </svg>
      <svg class="absolute left-full transform -translate-y-3/4 -translate-x-1/4 md:-translate-y-1/2 lg:-translate-x-1/2" width="404" height="784" fill="none" viewBox="0 0 404 784">
        <defs>
          <pattern id="5d0dd344-b041-4d26-bec4-8d33ea57ec9b" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
            <rect x="0" y="0" width="4" height="4" class="text-gray-200" fill="currentColor" />
          </pattern>
        </defs>
        <rect width="404" height="784" fill="url(#5d0dd344-b041-4d26-bec4-8d33ea57ec9b)" />
      </svg>
    </div>
  </div>
```

## The Problem

Now because [I'm using Next.js and TypeScript with Preact](https://github.com/developit/nextjs-preact-demo), I use Preact with a React alias - basically lying to TypeScript that we are using React so we benefit from it's mature tooling across VS Code and Next.js.

However React doesn't use `class` for classes, it uses `className`! (At least until [React Fire](https://github.com/facebook/react/issues/13525) lands.) So I have two choices:

- either go through and rename every `class` to `className` - like a heathen - every time I use Tailwind
- or try to use `class` as Preact lets me do

The problem goes back to what I stated above: we lied to TypeScript that we're using React, so it's not going to let us use `class`:

This:

```jsx
<div class="bg-black">Does this work?</div>
```

Leads to:

```
(JSX attribute) class: string
Type '{ children: string; href: string; class: string; }' is not assignable to type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
  Property 'class' does not exist on type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.ts(2322)
Peek Problem
No quick fixes available
```

Oh no! No quick fixes available??

Lies.

## The Quick Fix

Here's the fix. Add a [TypeScript ambient declaration](https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules) - basically a `anyname.d.ts` file anywhere in your project, assuming default `tsconfig` settings and add this:

```ts
// anyname.d.ts - place this anywhere in your project
declare namespace React {
  interface HTMLAttributes<T> {
    // Preact supports using "class" instead of "classname" - need to teach typescript
    class?: string;
  }
}
```

And now I can write `class` in my React code!

**If that's all you came to this blogpost for, then we're done**. I'm just going to discuss how I made my way to this solution as an intermediate TypeScript user, since this is a learning opportunity for broader TypeScript use.

> ⚠️ If you're also using Tailwind with Next.js, there is one more issue to resolve - disabling the styled-jsx plugin. More details here: https://github.com/zeit/next.js/issues/11675

## Appendix: Declaration Merging to Patch Definitions

One thing I solidified when I read [Boris Cherny's TypeScript book](https://mobile.twitter.com/swyx/status/1135525665971695617) is the power of [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) to fix issues with official typings. I even included this as part of [the Troubleshooting Handbook in the React TypeScript Cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#troubleshooting-handbook-bugs-in-official-typings).

But I'd only patched libraries before, never patched something in the core behavior of JSX itself.

When I googled how to do this, I found [this unhelpful Stackoverflow answer](https://stackoverflow.com/questions/51835611/specify-specific-props-and-accept-general-html-props-in-typescript-react-app) and eventually this [Stackoverflow answer](https://stackoverflow.com/questions/40093655/how-do-i-add-attributes-to-existing-html-elements-in-typescript-jsx):

```ts
declare module 'react' {
     interface HTMLProps<T> {
        block?:string;
        element?:string;
        modifiers?:string;
    }
}
```

But when I tried it, it didn't work.

I ultimately resorted to looking up [the React typings in DefinitelyTyped itself](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts), and realizing that React was exported as a `namespace`. Since Namespaces are a more arcane feature of TypeScript, I've never really used them in application code. 

But it was enough to go on - I swapped `declare module 'react'` with `declare namespace React` and prayed that declaration merging worked.

It did. And now you know too.