Advanced Usage
Nested Component Structure
Recast excels at managing complex React component structures, offering a significant advantage over other styling utilities like cva (opens in a new tab), tv (opens in a new tab), and stitches (opens in a new tab). Let's explore how Recast handles nested components using a slider example based on Radix UI (opens in a new tab).
Example: Slider Component
Here's how you can create a slider component with independently styled elements:
import { cn } from "../../utils/cn.js";
import * as RadixSliderPrimitive from "@radix-ui/react-slider";
import { RecastWithClassNameProps } from "@rpxl/recast";
import React, { forwardRef } from "react";
type Props = React.ComponentPropsWithoutRef<typeof RadixSliderPrimitive.Root> &
RecastWithClassNameProps<{
root: string;
track: string;
range: string;
thumb: string;
}>;
const Slider = forwardRef<
React.ElementRef<typeof RadixSliderPrimitive.Root>,
Props
>(({ className, cls, ...props }, ref) => {
return (
<RadixSliderPrimitive.Root
ref={ref}
className={cn(cls?.root, className)}
{...props}
>
<RadixSliderPrimitive.Track className={cls?.track}>
<RadixSliderPrimitive.Range className={cls?.range} />
</RadixSliderPrimitive.Track>
<RadixSliderPrimitive.Thumb className={cls?.thumb} />
</RadixSliderPrimitive.Root>
);
});
Now you can apply styles to nested elements when wrapping your component with Recast:
export const Slider = recast(SliderPrimitive, {
base: {
root: "relative flex w-full touch-none select-none items-center",
track: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-black",
range: "bg-primary absolute h-full",
thumb: "block h-4 w-4 rounded-full border border-black/50 bg-white shadow",
},
// Other Recast theme props can be added here
});
TypeScript Integration
Recast is designed to work seamlessly with TypeScript. When defining your
component props, you can use the RecastWithClassNameProps
type to ensure type
safety for your nested class names:
import { RecastWithClassNameProps } from "@rpxl/recast";
type Props = YourComponentProps &
RecastWithClassNameProps<{
root: string;
childElement: string;
// ... other nested elements
}>;
This approach provides excellent TypeScript support, enabling autocompletion and type checking for your themed components.
Wrapping a Recast Component
Sometimes you might want to wrap a Recast component to customize its behavior. Here's an example of wrapping a ScrollArea component to infer the variant based on the orientation prop:
"use client";
import { recast } from "@rpxl/recast";
import { ScrollAreaPrimitive } from "@/components/primitives/scroll-area.tsx";
import { forwardRef } from "react";
const BaseScrollArea = recast(ScrollAreaPrimitive, {
defaults: { variants: { variant: "vertical" } },
base: {
root: "relative overflow-hidden",
viewport: "h-full w-full rounded-[inherit]",
thumb: "bg-border relative flex-1 rounded-full",
scrollbar: "flex touch-none select-none transition-colors",
},
variants: {
variant: {
vertical: {
scrollbar: "h-full w-2.5 border-l border-l-transparent p-[1px]",
},
horizontal: {
scrollbar: "h-2.5 flex-col border-t border-t-transparent p-[1px]",
},
},
},
});
type Props = Omit<
React.ComponentPropsWithoutRef<typeof BaseScrollArea>,
"variant"
>;
export const ScrollArea = forwardRef<
React.ElementRef<typeof BaseScrollArea>,
Props
>(({ ...props }, ref) => {
return <BaseScrollArea ref={ref} variant={props.orientation} {...props} />;
});
ScrollArea.displayName = "ScrollArea";
This approach allows you to create more intuitive APIs for your components while still leveraging Recast's theming capabilities.