Ojasa Mirai

Ojasa Mirai

ReactJS

Loading...

Learning Level

🟢 Beginner🔵 Advanced
🎁 What are Props?📤 Passing Props to Components🔍 Reading Props in Components🎯 Props for Customization✅ Prop Types & Validation🔄 Props vs State⬇️ Passing Functions as Props🚀 Building Reusable Components
Reactjs/Props/Props Customization

🎯 Advanced Props Customization Patterns — Building Polymorphic Systems

Advanced customization patterns enable building component systems that remain flexible and maintainable as requirements grow. Polymorphic components, variant systems, and CSS-in-JS integration allow components to adapt to any context.


🎯 Polymorphic Components (As Prop Pattern)

Polymorphic components accept an `as` prop that determines which HTML element or component they render as. This pattern enables maximum flexibility while maintaining type safety.

The `as` prop pattern allows a single component to render as different HTML elements while maintaining full TypeScript type safety for the underlying element's props. This is powerful for building generic layout components that work anywhere.

import { ElementType, ComponentPropsWithoutRef, forwardRef } from "react";

interface PolymorphicProps<
  E extends ElementType = ElementType
> {
  as?: E;
  children?: React.ReactNode;
  className?: string;
}

type PolymorphicComponentProps<
  E extends ElementType,
  P = {}
> = PolymorphicProps<E> & Omit<ComponentPropsWithoutRef<E>, keyof PolymorphicProps<E>> & P;

interface BoxProps {
  variant?: "default" | "card" | "section";
  padding?: "sm" | "md" | "lg";
}

const Box = forwardRef<
  HTMLDivElement,
  PolymorphicComponentProps<"div", BoxProps>
>(
  (
    {
      as: Component = "div",
      className = "",
      variant = "default",
      padding = "md",
      ...rest
    },
    ref
  ) => {
    const paddingClasses = {
      sm: "p-2",
      md: "p-4",
      lg: "p-8",
    };

    const variantClasses = {
      default: "bg-white",
      card: "bg-white rounded-lg shadow-md",
      section: "bg-gray-50 border",
    };

    return (
      <Component
        ref={ref}
        className={`${variantClasses[variant]} ${paddingClasses[padding]} ${className}`}
        {...rest}
      />
    );
  }
);

// Usage: renders as different elements with correct prop types
<Box as="div" variant="card" padding="lg">
  Content
</Box>

<Box as="section" variant="section">
  Section content
</Box>

<Box as="article" className="custom">
  Article content
</Box>

<details>

<summary>📚 More Examples</summary>

// Example 2: Button polymorphic component
interface ButtonProps {
  variant?: "primary" | "secondary" | "ghost";
  size?: "sm" | "md" | "lg";
  isLoading?: boolean;
}

const Button = forwardRef<
  HTMLButtonElement,
  PolymorphicComponentProps<"button", ButtonProps>
>(
  (
    {
      as: Component = "button",
      variant = "primary",
      size = "md",
      isLoading = false,
      children,
      disabled,
      ...rest
    },
    ref
  ) => {
    const variantClasses = {
      primary: "bg-blue-600 text-white",
      secondary: "bg-gray-200 text-gray-900",
      ghost: "bg-transparent text-gray-600",
    };

    const sizeClasses = {
      sm: "px-2 py-1 text-sm",
      md: "px-4 py-2 text-base",
      lg: "px-6 py-3 text-lg",
    };

    return (
      <Component
        ref={ref}
        className={`${variantClasses[variant]} ${sizeClasses[size]} rounded cursor-pointer disabled:opacity-50`}
        disabled={disabled || isLoading}
        {...rest}
      >
        {isLoading ? "Loading..." : children}
      </Component>
    );
  }
);

// Can render as button or link while maintaining type safety
<Button onClick={() => {}}>Button</Button>
<Button as="a" href="/home">
  Link
</Button>

</details>

💡 Variant Systems and CVA (Class Variance Authority)

Variant systems use props to control styling through predefined combinations. CVA and similar libraries create type-safe, maintainable variant definitions.

Variant systems decouple styling logic from component behavior. By defining all valid combinations upfront, you ensure consistency across your UI and make it impossible to create invalid combinations.

import { cva, type VariantProps } from "class-variance-authority";

// Define all button variants upfront
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded font-medium transition-colors",
  {
    variants: {
      variant: {
        primary: "bg-blue-600 text-white hover:bg-blue-700",
        secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
        danger: "bg-red-600 text-white hover:bg-red-700",
        ghost: "text-gray-600 hover:bg-gray-100",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4 text-base",
        lg: "h-12 px-6 text-lg",
      },
      state: {
        default: "",
        disabled: "opacity-50 cursor-not-allowed",
        loading: "opacity-75 cursor-wait",
      },
    },
    compoundVariants: [
      {
        variant: "ghost",
        state: "disabled",
        className: "opacity-30",
      },
    ],
    defaultVariants: {
      variant: "primary",
      size: "md",
      state: "default",
    },
  }
);

// Extract type from variants
type ButtonVariants = VariantProps<typeof buttonVariants>;

interface ButtonProps
  extends ButtonVariants,
    React.ButtonHTMLAttributes<HTMLButtonElement> {}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, state, ...rest }, ref) => (
    <button
      ref={ref}
      className={buttonVariants({ variant, size, state, className })}
      {...rest}
    />
  )
);

// Type-safe variant combinations
<Button variant="primary" size="lg">
  Save
</Button>

<Button variant="danger" size="sm" state="loading">
  Deleting...
</Button>

// Compound variants ensure consistency
<Button variant="ghost" state="disabled">
  Disabled ghost button
</Button>

<details>

<summary>📚 More Examples</summary>

