ComponentPropsProvider
A powerful ComponentPropsProvider that allows you to set global default props and create custom variants for Kousta UI components. Perfect for maintaining consistent design systems and reducing prop duplication across your application.
When to use
- Design systems: Establish consistent defaults across your entire application
- Theming: Apply theme-specific variants (dark mode, brand colors, etc.)
- Prop reduction: Reduce repetitive prop declarations in components
- Custom variants: Create reusable component variations with custom styling
- A/B testing: Easily switch between different component configurations
Quick start
import { ComponentPropsProvider, Button, Modal } from "@kousta-ui/components";
export default function App() {
return (
<ComponentPropsProvider
button={{
size: "sm",
variant: "primary",
className: "app-button",
}}
modal={{
size: "lg",
position: "center",
withBackdrop: true,
}}
>
<div>
{/* All buttons will be small and primary by default */}
<Button>Default Button</Button>
<Button variant="secondary">Override</Button>
{/* All modals will be large and centered */}
<Modal title="Settings">
Settings content
</Modal>
</div>
</ComponentPropsProvider>
);
}
Provider structure
The provider accepts configuration for each component type:
<ComponentPropsProvider
button={ButtonConfiguration}
modal={ModalConfiguration}
menu={MenuConfiguration}
>
<YourApp />
</ComponentPropsProvider>
Button configuration
Basic defaults
<ComponentPropsProvider
button={{
size: "md", // Default size for all buttons
variant: "primary", // Default variant
type: "button", // Default button type
className: "btn", // Default CSS class
style: { // Default inline styles
borderRadius: "8px",
},
loadingIndicator: "Loading...", // Default loading text
}}
>
<Button>Uses provider defaults</Button>
</ComponentPropsProvider>
Custom variants
Create reusable button variants with custom styling:
<ComponentPropsProvider
button={{
// Default props
variant: "primary",
size: "md",
// Custom variants
variants: {
// Ghost variant
ghost: {
className: "btn-ghost",
style: { background: "transparent", border: "1px solid #ddd" },
},
// Brand variant
brand: {
className: "btn-brand",
style: {
background: "linear-gradient(45deg, #667eea, #764ba2)",
color: "white",
border: "none",
},
},
// Danger variant with icon
danger: {
className: "btn-danger",
"aria-label": "Danger action",
style: { background: "#dc2626", color: "white" },
},
// Social login buttons
google: {
className: "btn-google",
style: {
background: "#4285f4",
color: "white",
display: "flex",
alignItems: "center",
gap: "8px",
},
},
},
}}
>
{/* Use custom variants */}
<Button variant="ghost">Ghost Button</Button>
<Button variant="brand">Brand Button</Button>
<Button variant="danger">Delete</Button>
<Button variant="google">
<span>G</span> Sign in with Google
</Button>
{/* Still can override locally */}
<Button size="lg" variant="primary">Large Override</Button>
</ComponentPropsProvider>
Modal configuration
<ComponentPropsProvider
modal={{
size: "md",
position: "center",
withBackdrop: true,
withCloseBtn: true,
closeOnClickEsc: true,
closeOnClickOutside: true,
offset: 0,
}}
>
<Modal title="Default Modal">
This modal uses provider defaults
</Modal>
{/* Override specific props */}
<Modal
title="Large Modal"
size="xl"
position="top"
>
This modal overrides size and position
</Modal>
</ComponentPropsProvider>
Menu configuration
<ComponentPropsProvider
menu={{
// Menu container defaults
menu: {
type: "click",
closeOnClick: true,
position: "Bottom-Start",
offset: 4,
},
// Menu item defaults
menuItem: {
closeMenuOnClick: true,
disabled: false,
},
}}
>
<Menu.Menu>
<Menu.Target>Menu</Menu.Target>
<Menu.DropDown>
<Menu.Item>Item 1</Menu.Item>
<Menu.Item closeMenuOnClick={false}>Item 2 (stays open)</Menu.Item>
</Menu.DropDown>
</Menu.Menu>
</ComponentPropsProvider>
Precedence rules
Understanding how props are merged is crucial:
1. Component props win over provider defaults
<ComponentPropsProvider button={{ size: "lg" }}>
{/* This will be medium, not large */}
<Button size="md">Medium Button</Button>
</ComponentPropsProvider>
2. Provider variants are merged with component props
<ComponentPropsProvider
button={{
variants: {
custom: {
className: "custom-btn",
style: { background: "blue" },
},
},
}}
>
{/* Both provider variant and local style are applied */}
<Button
variant="custom"
style={{ color: "white" }}
>
{/* Final style: background: "blue", color: "white" */}
</Button>
</ComponentPropsProvider>
3. Class names are concatenated
Order of concatenation:
- Provider
className - Variant/size CSS classes
- Component
className
<ComponentPropsProvider button={{ className: "provider-btn" }}>
<Button className="local-btn" variant="primary">
{/* Final className: "provider-btn btn-primary btn-md local-btn" */}
</Button>
</ComponentPropsProvider>
Advanced patterns
Theme-based providers
function ThemeProvider({ children, theme }) {
const themeConfig = {
light: {
button: {
variant: "primary",
className: "light-theme-btn",
},
modal: {
withBackdrop: true,
},
},
dark: {
button: {
variant: "primary-light",
className: "dark-theme-btn",
},
modal: {
withBackdrop: false,
},
},
};
return (
<ComponentPropsProvider {...themeConfig[theme]}>
{children}
</ComponentPropsProvider>
);
}
// Usage
<ThemeProvider theme="dark">
<App />
</ThemeProvider>
Section-specific providers
function AdminPanel() {
return (
<ComponentPropsProvider
button={{
size: "sm",
variant: "neutral-outline",
variants: {
admin: {
className: "admin-btn",
style: { background: "#1f2937", color: "white" },
},
},
}}
>
<div className="admin-panel">
<Button variant="admin">Admin Action</Button>
<Button>Default Admin Button</Button>
</div>
</ComponentPropsProvider>
);
}
Nested providers
function App() {
return (
// Global defaults
<ComponentPropsProvider
button={{ size: "md", variant: "primary" }}
>
<Header />
{/* Override for specific section */}
<ComponentPropsProvider
button={{ size: "sm", variant: "secondary" }}
>
<Sidebar />
</ComponentPropsProvider>
<Footer />
</ComponentPropsProvider>
);
}
Dynamic configuration
function DynamicProvider({ children }) {
const [config, setConfig] = useState({
button: {
size: "md",
variant: "primary",
},
});
// Update configuration based on user preferences
useEffect(() => {
const userPrefs = getUserPreferences();
setConfig({
button: {
size: userPrefs.buttonSize || "md",
variant: userPrefs.theme === "dark" ? "primary-light" : "primary",
},
});
}, []);
return (
<ComponentPropsProvider {...config}>
{children}
</ComponentPropsProvider>
);
}
TypeScript support
Full TypeScript support with proper type inference:
import { ComponentPropsProvider, ButtonPropsProvided } from "@kousta-ui/components";
const buttonConfig: ButtonPropsProvided = {
size: "md",
variant: "primary",
className: "custom-btn",
variants: {
custom: {
className: "custom-variant",
style: { background: "custom-color" },
},
},
};
<ComponentPropsProvider button={buttonConfig}>
<Button>Typed Button</Button>
</ComponentPropsProvider>
Performance considerations
- Provider context: Uses React Context for efficient prop distribution
- Selective updates: Only components that use the affected props will re-render
- Shallow comparison: Props are compared shallowly to avoid unnecessary re-renders
- Static configuration: Best performance when configuration doesn't change frequently
Tip Keep provider configuration stable to avoid unnecessary re-renders.
Migration guide
From individual props
Before:
<Button size="md" variant="primary" className="btn">
Save
</Button>
<Button size="md" variant="primary" className="btn">
Cancel
</Button>
<Button size="md" variant="primary" className="btn">
Delete
</Button>
After:
<ComponentPropsProvider
button={{
size: "md",
variant: "primary",
className: "btn",
}}
>
<Button>Save</Button>
<Button>Cancel</Button>
<Button>Delete</Button>
</ComponentPropsProvider>
Types (reference)
import { ComponentPropsWithoutRef, ReactNode } from "react";
// Button provider types
export type ButtonPropsProvided = Pick<
ButtonProps,
"size" | "type" | "variant" | "style" | "loadingIndicator" | "className"
> & {
variants?: Record<string, ComponentPropsWithoutRef<"button">>;
};
// Menu provider types
export type MenuPropsProvided = {
menu: GetOptionalProperties<MenuProps>;
menuItem: GetOptionalProperties<MenuItemProps>;
};
// Modal provider types
export type ModalPropsProvided = GetOptionalProperties<ModalProps>;
// Main provider type
type PropsContextType = {
button?: ButtonPropsProvided;
menu?: MenuPropsProvided;
modal?: ModalPropsProvided;
};
// Provider component
export const ComponentPropsProvider: ({
children,
...value
}: PropsWithChildren<PropsContextType>) => JSX.Element;