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.DerivedComponentType
The type of a derived component. (Removing theas
prop from the base component)Classed.StrictComponentType
The type of a strict classed component (Infers required variants based ondefaultVariants
)Classed.InferVariantProps
Infers the variant props of a set of variants (internal but exported for convenience)Classed.VariantProps
Infers the variant props of a componentClassed.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.