React
TypeScript

Importing types

You can import all types at once:

import type * as Classed from "@tw-classed/react";

These include:

  • Classed.ClassedComponentType - the type of a classed component
  • Classed.DerivedComponentType The type of a derived component. (Removing the as prop from the base component)
  • Classed.StrictComponentType The type of a strict classed component (Infers required variants based on defaultVariants)
  • Classed.InferVariantProps Infers the variant props of a set of variants (internal but exported for convenience)
  • Classed.VariantProps Infers the variant props of a component
  • Classed.Variants The legal variants of a component (use to type an external variant object)
  • Classed.ComponentProps The props of a component (use to type an external component)

VariantProps - Extract variants from a component

import type * as Classed from "@tw-classed/react";
 
const Button = classed("button", "px-2 py-1", {
  variants: {
    color: {
      blue: "bg-blue-500",
      red: "bg-red-500",
    },
  },
});
 
type ButtonVariants = Classed.VariantProps<typeof Button>;
// {
//     color: "blue" | "red";
// }
 
export type ButtonProps = Classed.ComponentProps<typeof Button>; // All props of the component
export type ButtonProps = React.ComponentProps<typeof Button>; // Also works

DerivedComponentType - Creating a derived component

⚠️

This API is not recommended, see caveats. Use deriveClassed instead.

Sometimes you might have to extend a component with React logic. You can do this by creating a derived component.

For Typescript to work correctly with the as prop you need to override the component's type as DerivedComponentType type. This internally removes the as prop from the base component.

import { DerivedComponentType } from "@tw-classed/react";
import { forwardRef } from "react";
import { classed } from "@tw-classed/react";
 
const BaseButton = classed("button", "px-2 py-4", {
  variants: {
    color: {
      blue: "bg-blue-500",
      red: "bg-red-500",
    },
  },
});
 
type BaseButtonProps = React.ComponentProps<typeof BaseButton> & {
  icon?: React.ReactNode; // Add an icon prop
};
 
const Button = forwardRef<HTMLButtonElement, BaseButtonProps>(
  ({ icon, ...props }, ref) => {
    return (
      <BaseButton {...props} ref={ref}>
        {icon && <span className="mr-2">{icon}</span>}
        {props.children}
      </BaseButton>
    );
  }
) as DerivedComponentType<typeof BaseButton, BaseButtonProps>;
 
() => (
  <Button color="blue" as="a" href="/">
    Click me
  </Button>
);

Caveats

When using this API classed has no way of internally knowing which component to render. This is not a problem when using the derived component directly, however if you wrap the derived component in classed, it will be skipped when an as prop is passed during rendering.

Example:

const Base = classed.div("bg-red-500");
 
const DerivedBase = forwardRef<HTMLDivElement, BaseProps>(
  ({ children, ...props }, ref) => {
    console.log("Hello");
    return (
      <Base {...props} ref={ref}>
        {children}
      </Base>
    );
  }
) as DerivedComponentType<typeof Base>;
 
const ClassedDerivedBase = classed(DerivedBase);
 
() => <ClassedDerivedBase />; // ✅ Logs "Hello"
() => <ClassedDerivedBase as="a" href="/" />; // ❌ Does not log "Hello" - DerivedBase is skipped

To avoid this prefer the deriveClassed API as seen here: Deriving a classed component. This API will tell the classed function to render the derived component and forward the as prop, making the above example work.

StrictComponentType - Creating a strict component

StrictComponentType is a variant of ClassedComponentType that infers the required variants based on the defaultVariants prop or manual string union.

Automatically inferring required variants

By casting the component to StrictComponentType you can automatically infer the required variants based on the defaultVariants prop. Variants without a default variant are required.

const Button = classed("button", {
  base: "px-4 py-2 rounded-md",
  variants: {
    color: {
      primary: "bg-blue-500 text-white",
      secondary: "bg-gray-500 text-white",
    },
    size: {
      sm: "text-sm",
      lg: "text-lg",
    },
    loading: {
      true: "opacity-50 cursor-progress",
    },
  },
  defaultVariants: {
    color: "primary",
    size: "sm",
  },
}) as StrictComponentType<typeof Button>;
 
() => <Button />;
// ❌ TypeError - Missing required prop 'loading'

Manually defining required variants

By passing a string union to StrictComponentType you can manually define the required variants.

const Button = classed("button", {
  base: "px-4 py-2 rounded-md",
  variants: {
    color: {
      primary: "bg-blue-500 text-white",
      secondary: "bg-gray-500 text-white",
    },
    size: {
      sm: "text-sm",
      lg: "text-lg",
    },
    loading: {
      true: "opacity-50 cursor-progress",
    },
  },
}) as StrictComponentType<typeof Button, "color">;
 
() => <Button />;
// ❌ TypeError - Missing required prop 'color'

Typescript 4.9 and above (satisfies api)

On Typescript 4.9 and above you can use the satisfies keyword to define a variant object outside of the classed function. This is useful when creating shared variants.

import { Variants, classed } from "@tw-classed/react";
 
const colorVariants = {
  color: {
    blue: "bg-blue-500",
    red: "bg-red-500",
  },
} satisfies Variants;
 
// In Button.tsx
const Button = classed("button", "px-2 py-1", {
  variants: colorVariants,
});
 
type ButtonVariants = Classed.VariantProps<typeof Button>; // Variants are persisted.