// Example 2: Card component with CVA
const cardVariants = cva("rounded border", {
  variants: {
    variant: {
      default: "bg-white border-gray-200",
      elevated: "bg-white shadow-lg",
      outlined: "bg-white border-2 border-gray-300",
    },
    interactive: {
      true: "cursor-pointer hover:shadow-md transition-shadow",
      false: "",
    },
    padding: {
      none: "p-0",
      sm: "p-4",
      md: "p-6",
      lg: "p-8",
    },
  },
  compoundVariants: [
    {
      variant: "elevated",
      interactive: true,
      className: "hover:shadow-xl",
    },
  ],
  defaultVariants: {
    variant: "default",
    interactive: false,
    padding: "md",
  },
});

type CardVariants = VariantProps<typeof cardVariants>;

interface CardProps
  extends CardVariants,
    React.HTMLAttributes<HTMLDivElement> {}

const Card = forwardRef<HTMLDivElement, CardProps>(
  ({ className, variant, interactive, padding, ...rest }, ref) => (
    <div
      ref={ref}
      className={cardVariants({ variant, interactive, padding, className })}
      {...rest}
    />
  )
);

</details>

🔧 Extensible Props with Composition

Building extensible components requires designing props that can be extended without modification. This enables plugins, themes, and custom behaviors.

// Base component props
interface BaseComponentProps {
  children?: React.ReactNode;
  className?: string;
  data?: Record<string, any>;
}

// Plugin system for extensibility
interface ComponentPlugin<T = any> {
  name: string;
  enhance?: (props: T) => Partial<T>;
  render?: (component: React.ReactNode) => React.ReactNode;
}

interface ExtensibleComponentProps<P = {}> extends BaseComponentProps {
  plugins?: ComponentPlugin<P>[];
}

function withPlugins<P extends ExtensibleComponentProps>(
  Component: React.ComponentType<P>
) {
  return function PluginComponent({
    plugins = [],
    ...props
  }: P) {
    // Apply plugin enhancements
    let enhancedProps = { ...props };
    plugins.forEach((plugin) => {
      if (plugin.enhance) {
        enhancedProps = { ...enhancedProps, ...plugin.enhance(enhancedProps) };
      }
    });

    // Render component
    let rendered = <Component {...(enhancedProps as P)} />;

    // Apply plugin renders
    plugins.forEach((plugin) => {
      if (plugin.render) {
        rendered = plugin.render(rendered);
      }
    });

    return rendered;
  };
}

// Example usage with plugins
const plugins: ComponentPlugin[] = [
  {
    name: "tooltip",
    render: (component) => (
      <div title="Help text">{component}</div>
    ),
  },
  {
    name: "analytics",
    enhance: (props) => ({
      onClick: () => {
        console.log("Tracked click");
      },
    }),
  },
];

<Component plugins={plugins} />;

<details>

<summary>📚 More Examples</summary>

// Example 2: Theme-aware component system
interface ThemeConfig {
  colors: Record<string, string>;
  spacing: Record<string, string>;
  typography: Record<string, string>;
}

const ThemeContext = React.createContext<ThemeConfig>({
  colors: {},
  spacing: {},
  typography: {},
});

interface ThemedComponentProps extends BaseComponentProps {
  colorScheme?: "primary" | "secondary" | "accent";
  size?: "compact" | "normal" | "spacious";
}

function ThemedComponent({
  colorScheme = "primary",
  size = "normal",
  className,
  ...props
}: ThemedComponentProps) {
  const theme = React.useContext(ThemeContext);

  const themeClassName = `color-${colorScheme} size-${size}`;

  return (
    <div className={`${themeClassName} ${className}`} {...props} />
  );
}

// Customize theme at any level
<ThemeContext.Provider value={customTheme}>
  <ThemedComponent colorScheme="primary" size="spacious" />
</ThemeContext.Provider>

</details>

🎨 Props Composition for Complex UIs

Composing complex props from smaller, reusable prop builders creates maintainable, scalable UI systems.

// Props builder pattern for complex components
interface DialogProps {
  title: string;
  content: React.ReactNode;
  actions: Array<{
    label: string;
    onClick: () => void;
    variant?: "primary" | "secondary" | "danger";
  }>;
  size?: "sm" | "md" | "lg";
  onClose: () => void;
}

// Builder for common dialog patterns
const dialogBuilders = {
  confirm: (message: string) => ({
    content: message,
    actions: [
      { label: "Cancel", variant: "secondary" as const },
      { label: "Confirm", variant: "primary" as const },
    ],
  }),
  alert: (message: string) => ({
    content: message,
    actions: [{ label: "OK", variant: "primary" as const }],
  }),
  delete: (itemName: string) => ({
    content: `Are you sure you want to delete "${itemName}"? This cannot be undone.`,
    actions: [
      { label: "Cancel", variant: "secondary" as const },
      { label: "Delete", variant: "danger" as const },
    ],
  }),
};

// Usage: clean, composable dialog creation
const dialogProps: DialogProps = {
  title: "Confirm Action",
  ...dialogBuilders.confirm("Do you want to proceed?"),
  onClose: () => {},
};

<Dialog {...dialogProps} />;

🔑 Key Takeaways

  • ✅ Polymorphic components with `as` prop enable maximum flexibility with type safety
  • ✅ Variant systems ensure consistency and prevent invalid combinations
  • ✅ CVA and similar libraries scale variant systems efficiently
  • ✅ Plugin systems enable extensibility without modifying core components
  • ✅ Theme context and composition create powerful customization points
  • ✅ Props builders reduce boilerplate and ensure consistency
  • ✅ Layered customization patterns support diverse use cases at scale

Ready to practice? Challenges | Next: Props Validation


Resources

Python Docs

Ojasa Mirai

Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.

Learn Deep • Build Real • Verify Skills • Launch Forward

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn