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 componentClassed.DerivedComponentTypeThe type of a derived component. (Removing theasprop from the base component)Classed.StrictComponentTypeThe type of a strict classed component (Infers required variants based ondefaultVariants)Classed.InferVariantPropsInfers the variant props of a set of variants (internal but exported for convenience)Classed.VariantPropsInfers the variant props of a componentClassed.VariantsThe legal variants of a component (use to type an external variant object)Classed.ComponentPropsThe 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 worksDerivedComponentType - 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 skippedTo 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.