typescriptweb-componentsolid-js

SolidJS with Material 3 Web Components type definitions


Using SolidJS, how can I properly import the official Material 3 Web Components? e.g. https://github.com/material-components/material-web/blob/main/docs/components/tabs.md

My main issue so far has been typescript errors, I can see the components in the page itself.

I've imported the required js files like so in my index.tsx:

import '@material/web/button/filled-button.js';
import '@material/web/button/outlined-button.js';
import '@material/web/checkbox/checkbox.js';
import '@material/web/tabs/tabs.js';
import '@material/web/tabs/secondary-tab.js';
import '@material/web/tabs/primary-tab.js';

The problem is that I get type errors for each Web Component instance:

<md-tabs>
  <md-primary-tab>Birds</md-primary-tab>
  <md-secondary-tab>Cats</md-secondary-tab>
  <md-secondary-tab>Dogs</md-secondary-tab>
</md-tabs>

I know I could kind of work around it by doing something like the following, but that's using any which isn't really a good solution.

declare module "solid-js" {
  namespace JSX {
    interface IntrinsicElements {
      "md-tabs": any
    }
  }
}

Material 3 Web Components ship with .d.ts files, but I'm not sure how to make SolidJS aware of those declarations, I've tried /// <reference types="@material/web/all" /> to no avail.

I've also tried the following:

import { MdTabs } from "@material/web/tabs/tabs.js";
import { MdPrimaryTab } from "@material/web/tabs/primary-tab";
import { MdSecondaryTab } from "@material/web/tabs/secondary-tab.js";

declare module "solid-js" {
  namespace JSX {
    interface IntrinsicElements {
      "md-tabs": MdTabs,
      'md-primary-tab': MdPrimaryTab,
      'md-secondary-tab': MdSecondaryTab
    }
  }
}

But then it still shows an error for md-primary-tab and md-secondary-tab (no error for md-tabs though).

Type 'import("c:/Users/USER/dev/sstart/node_modules/solid-js/types/jsx").JSX.Element' is not assignable to type 'Element'.
  Type 'undefined' is not assignable to type 'Element'.ts(2322)
This JSX tag's 'children' prop expects type 'HTMLCollection' which requires multiple children, but only a single child was provided.ts(2745)

A workaround is to do the following, but it's still feels pretty hacky:

declare module "solid-js" {
  namespace JSX {
    type MarriedWithChildren = {children:any} | {} | {class?:string, classList?:Record<string,boolean>
    interface IntrinsicElements {
      "md-tabs": MdTabs | MarriedWithChildren,
      'md-primary-tab': MdPrimaryTab | MarriedWithChildren,
      'md-secondary-tab': MdSecondaryTab | MarriedWithChildren
    }
  }
}

Solution

  • After some playing around, I ended up doing the following in global.d.ts

    import "@material/web/all"
    
    type WithAnyChildrenAndClasses = {children:any} | {} | {class?:string, classList?:Record<string,boolean>};
    
    type SolidInterface = {
      [P in keyof HTMLElementTagNameMap]: HTMLElementTagNameMap[P] | WithAnyChildrenAndClasses;
    };
    
    declare module "solid-js" {
      namespace JSX {
        interface IntrinsicElements extends SolidInterface { }
      }
    }
    

    Update

    A better solution has been suggested here:
    https://github.com/shoelace-style/shoelace/discussions/770#discussioncomment-2852125

    declare module 'solid-js' {
      namespace JSX {
        type ElementProps<T> = {
          // Add both the element's prefixed properties and the attributes
          [K in keyof T]: Props<T[K]> & HTMLAttributes<T[K]>;
        }
        // Prefixes all properties with `prop:` to match Solid's property setting syntax
        type Props<T> = {
          [K in keyof T as `prop:${string & K}`]?: T[K];
        }
        interface IntrinsicElements extends ElementProps<HTMLElementTagNameMap> {
        }
      }
    }
    

    PS
    To use the classList syntax, with tailwind autocompletion such as

    <md-button classList={{"bg-blue-500":someSignal()}}></md-button>
    

    add the following in vscode/.settings.json

    {
        "tailwindCSS.classAttributes": ["class", "classList", "className"]
    }