styled-components provides comprehensive TypeScript support with advanced type definitions, generic type preservation, theme typing, and full IntelliSense support for enhanced developer experience.
// Basic component with props
interface ButtonProps {
primary?: boolean;
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
}
const Button = styled.button<ButtonProps>`
background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
padding: ${props => {
switch (props.size) {
case 'small': return '4px 8px';
case 'large': return '12px 24px';
default: return '8px 16px';
}
}};
opacity: ${props => props.disabled ? 0.6 : 1};
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
`;
// Usage with full type safety
<Button primary size="large" disabled={false}>Click me</Button>// styled-components automatically infers component props
const Input = styled.input<{ hasError?: boolean }>`
border-color: ${props => props.hasError ? 'red' : '#ccc'};
`;
// TypeScript knows all input props are available
<Input
type="email" // ✅ Valid input prop
placeholder="Email" // ✅ Valid input prop
hasError={true} // ✅ Custom prop
onChange={handleChange} // ✅ Valid input prop
customProp="invalid" // ❌ TypeScript error
/>// styled-components.d.ts
import 'styled-components';
interface AppTheme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
error: string;
success: string;
warning: string;
info: string;
};
fonts: {
main: string;
mono: string;
sizes: {
small: string;
medium: string;
large: string;
};
};
spacing: {
xs: string;
sm: string;
md: string;
lg: string;
xl: string;
};
breakpoints: {
mobile: string;
tablet: string;
desktop: string;
};
zIndex: {
dropdown: number;
modal: number;
tooltip: number;
overlay: number;
};
shadows: {
light: string;
medium: string;
heavy: string;
};
}
declare module 'styled-components' {
export interface DefaultTheme extends AppTheme {}
}// All theme properties are now type-safe
const ThemedButton = styled.button`
background-color: ${props => props.theme.colors.primary}; // ✅ Type-safe
font-family: ${props => props.theme.fonts.main}; // ✅ Type-safe
padding: ${props => props.theme.spacing.md}; // ✅ Type-safe
box-shadow: ${props => props.theme.shadows.medium}; // ✅ Type-safe
z-index: ${props => props.theme.zIndex.dropdown}; // ✅ Type-safe
// TypeScript will catch typos
color: ${props => props.theme.colors.primaryColor}; // ❌ TypeScript error
`;
// Theme utilities with proper typing
function getColor(theme: DefaultTheme, color: keyof AppTheme['colors']): string {
return theme.colors[color];
}
function getSpacing(theme: DefaultTheme, size: keyof AppTheme['spacing']): string {
return theme.spacing[size];
}// Generic component that preserves type information
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
const StyledList = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`;
const ListItem = styled.div`
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
`;
function TypedList<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<StyledList>
{items.map((item, index) => (
<ListItem key={keyExtractor(item)}>
{renderItem(item, index)}
</ListItem>
))}
</StyledList>
);
}
// Usage with full type safety
interface User {
id: string;
name: string;
email: string;
}
const users: User[] = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
];
<TypedList
items={users} // T is inferred as User
keyExtractor={user => user.id} // user is typed as User
renderItem={user => <span>{user.name}</span>} // user is typed as User
/>// Discriminated union types for conditional props
interface BaseButtonProps {
children: React.ReactNode;
disabled?: boolean;
}
interface LinkButtonProps extends BaseButtonProps {
variant: 'link';
href: string;
target?: '_blank' | '_self';
}
interface RegularButtonProps extends BaseButtonProps {
variant: 'primary' | 'secondary';
onClick: () => void;
type?: 'button' | 'submit' | 'reset';
}
type ButtonProps = LinkButtonProps | RegularButtonProps;
const Button = styled.button<ButtonProps>`
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: ${props => {
if (props.variant === 'link') return 'transparent';
if (props.variant === 'primary') return '#007bff';
return '#6c757d';
}};
color: ${props => props.variant === 'link' ? '#007bff' : 'white'};
text-decoration: ${props => props.variant === 'link' ? 'underline' : 'none'};
`;
// Usage with proper type checking
<Button variant="primary" onClick={() => {}}>Primary Button</Button>
<Button variant="link" href="/about" target="_blank">Link Button</Button>
<Button variant="secondary" href="/invalid">Invalid - TypeScript error</Button>// Polymorphic component with proper typing
interface PolymorphicProps<T extends React.ElementType> {
as?: T;
children: React.ReactNode;
}
type PolymorphicComponentProps<T extends React.ElementType, Props = {}> =
PolymorphicProps<T> &
Props &
Omit<React.ComponentPropsWithoutRef<T>, keyof (PolymorphicProps<T> & Props)>;
interface BoxOwnProps {
variant?: 'default' | 'elevated' | 'outlined';
padding?: keyof AppTheme['spacing'];
}
const StyledBox = styled.div<BoxOwnProps>`
padding: ${props => props.theme.spacing[props.padding || 'md']};
border-radius: 8px;
${props => {
switch (props.variant) {
case 'elevated':
return `
background: white;
box-shadow: ${props.theme.shadows.medium};
`;
case 'outlined':
return `
background: transparent;
border: 1px solid ${props.theme.colors.border};
`;
default:
return `
background: ${props.theme.colors.background};
`;
}
}}
`;
function Box<T extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicComponentProps<T, BoxOwnProps>) {
const Component = as || 'div';
return <StyledBox as={Component} {...props}>{children}</StyledBox>;
}
// Usage with full type safety for different elements
<Box>Default div box</Box>
<Box as="section" variant="elevated">Section box</Box>
<Box as="a" href="/link" variant="outlined">Link box</Box>
<Box as="button" onClick={() => {}} disabled>Button box</Box>// Custom interpolation function with proper typing
type ThemeFunction<Props = {}> = (props: Props & { theme: DefaultTheme }) => string | number;
interface StyledInterpolation<Props = {}> {
(props: Props & { theme: DefaultTheme }): string | number | false | undefined | null;
}
// Helper for creating typed interpolations
function createThemeFunction<Props = {}>(
fn: (theme: DefaultTheme, props: Props) => string | number
): ThemeFunction<Props> {
return (allProps) => {
const { theme, ...props } = allProps;
return fn(theme, props as Props);
};
}
// Usage
interface ColorProps {
variant: 'primary' | 'secondary' | 'danger';
}
const getButtonColor = createThemeFunction<ColorProps>((theme, props) => {
switch (props.variant) {
case 'primary': return theme.colors.primary;
case 'secondary': return theme.colors.secondary;
case 'danger': return theme.colors.error;
default: return theme.colors.primary;
}
});
const TypedButton = styled.button<ColorProps>`
background-color: ${getButtonColor};
color: white;
padding: ${props => props.theme.spacing.md};
`;// Type-safe prop filtering
interface FilteredProps {
variant: 'primary' | 'secondary';
size: 'small' | 'large';
customProp: string;
}
const shouldForwardProp = <T extends keyof FilteredProps>(
prop: string,
defaultValidatorFn: (prop: string) => boolean
): prop is T => {
const customProps: (keyof FilteredProps)[] = ['variant', 'size', 'customProp'];
return !customProps.includes(prop as keyof FilteredProps) && defaultValidatorFn(prop);
};
const FilteredButton = styled.button.withConfig({
shouldForwardProp,
})<FilteredProps>`
background-color: ${props => props.variant === 'primary' ? '#007bff' : '#6c757d'};
padding: ${props => props.size === 'small' ? '4px 8px' : '12px 24px'};
`;// Interface for styled component instances
interface IStyledComponent<Target, Props = {}> extends React.ComponentType<Props> {
styledComponentId: string;
withComponent<NewTarget extends React.ElementType>(
tag: NewTarget
): IStyledComponent<NewTarget, Props>;
attrs<AttrsProps extends object>(
attrs: Attrs<AttrsProps & Props>
): IStyledComponent<Target, Props & AttrsProps>;
}
// Type for style functions
type StyleFunction<Props extends object> = (
executionContext: ExecutionContext & Props
) => Interpolation<Props>;
// Interpolation union type
type Interpolation<Props extends object> =
| string
| number
| false
| undefined
| null
| StyleFunction<Props>
| StyledObject<Props>
| TemplateStringsArray
| Keyframes
| RuleSet<Props>
| Interpolation<Props>[];
// CSS object type
interface StyledObject<Props extends object> {
[key: string]:
| string
| number
| StyleFunction<Props>
| StyledObject<Props>
| undefined;
}// Factory function types
interface StyledComponentFactory<Target, Props = {}> {
(
strings: TemplateStringsArray,
...interpolations: Interpolation<Props>[]
): IStyledComponent<Target, Props>;
attrs<AttrsProps extends object>(
attrs: Attrs<AttrsProps & Props>
): StyledComponentFactory<Target, Props & AttrsProps>;
withConfig(
config: StyledOptions<'web', Props>
): StyledComponentFactory<Target, Props>;
}
// Helper type for creating component factories
type CreateStyledComponent<Target> = <Props = {}>(
target: Target
) => StyledComponentFactory<Target, Props>;// Enable better debugging with displayName
const DebugButton = styled.button.withConfig({
displayName: 'DebugButton'
})<{ variant: string }>`
background: ${props => props.variant === 'primary' ? 'blue' : 'gray'};
`;
// Custom hook for styled component debugging
function useStyledDebug<T extends IStyledComponent<any, any>>(
Component: T,
displayName: string
) {
React.useEffect(() => {
if (process.env.NODE_ENV === 'development') {
(Component as any).displayName = displayName;
}
}, [Component, displayName]);
return Component;
}// CSS property validation
interface CSSProperties {
display?: 'block' | 'inline' | 'flex' | 'grid' | 'none';
flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
// ... other CSS properties
}
// Type-safe CSS object creation
function createCSSObject<T extends CSSProperties>(styles: T): T {
return styles;
}
const flexStyles = createCSSObject({
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
// invalidProperty: 'value' // ❌ TypeScript error
});
const FlexContainer = styled.div`
${flexStyles}
`;// Extract props type from styled component
type ExtractProps<T> = T extends IStyledComponent<any, infer P> ? P : never;
const ExampleButton = styled.button<{ variant: string; size: number }>`
background: blue;
`;
type ButtonProps = ExtractProps<typeof ExampleButton>;
// ButtonProps = { variant: string; size: number }
// Use extracted props in other components
function ButtonWrapper(props: ButtonProps) {
return <ExampleButton {...props} />;
}// Type-safe conditional styling helper
function conditionalStyle<Props>(
condition: (props: Props) => boolean,
styles: string | ((props: Props) => string)
) {
return (props: Props) => {
if (condition(props)) {
return typeof styles === 'function' ? styles(props) : styles;
}
return '';
};
}
interface ConditionalProps {
isActive: boolean;
variant: 'primary' | 'secondary';
}
const ConditionalButton = styled.button<ConditionalProps>`
padding: 12px 24px;
border: none;
${conditionalStyle<ConditionalProps>(
props => props.isActive,
'background-color: #28a745; color: white;'
)}
${conditionalStyle<ConditionalProps>(
props => props.variant === 'primary',
props => `background-color: ${props.theme.colors.primary};`
)}
`;// Fast omit utility for better performance
type FastOmit<T extends object, U extends string | number | symbol> = {
[K in keyof T as K extends U ? never : K]: T[K];
};
// Runtime environment type
type Runtime = 'web' | 'native';
// Data attributes helper type
type DataAttributes = { [key: `data-${string}`]: any };
// Polymorphic component interfaces
interface PolymorphicComponent<T extends React.ElementType> {
<C extends React.ElementType = T>(
props: PolymorphicComponentProps<C, {}>
): React.ReactElement | null;
}
type PolymorphicComponentProps<
T extends React.ElementType,
Props = {}
> = Props &
Omit<React.ComponentPropsWithoutRef<T>, keyof Props> & {
as?: T;
};