TypeScript Tips
TypeScript best practices and advanced types for typestyles
TypeScript Tips
TypeStyles is built with TypeScript in mind. This guide covers tips for getting the most out of types.
Basic types
Style definitions
TypeStyles automatically infers types from your definitions:
import { styles } from 'typestyles';
// Types are inferred automatically
const button = styles.create('button', {
base: {
padding: '8px 16px',
backgroundColor: '#0066ff',
},
primary: {
color: 'white',
},
});
// selector function is typed
const classes = button('base', 'primary');
// ^? string
Token types
Token references are typed as strings:
import { tokens } from 'typestyles';
const color = tokens.create('color', {
primary: '#0066ff',
secondary: '#6b7280',
});
// color is typed with specific keys
color.primary; // string
color.secondary; // string
color.tertiary; // Error: Property 'tertiary' does not exist
Strict mode compatibility
TypeStyles works great with TypeScript's strict mode. Ensure your tsconfig.json has:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
Extending types
Custom CSS properties
If you need custom CSS properties that aren't in the standard types:
import { CSSProperties } from 'typestyles';
// Extend the base type
interface CustomProperties extends CSSProperties {
'--custom-property'?: string;
'--theme-color'?: string;
}
// Use in your style definitions
const customStyles: Record<string, CustomProperties> = {
base: {
'--custom-property': 'value',
'--theme-color': '#0066ff',
},
};
Custom at-rules
For custom at-rules that TypeStyles doesn't know about:
interface CustomAtRules extends CSSProperties {
'@layer'?: Record<string, CSSProperties>;
}
const styles: Record<string, CustomAtRules> = {
base: {
'@layer': {
utilities: {
padding: '8px',
},
},
},
};
Component prop types
Typed variant props
Make your component props type-safe:
import { styles } from 'typestyles';
const button = styles.create('button', {
base: { ... },
primary: { ... },
secondary: { ... },
ghost: { ... },
small: { ... },
medium: { ... },
large: { ... },
});
// Extract variant types
type ButtonVariants = Parameters<typeof button>;
// ^? ('base' | 'primary' | 'secondary' | 'ghost' | 'small' | 'medium' | 'large' | false | null | undefined)[]
// Or define explicitly
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'small' | 'medium' | 'large';
children: React.ReactNode;
}
function Button({ variant = 'primary', size = 'medium', children }: ButtonProps) {
return (
<button className={button('base', variant, size)}>
{children}
</button>
);
}
Stricter object literals
You can add as const to nested values when you want literal types preserved (for example token-like maps). Variant keys for styles.create are already inferred from the definitions object; use explicit component prop types when you need a narrower public API than the style keys alone.
Utility types
Extracting style types
import { styles } from 'typestyles';
const card = styles.create('card', {
base: { ... },
elevated: { ... },
});
// Get the style definition type
type CardStyle = Parameters<typeof card>[number];
// ^? 'base' | 'elevated' | null | undefined | false
// Create a type for your component props
type CardProps = {
variant?: Extract<CardStyle, string>; // Only the string variants
};
Token type extraction
import { tokens } from 'typestyles';
const themeTokens = {
color: tokens.create('color', {
primary: '#0066ff',
secondary: '#6b7280',
}),
space: tokens.create('space', {
sm: '8px',
md: '16px',
}),
};
// Extract specific token types
type ColorToken = keyof typeof themeTokens.color;
// ^? 'primary' | 'secondary'
type SpaceToken = keyof typeof themeTokens.space;
// ^? 'sm' | 'md'
Type-safe themes
Theme type definition
// types/theme.ts
export interface Theme {
color: {
primary: string;
secondary: string;
text: string;
surface: string;
};
space: {
sm: string;
md: string;
lg: string;
};
}
// Ensure your tokens match the theme
export const color = tokens.create('color', {
primary: '#0066ff',
secondary: '#6b7280',
text: '#111827',
surface: '#ffffff',
});
// TypeScript will error if you miss a key
Theme-aware components
import { tokens } from 'typestyles';
const themeTokens = {
color: tokens.create('color', {
primary: '#0066ff',
secondary: '#6b7280',
}),
space: tokens.create('space', {
sm: '8px',
md: '16px',
}),
};
interface ThemedComponentProps {
color: keyof typeof themeTokens.color;
space: keyof typeof themeTokens.space;
}
function ThemedComponent({ color, space }: ThemedComponentProps) {
const inline = {
color: themeTokens.color[color],
padding: themeTokens.space[space],
};
return <div style={inline}>Content</div>;
}
// Usage with autocomplete:
// <ThemedComponent color="primary" space="md" />
Generic components
Generic style components
import { styles } from 'typestyles';
// Generic component that accepts any style set
function StyledBox<T extends string>({
styleSet,
variant,
children,
}: {
styleSet: { (...variants: (T | false | null | undefined)[]): string };
variant?: T;
children: React.ReactNode;
}) {
return <div className={styleSet('base', variant)}>{children}</div>;
}
// Usage
const box = styles.create('box', {
base: { padding: '16px' },
elevated: { boxShadow: '0 4px 6px rgba(0,0,0,0.1)' },
});
<StyledBox styleSet={box} variant="elevated">
Content
</StyledBox>;
Conditional types
Responsive style types
type Breakpoint = 'sm' | 'md' | 'lg' | 'xl';
type ResponsiveValue<T> = T | Partial<Record<Breakpoint, T>>;
interface ResponsiveProps {
padding: ResponsiveValue<string>;
display: ResponsiveValue<'block' | 'flex' | 'grid'>;
}
// Implementation would handle responsive logic
Variant combinations
// Type for all possible button combinations
type ButtonVariant =
| { variant: 'primary'; size: 'small' | 'medium' | 'large' }
| { variant: 'secondary'; size: 'small' | 'medium' | 'large' }
| { variant: 'ghost'; size: 'small' | 'medium' };
function Button(props: ButtonVariant & { children: React.ReactNode }) {
const { variant, size, children } = props;
// Implementation
}
// TypeScript enforces valid combinations:
Button({ variant: 'primary', size: 'large', children: 'Click' }); // ✓
Button({ variant: 'ghost', size: 'large', children: 'Click' }); // ✗ Error: 'large' not assignable
Module augmentation
Extending typestyles types
If you need to add custom types to typestyles:
// types/typestyles.d.ts
declare module 'typestyles' {
export interface CSSProperties {
// Add custom properties
'anchor-name'?: string;
'position-anchor'?: string;
// Add custom values to existing properties
display?: 'block' | 'flex' | 'grid' | 'custom-value';
}
}
Type guards
Safe variant checking
function isValidVariant(
variant: string
): variant is 'primary' | 'secondary' | 'ghost' {
return ['primary', 'secondary', 'ghost'].includes(variant);
}
function Button({ variant }: { variant?: string }) {
const safeVariant = variant && isValidVariant(variant) ? variant : 'primary';
return <button className={button('base', safeVariant)}>Click</button>;
}
Configuration types
Strict style configuration
// styles/config.ts
import { styles } from 'typestyles';
interface StyleConfig<V extends string> {
namespace: string;
variants: Record<V, CSSProperties>;
}
function createStrictStyles<V extends string>(config: StyleConfig<V>) {
return styles.create(config.namespace, config.variants);
}
// Usage with full type safety
const button = createStrictStyles({
namespace: 'button',
variants: {
base: { padding: '8px' },
primary: { backgroundColor: 'blue' },
},
});
// TypeScript knows these are the only valid variants
button('base', 'primary'); // ✓
button('invalid'); // ✗ Type error
Type narrowing
Narrowing with type predicates
// Define your variant type
type ButtonVariant = 'primary' | 'secondary' | 'ghost';
// Type predicate function
function isButtonVariant(value: string): value is ButtonVariant {
return ['primary', 'secondary', 'ghost'].includes(value);
}
// Use in component
function Button({ variant: variantProp }: { variant?: string }) {
const variant: ButtonVariant = isButtonVariant(variantProp ?? '')
? variantProp
: 'primary';
return <button className={button('base', variant)}>Click</button>;
}
Best practices
- Let types be inferred when possible
- Define explicit interfaces for component props
- Use
as conston nested maps when you need literal types - Extract shared types to avoid duplication
- Leverage
keyoffor token-based props - Use strict mode for best type safety
- Export types that consumers might need
- Document complex types with JSDoc comments
Nested keys: container(), has(), is(), where()
Style objects allow nested keys that start with & (pseudos, descendants), [ (attributes), or @ (at-rules). When you use a computed key next to normal longhands (color, padding, …), TypeScript must keep that key as a narrow template literal. If it widens to plain string, the object no longer matches CSSProperties and you might be tempted to use as CSSProperties.
TypeStyles narrows keys when you use the builders with literal inputs:
styles.container({ minWidth: 400 }),styles.container('sidebar', { minWidth: 300 }), andstyles.container('(min-width: 1px)')infer a concrete`@container …`key.styles.has('.active'),styles.is(':hover', ':focus-visible'),styles.where('.nav')infer a concrete`&:…`key.
So this pattern type-checks without casting:
import { styles } from 'typestyles';
styles.class('card', {
color: 'inherit',
[styles.container({ minWidth: 400 })]: { display: 'grid' },
[styles.has('.expanded')]: { borderColor: 'blue' },
});
When the query or selector is only known as a string variable at compile time, use …styles.atRuleBlock(containerKey, { … }) (or spread a one-key object) instead of [someString]: { … }. See Custom selectors & at-rules.
Common type issues
Issue: "Type instantiation is excessively deep"
This can happen with very complex nested styles. Solution: simplify nesting or add explicit type annotations.
// If you get deep type errors, add explicit return type
const complex = styles.create('complex', {
base: {
// very deep nesting
},
} as const); // or use explicit type annotation
Issue: "Property does not exist"
Make sure you're importing the correct types:
// ✅ Import from typestyles
import type { CSSProperties } from 'typestyles';
// ❌ Don't use React's CSSProperties for styles
import type { CSSProperties } from 'react'; // Wrong!
Issue: "Excessively deep type instantiation"
Break complex styles into smaller pieces:
// ❌ Avoid very complex single definitions
const complex = styles.create('complex', {
base: {
// hundreds of lines
},
});
// ✅ Break into logical groups
const header = styles.create('header', { ... });
const content = styles.create('content', { ... });
const footer = styles.create('footer', { ... });
Summary
TypeStyles provides excellent TypeScript support out of the box. Key points:
- Types are inferred automatically
- Strict mode is fully supported
- You can extend types for custom use cases
- Use explicit interfaces for component props
- Leverage TypeScript's type system for variant safety