Creating Custom Components - Flowbite React
The Flowbite React theming system can be used to create your own themeable components. The system provides powerful utilities from resolve-theme.ts that handle theme resolution, merging, and inheritance.
Using the CLI#
The easiest way to create a new custom component is to use the Flowbite React CLI:
npx flowbite-react@latest create my-component
This will generate a new component with the proper structure and theming setup. The CLI uses the configuration from .flowbite-react/config.json to determine:
- Where to create the component (
path) - Whether to include the
"use client"directive (rsc) - Whether to use TypeScript or JavaScript (
tsx)
You can customize these options in your config file .flowbite-react/config.json:
{
"$schema": "https://unpkg.com/flowbite-react/schema.json",
"components": [],
"dark": true,
"path": "src/components",
"prefix": "",
"rsc": true,
"tsx": true,
"version": 4
}
Component Blueprint#
Here's a minimal blueprint for creating Flowbite React components:
"use client";
import { createTheme } from "flowbite-react/helpers/create-theme";
import { get } from "flowbite-react/helpers/get";
import { resolveProps } from "flowbite-react/helpers/resolve-props";
import { useResolveTheme } from "flowbite-react/helpers/resolve-theme";
import { twMerge } from "flowbite-react/helpers/tailwind-merge";
import { useThemeProvider } from "flowbite-react/theme/provider";
import type { ThemingProps } from "flowbite-react/types";
import { forwardRef, type ComponentProps } from "react";
// 1. Extend the FlowbiteTheme and FlowbiteProps interfaces
declare module "flowbite-react/types" {
interface FlowbiteTheme {
myComponent: MyComponentTheme;
}
interface FlowbiteProps {
myComponent: Partial<WithoutThemingProps<MyComponentProps>>;
}
}
// 2. Theme structure
export interface MyComponentTheme {
base: string;
color: MyComponentColors;
}
export interface MyComponentColors {
info: string;
success: string;
error: string;
}
// 3. Default theme
export const myComponentTheme = createTheme<MyComponentTheme>({
base: "flex items-center font-medium",
color: {
info: "text-blue-600",
success: "text-green-600",
error: "text-red-600",
},
});
// 4. Props
export interface MyComponentProps extends ComponentProps<"div">, ThemingProps<MyComponentTheme> {
color?: keyof MyComponentColors;
}
// 5. Component
export const MyComponent = forwardRef<HTMLDivElement, MyComponentProps>((props, ref) => {
// Get theme from provider
const provider = useThemeProvider();
// Resolve theme with proper inheritance
const theme = useResolveTheme(
[myComponentTheme, provider.theme?.myComponent, props.theme],
[get(provider.clearTheme, "myComponent"), props.clearTheme],
[get(provider.applyTheme, "myComponent"), props.applyTheme],
);
// Resolve props with provider
const { children, color = "info", className, ...restProps } = resolveProps(props, provider.props?.myComponent);
return (
<div ref={ref} className={twMerge(theme.base, theme.color[color], className)} {...restProps}>
{children}
</div>
);
});
MyComponent.displayName = "MyComponent";
Using the Component#
import { createTheme, ThemeProvider } from "flowbite-react";
import { MyComponent } from "./MyComponent";
// Custom theme
const theme = createTheme({
myComponent: {
base: "flex items-center gap-2 rounded-lg p-2",
color: {
info: "bg-blue-100 text-blue-800",
success: "bg-green-100 text-green-800",
error: "bg-red-100 text-red-800",
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
{/* Basic usage */}
<MyComponent>Default Component</MyComponent>
{/* With color */}
<MyComponent color="success">Success Component</MyComponent>
{/* With theme override */}
<MyComponent
theme={{
base: "flex items-center gap-4 rounded-full p-3",
color: {
info: "bg-purple-100 text-purple-800",
},
}}
>
Custom Theme
</MyComponent>
{/* With theme clearing */}
<MyComponent clearTheme={{ color: true }}>Default Color Theme</MyComponent>
{/* With theme application control */}
<MyComponent
theme={{
base: "shadow-lg",
}}
applyTheme={{
base: "merge", // Will merge with existing base styles
}}
>
Merged Styles
</MyComponent>
</ThemeProvider>
);
}
Component Features#
The component automatically supports:
- Global theme inheritance
- Component-level overrides
- Provider-level props
- Theme clearing and applying
- Type safety
Understanding the Blueprint#
Let's break down the key parts of the component blueprint:
1. Extend the FlowbiteTheme interface#
declare module "flowbite-react/types" {
interface FlowbiteTheme {
myComponent: MyComponentTheme;
}
}
This is necessary to enable TypeScript type inference for your component's theme.
2. Theme Structure#
export interface MyComponentTheme {
base: string;
color: MyComponentColors;
}
This interface defines the structure of your component's theme. It should include all the customizable style aspects of your component.
3. Default Theme#
const myComponentTheme = createTheme<MyComponentTheme>({
base: "flex items-center font-medium",
color: {
info: "text-blue-600",
success: "text-green-600",
error: "text-red-600",
},
});
The default theme provides the base styling for your component when no custom theme is applied. Using createTheme ensures type safety and enables Tailwind CSS IntelliSense.
4. Props Interface#
export interface MyComponentProps extends ComponentProps<"div">, ThemingProps<MyComponentTheme> {
color?: keyof MyComponentColors;
}
The props interface extends:
ComponentProps<"div">- All standard HTML div propsThemingProps<MyComponentTheme>- Theme-related props (theme,clearTheme,applyTheme)- Custom props specific to your component
5. Theme Resolution#
const theme = useResolveTheme(
[myComponentTheme, provider.theme?.myComponent, props.theme],
[get(provider.clearTheme, "myComponent"), props.clearTheme],
[get(provider.applyTheme, "myComponent"), props.applyTheme],
);
The useResolveTheme hook handles the complex logic of:
- Merging themes from different sources
- Applying theme clearing
- Controlling how themes are merged or replaced
6. Props Resolution#
const { children, color = "info", className, ...restProps } = resolveProps(props, provider.props?.myComponent);
The resolveProps helper merges component-specific props with provider-level props, with component props taking precedence.
Advanced Component Patterns#
Compound Components#
For more complex components with multiple parts, you can create separate theme structures for each part:
export interface CardTheme {
root: {
base: string;
children: string;
};
img: {
base: string;
horizontal: string;
};
header: {
base: string;
};
footer: {
base: string;
};
}
State-Based Styling#
For components with different states, include state variations in your theme:
export interface ButtonTheme {
base: string;
color: Record<string, string>;
disabled: string;
loading: string;
}
Responsive Components#
For responsive components, include different size variations:
export interface ModalTheme {
base: string;
show: {
on: string;
off: string;
};
sizes: {
sm: string;
md: string;
lg: string;
xl: string;
"2xl": string;
"3xl": string;
"4xl": string;
"5xl": string;
"6xl": string;
"7xl": string;
};
}
Best Practices#
- Keep Theme Structures Flat: Avoid deeply nested theme structures when possible
- Use Descriptive Names: Name theme properties clearly to make customization intuitive
- Provide Sensible Defaults: Default themes should work well without customization
- Document Theme Structure: Include comments or documentation for complex theme properties
- Use Type Safety: Always use TypeScript interfaces for theme structures
- Test with Different Themes: Ensure your component works with various theme configurations
By following these patterns, you can create flexible, themeable components that integrate seamlessly with the Flowbite React theming system.