Type safe CSS-in-JS API heavily inspired by react-jss
—
TSS-React provides low-level CSS generation and class manipulation utilities for advanced use cases, direct Emotion integration, and fine-grained styling control. These utilities form the foundation of the higher-level APIs.
Creates css and cx functions bound to a specific Emotion cache instance.
/**
* Creates css and cx functions for a given Emotion cache
* @param params - Configuration object with cache instance
* @returns Object containing css and cx functions
*/
function createCssAndCx(params: { cache: EmotionCache }): {
css: Css;
cx: Cx;
};
interface EmotionCache {
key: string;
inserted: Record<string, string | boolean>;
registered: Record<string, string>;
sheet: StyleSheet;
compat?: boolean;
nonce?: string;
insert: (rule: string, serialized: SerializedStyles) => void;
}Usage Examples:
import createCache from "@emotion/cache";
import { createCssAndCx } from "tss-react/cssAndCx";
// Create custom cache
const cache = createCache({
key: "my-styles",
prepend: true
});
// Create css and cx functions
const { css, cx } = createCssAndCx({ cache });
// Use css function for dynamic styles
const dynamicClass = css({
backgroundColor: "blue",
color: "white",
padding: 16,
"&:hover": {
backgroundColor: "darkblue"
}
});
// Use cx function for combining classes
const combinedClass = cx(
"base-class",
dynamicClass,
{
"conditional-class": true,
"disabled-class": false
}
);
function MyComponent() {
return <div className={combinedClass}>Styled content</div>;
}Creates a React hook that returns css and cx functions with cache integration.
/**
* Creates a hook that returns css and cx functions
* @param params - Configuration object with cache hook
* @returns Object containing useCssAndCx hook
*/
function createUseCssAndCx(params: {
useCache: () => EmotionCache;
}): {
useCssAndCx(): { css: Css; cx: Cx };
};Usage Examples:
import createCache from "@emotion/cache";
import { createUseCssAndCx } from "tss-react/cssAndCx";
import { useState } from "react";
// Create cache provider hook
function useMyCache() {
const [cache] = useState(() => createCache({
key: "dynamic-styles",
prepend: true
}));
return cache;
}
// Create hook factory
const { useCssAndCx } = createUseCssAndCx({
useCache: useMyCache
});
// Use in components
function DynamicStyledComponent({ variant }: { variant: "primary" | "secondary" }) {
const { css, cx } = useCssAndCx();
const baseStyles = css({
padding: "12px 24px",
borderRadius: "4px",
border: "none",
cursor: "pointer",
fontSize: "16px",
fontWeight: "500",
transition: "all 0.2s ease"
});
const variantStyles = css(
variant === "primary"
? {
backgroundColor: "#007bff",
color: "white",
"&:hover": { backgroundColor: "#0056b3" }
}
: {
backgroundColor: "#6c757d",
color: "white",
"&:hover": { backgroundColor: "#545b62" }
}
);
return (
<button className={cx(baseStyles, variantStyles)}>
Click me
</button>
);
}Template literal and function-based CSS generation with Emotion serialization.
interface Css {
/**
* Template literal CSS generation
* @param template - Template strings array
* @param args - Interpolated CSS values
* @returns Generated CSS class name
*/
(template: TemplateStringsArray, ...args: CSSInterpolation[]): string;
/**
* Function-based CSS generation
* @param args - CSS interpolation values
* @returns Generated CSS class name
*/
(...args: CSSInterpolation[]): string;
}
type CSSInterpolation =
| CSSObject
| string
| number
| false
| null
| undefined
| CSSInterpolation[];Usage Examples:
import { css } from "my-css-instance";
// Template literal usage
const templateClass = css`
background-color: ${"red"};
color: white;
padding: ${16}px;
&:hover {
background-color: ${"darkred"};
}
@media (max-width: 768px) {
padding: ${8}px;
}
`;
// Function usage with CSS object
const objectClass = css({
backgroundColor: "blue",
color: "white",
padding: 16,
borderRadius: 4,
"&:hover": {
backgroundColor: "darkblue",
transform: "scale(1.02)"
},
"@media (max-width: 768px)": {
padding: 8,
fontSize: 14
}
});
// Function usage with multiple arguments
const multiArgClass = css(
{ backgroundColor: "green", color: "white" },
{ padding: 12, borderRadius: 8 },
{
"&:active": {
transform: "scale(0.98)"
}
}
);
// Dynamic CSS generation
function createButtonStyles(theme: any, variant: string) {
return css({
backgroundColor: theme.colors[variant],
color: theme.textColors[variant],
padding: theme.spacing.md,
borderRadius: theme.borderRadius,
border: "none",
cursor: "pointer",
fontSize: theme.fontSizes.md,
fontWeight: theme.fontWeights.semibold,
transition: "all 0.2s ease",
"&:hover": {
backgroundColor: theme.colors[`${variant}Hover`],
boxShadow: theme.shadows.md
},
"&:focus": {
outline: `2px solid ${theme.colors.focus}`,
outlineOffset: "2px"
},
"&:disabled": {
opacity: 0.5,
cursor: "not-allowed"
}
});
}Classname combination utility with conditional logic support.
/**
* Combines multiple class names with conditional logic
* @param classNames - Array of class name arguments
* @returns Combined class name string
*/
type Cx = (...classNames: CxArg[]) => string;
type CxArg =
| string
| number
| boolean
| undefined
| null
| { [className: string]: boolean | undefined | null }
| CxArg[];Usage Examples:
import { cx } from "my-cx-instance";
// Basic class combination
const basicClasses = cx("btn", "btn-primary", "btn-large");
// Result: "btn btn-primary btn-large"
// Conditional classes with object syntax
const conditionalClasses = cx(
"btn",
{
"btn-primary": variant === "primary",
"btn-secondary": variant === "secondary",
"btn-disabled": disabled,
"btn-loading": loading
}
);
// Mixed arguments
const mixedClasses = cx(
"base-class",
dynamicClass,
{
"active": isActive,
"highlighted": isHighlighted
},
extraClass && "extra-class",
["array", "of", "classes"]
);
// Real-world component example
function Button({
variant = "primary",
size = "medium",
disabled = false,
loading = false,
className,
children,
...props
}: ButtonProps) {
const { css, cx } = useCssAndCx();
const baseStyles = css({
border: "none",
borderRadius: 4,
cursor: "pointer",
fontWeight: 500,
transition: "all 0.2s ease",
display: "inline-flex",
alignItems: "center",
justifyContent: "center"
});
const sizeStyles = css({
padding: {
small: "6px 12px",
medium: "8px 16px",
large: "12px 24px"
}[size],
fontSize: {
small: "14px",
medium: "16px",
large: "18px"
}[size]
});
const variantStyles = css(
variant === "primary"
? { backgroundColor: "#007bff", color: "white" }
: { backgroundColor: "#6c757d", color: "white" }
);
const buttonClass = cx(
baseStyles,
sizeStyles,
variantStyles,
{
[css({ opacity: 0.5, cursor: "not-allowed" })]: disabled,
[css({ position: "relative", "&::after": { content: '"..."' } })]: loading
},
className
);
return (
<button
className={buttonClass}
disabled={disabled || loading}
{...props}
>
{children}
</button>
);
}Utility function for merging style classes with override support, particularly useful for component libraries.
/**
* Merges classes from useStyles with override classes
* @param classesFromUseStyles - Classes generated by TSS-React hooks
* @param classesOverrides - Optional override classes from props
* @param cx - Class combination function
* @returns Merged classes object with overrides applied
*/
function mergeClasses<T extends string, U extends string>(
classesFromUseStyles: Record<T, string>,
classesOverrides: Partial<Record<U, string>> | undefined,
cx: Cx
): Record<T, string> & Partial<Record<Exclude<U, T>, string>>;Usage Examples:
import { mergeClasses } from "tss-react";
import { tss } from "tss-react";
// Component with style overrides support
interface CardProps {
title: string;
children: React.ReactNode;
classes?: {
root?: string;
title?: string;
content?: string;
footer?: string;
};
}
const useCardStyles = tss.create({
root: {
backgroundColor: "white",
borderRadius: 8,
padding: 16,
boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
},
title: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 12,
color: "#333"
},
content: {
color: "#666",
lineHeight: 1.5
}
});
function Card({ title, children, classes: classesOverrides }: CardProps) {
const { classes: defaultClasses, cx } = useCardStyles();
// Merge default classes with overrides
const classes = mergeClasses(defaultClasses, classesOverrides, cx);
return (
<div className={classes.root}>
<h3 className={classes.title}>{title}</h3>
<div className={classes.content}>{children}</div>
</div>
);
}
// Usage with overrides
function App() {
return (
<Card
title="Custom Card"
classes={{
root: "custom-card-root",
title: "custom-card-title",
// Additional classes not in original component
footer: "custom-footer-class"
}}
>
<p>Card content here</p>
</Card>
);
}
// Component library pattern
const useLibraryStyles = tss
.withParams<{ variant: "outlined" | "filled"; size: "small" | "large" }>()
.create(({ theme }, { variant, size }) => ({
root: {
padding: size === "small" ? 8 : 16,
borderRadius: 4,
...(variant === "outlined" && {
border: "1px solid #ccc",
backgroundColor: "transparent"
}),
...(variant === "filled" && {
backgroundColor: "#f5f5f5",
border: "none"
})
},
label: {
fontSize: size === "small" ? 14 : 16,
fontWeight: 500
}
}));
interface LibraryComponentProps {
variant: "outlined" | "filled";
size: "small" | "large";
label: string;
classes?: {
root?: string;
label?: string;
icon?: string;
};
}
function LibraryComponent({
variant,
size,
label,
classes: classesOverrides
}: LibraryComponentProps) {
const { classes: defaultClasses, cx } = useLibraryStyles({ variant, size });
const classes = mergeClasses(defaultClasses, classesOverrides, cx);
return (
<div className={classes.root}>
<span className={classes.label}>{label}</span>
{classes.icon && <span className={classes.icon}>→</span>}
</div>
);
}import createCache from "@emotion/cache";
import { createCssAndCx } from "tss-react/cssAndCx";
// Performance-optimized cache
const performanceCache = createCache({
key: "perf",
speedy: true, // No text content in style elements (faster)
prepend: true // Insert at beginning of head
});
// Development cache with debugging
const devCache = createCache({
key: "dev",
speedy: false, // Include text content for debugging
nonce: process.env.CSP_NONCE
});
const { css: perfCss, cx: perfCx } = createCssAndCx({
cache: process.env.NODE_ENV === "production" ? performanceCache : devCache
});function createDynamicStyles(theme: any) {
const { css, cx } = createCssAndCx({ cache: theme.cache });
return {
// Generate styles based on theme configuration
generateButtonStyle: (variant: string, size: string) => css({
backgroundColor: theme.palette[variant].main,
color: theme.palette[variant].contrastText,
padding: theme.spacing[size],
borderRadius: theme.shape.borderRadius,
fontSize: theme.typography[size].fontSize,
"&:hover": {
backgroundColor: theme.palette[variant].dark
}
}),
// Combine multiple dynamic styles
combineStyles: (...styles: string[]) => cx(...styles),
// Generate responsive utilities
generateResponsiveStyle: (breakpoints: Record<string, any>) => css(
Object.entries(breakpoints).reduce((acc, [bp, styles]) => ({
...acc,
[`@media (min-width: ${theme.breakpoints[bp]})`]: styles
}), {})
)
};
}Install with Tessl CLI
npx tessl i tessl/npm-tss-react