0
# CSS Utilities
1
2
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.
3
4
## Capabilities
5
6
### CSS and CX Factory Function
7
8
Creates css and cx functions bound to a specific Emotion cache instance.
9
10
```typescript { .api }
11
/**
12
* Creates css and cx functions for a given Emotion cache
13
* @param params - Configuration object with cache instance
14
* @returns Object containing css and cx functions
15
*/
16
function createCssAndCx(params: { cache: EmotionCache }): {
17
css: Css;
18
cx: Cx;
19
};
20
21
interface EmotionCache {
22
key: string;
23
inserted: Record<string, string | boolean>;
24
registered: Record<string, string>;
25
sheet: StyleSheet;
26
compat?: boolean;
27
nonce?: string;
28
insert: (rule: string, serialized: SerializedStyles) => void;
29
}
30
```
31
32
**Usage Examples:**
33
34
```typescript
35
import createCache from "@emotion/cache";
36
import { createCssAndCx } from "tss-react/cssAndCx";
37
38
// Create custom cache
39
const cache = createCache({
40
key: "my-styles",
41
prepend: true
42
});
43
44
// Create css and cx functions
45
const { css, cx } = createCssAndCx({ cache });
46
47
// Use css function for dynamic styles
48
const dynamicClass = css({
49
backgroundColor: "blue",
50
color: "white",
51
padding: 16,
52
"&:hover": {
53
backgroundColor: "darkblue"
54
}
55
});
56
57
// Use cx function for combining classes
58
const combinedClass = cx(
59
"base-class",
60
dynamicClass,
61
{
62
"conditional-class": true,
63
"disabled-class": false
64
}
65
);
66
67
function MyComponent() {
68
return <div className={combinedClass}>Styled content</div>;
69
}
70
```
71
72
### CSS-and-CX Hook Factory
73
74
Creates a React hook that returns css and cx functions with cache integration.
75
76
```typescript { .api }
77
/**
78
* Creates a hook that returns css and cx functions
79
* @param params - Configuration object with cache hook
80
* @returns Object containing useCssAndCx hook
81
*/
82
function createUseCssAndCx(params: {
83
useCache: () => EmotionCache;
84
}): {
85
useCssAndCx(): { css: Css; cx: Cx };
86
};
87
```
88
89
**Usage Examples:**
90
91
```typescript
92
import createCache from "@emotion/cache";
93
import { createUseCssAndCx } from "tss-react/cssAndCx";
94
import { useState } from "react";
95
96
// Create cache provider hook
97
function useMyCache() {
98
const [cache] = useState(() => createCache({
99
key: "dynamic-styles",
100
prepend: true
101
}));
102
return cache;
103
}
104
105
// Create hook factory
106
const { useCssAndCx } = createUseCssAndCx({
107
useCache: useMyCache
108
});
109
110
// Use in components
111
function DynamicStyledComponent({ variant }: { variant: "primary" | "secondary" }) {
112
const { css, cx } = useCssAndCx();
113
114
const baseStyles = css({
115
padding: "12px 24px",
116
borderRadius: "4px",
117
border: "none",
118
cursor: "pointer",
119
fontSize: "16px",
120
fontWeight: "500",
121
transition: "all 0.2s ease"
122
});
123
124
const variantStyles = css(
125
variant === "primary"
126
? {
127
backgroundColor: "#007bff",
128
color: "white",
129
"&:hover": { backgroundColor: "#0056b3" }
130
}
131
: {
132
backgroundColor: "#6c757d",
133
color: "white",
134
"&:hover": { backgroundColor: "#545b62" }
135
}
136
);
137
138
return (
139
<button className={cx(baseStyles, variantStyles)}>
140
Click me
141
</button>
142
);
143
}
144
```
145
146
### CSS Function Interface
147
148
Template literal and function-based CSS generation with Emotion serialization.
149
150
```typescript { .api }
151
interface Css {
152
/**
153
* Template literal CSS generation
154
* @param template - Template strings array
155
* @param args - Interpolated CSS values
156
* @returns Generated CSS class name
157
*/
158
(template: TemplateStringsArray, ...args: CSSInterpolation[]): string;
159
160
/**
161
* Function-based CSS generation
162
* @param args - CSS interpolation values
163
* @returns Generated CSS class name
164
*/
165
(...args: CSSInterpolation[]): string;
166
}
167
168
type CSSInterpolation =
169
| CSSObject
170
| string
171
| number
172
| false
173
| null
174
| undefined
175
| CSSInterpolation[];
176
```
177
178
**Usage Examples:**
179
180
```typescript
181
import { css } from "my-css-instance";
182
183
// Template literal usage
184
const templateClass = css`
185
background-color: ${"red"};
186
color: white;
187
padding: ${16}px;
188
189
&:hover {
190
background-color: ${"darkred"};
191
}
192
193
@media (max-width: 768px) {
194
padding: ${8}px;
195
}
196
`;
197
198
// Function usage with CSS object
199
const objectClass = css({
200
backgroundColor: "blue",
201
color: "white",
202
padding: 16,
203
borderRadius: 4,
204
"&:hover": {
205
backgroundColor: "darkblue",
206
transform: "scale(1.02)"
207
},
208
"@media (max-width: 768px)": {
209
padding: 8,
210
fontSize: 14
211
}
212
});
213
214
// Function usage with multiple arguments
215
const multiArgClass = css(
216
{ backgroundColor: "green", color: "white" },
217
{ padding: 12, borderRadius: 8 },
218
{
219
"&:active": {
220
transform: "scale(0.98)"
221
}
222
}
223
);
224
225
// Dynamic CSS generation
226
function createButtonStyles(theme: any, variant: string) {
227
return css({
228
backgroundColor: theme.colors[variant],
229
color: theme.textColors[variant],
230
padding: theme.spacing.md,
231
borderRadius: theme.borderRadius,
232
border: "none",
233
cursor: "pointer",
234
fontSize: theme.fontSizes.md,
235
fontWeight: theme.fontWeights.semibold,
236
transition: "all 0.2s ease",
237
"&:hover": {
238
backgroundColor: theme.colors[`${variant}Hover`],
239
boxShadow: theme.shadows.md
240
},
241
"&:focus": {
242
outline: `2px solid ${theme.colors.focus}`,
243
outlineOffset: "2px"
244
},
245
"&:disabled": {
246
opacity: 0.5,
247
cursor: "not-allowed"
248
}
249
});
250
}
251
```
252
253
### CX Function Interface
254
255
Classname combination utility with conditional logic support.
256
257
```typescript { .api }
258
/**
259
* Combines multiple class names with conditional logic
260
* @param classNames - Array of class name arguments
261
* @returns Combined class name string
262
*/
263
type Cx = (...classNames: CxArg[]) => string;
264
265
type CxArg =
266
| string
267
| number
268
| boolean
269
| undefined
270
| null
271
| { [className: string]: boolean | undefined | null }
272
| CxArg[];
273
```
274
275
**Usage Examples:**
276
277
```typescript
278
import { cx } from "my-cx-instance";
279
280
// Basic class combination
281
const basicClasses = cx("btn", "btn-primary", "btn-large");
282
// Result: "btn btn-primary btn-large"
283
284
// Conditional classes with object syntax
285
const conditionalClasses = cx(
286
"btn",
287
{
288
"btn-primary": variant === "primary",
289
"btn-secondary": variant === "secondary",
290
"btn-disabled": disabled,
291
"btn-loading": loading
292
}
293
);
294
295
// Mixed arguments
296
const mixedClasses = cx(
297
"base-class",
298
dynamicClass,
299
{
300
"active": isActive,
301
"highlighted": isHighlighted
302
},
303
extraClass && "extra-class",
304
["array", "of", "classes"]
305
);
306
307
// Real-world component example
308
function Button({
309
variant = "primary",
310
size = "medium",
311
disabled = false,
312
loading = false,
313
className,
314
children,
315
...props
316
}: ButtonProps) {
317
const { css, cx } = useCssAndCx();
318
319
const baseStyles = css({
320
border: "none",
321
borderRadius: 4,
322
cursor: "pointer",
323
fontWeight: 500,
324
transition: "all 0.2s ease",
325
display: "inline-flex",
326
alignItems: "center",
327
justifyContent: "center"
328
});
329
330
const sizeStyles = css({
331
padding: {
332
small: "6px 12px",
333
medium: "8px 16px",
334
large: "12px 24px"
335
}[size],
336
fontSize: {
337
small: "14px",
338
medium: "16px",
339
large: "18px"
340
}[size]
341
});
342
343
const variantStyles = css(
344
variant === "primary"
345
? { backgroundColor: "#007bff", color: "white" }
346
: { backgroundColor: "#6c757d", color: "white" }
347
);
348
349
const buttonClass = cx(
350
baseStyles,
351
sizeStyles,
352
variantStyles,
353
{
354
[css({ opacity: 0.5, cursor: "not-allowed" })]: disabled,
355
[css({ position: "relative", "&::after": { content: '"..."' } })]: loading
356
},
357
className
358
);
359
360
return (
361
<button
362
className={buttonClass}
363
disabled={disabled || loading}
364
{...props}
365
>
366
{children}
367
</button>
368
);
369
}
370
```
371
372
### Class Merging Utility
373
374
Utility function for merging style classes with override support, particularly useful for component libraries.
375
376
```typescript { .api }
377
/**
378
* Merges classes from useStyles with override classes
379
* @param classesFromUseStyles - Classes generated by TSS-React hooks
380
* @param classesOverrides - Optional override classes from props
381
* @param cx - Class combination function
382
* @returns Merged classes object with overrides applied
383
*/
384
function mergeClasses<T extends string, U extends string>(
385
classesFromUseStyles: Record<T, string>,
386
classesOverrides: Partial<Record<U, string>> | undefined,
387
cx: Cx
388
): Record<T, string> & Partial<Record<Exclude<U, T>, string>>;
389
```
390
391
**Usage Examples:**
392
393
```typescript
394
import { mergeClasses } from "tss-react";
395
import { tss } from "tss-react";
396
397
// Component with style overrides support
398
interface CardProps {
399
title: string;
400
children: React.ReactNode;
401
classes?: {
402
root?: string;
403
title?: string;
404
content?: string;
405
footer?: string;
406
};
407
}
408
409
const useCardStyles = tss.create({
410
root: {
411
backgroundColor: "white",
412
borderRadius: 8,
413
padding: 16,
414
boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
415
},
416
title: {
417
fontSize: 18,
418
fontWeight: "bold",
419
marginBottom: 12,
420
color: "#333"
421
},
422
content: {
423
color: "#666",
424
lineHeight: 1.5
425
}
426
});
427
428
function Card({ title, children, classes: classesOverrides }: CardProps) {
429
const { classes: defaultClasses, cx } = useCardStyles();
430
431
// Merge default classes with overrides
432
const classes = mergeClasses(defaultClasses, classesOverrides, cx);
433
434
return (
435
<div className={classes.root}>
436
<h3 className={classes.title}>{title}</h3>
437
<div className={classes.content}>{children}</div>
438
</div>
439
);
440
}
441
442
// Usage with overrides
443
function App() {
444
return (
445
<Card
446
title="Custom Card"
447
classes={{
448
root: "custom-card-root",
449
title: "custom-card-title",
450
// Additional classes not in original component
451
footer: "custom-footer-class"
452
}}
453
>
454
<p>Card content here</p>
455
</Card>
456
);
457
}
458
459
// Component library pattern
460
const useLibraryStyles = tss
461
.withParams<{ variant: "outlined" | "filled"; size: "small" | "large" }>()
462
.create(({ theme }, { variant, size }) => ({
463
root: {
464
padding: size === "small" ? 8 : 16,
465
borderRadius: 4,
466
...(variant === "outlined" && {
467
border: "1px solid #ccc",
468
backgroundColor: "transparent"
469
}),
470
...(variant === "filled" && {
471
backgroundColor: "#f5f5f5",
472
border: "none"
473
})
474
},
475
label: {
476
fontSize: size === "small" ? 14 : 16,
477
fontWeight: 500
478
}
479
}));
480
481
interface LibraryComponentProps {
482
variant: "outlined" | "filled";
483
size: "small" | "large";
484
label: string;
485
classes?: {
486
root?: string;
487
label?: string;
488
icon?: string;
489
};
490
}
491
492
function LibraryComponent({
493
variant,
494
size,
495
label,
496
classes: classesOverrides
497
}: LibraryComponentProps) {
498
const { classes: defaultClasses, cx } = useLibraryStyles({ variant, size });
499
const classes = mergeClasses(defaultClasses, classesOverrides, cx);
500
501
return (
502
<div className={classes.root}>
503
<span className={classes.label}>{label}</span>
504
{classes.icon && <span className={classes.icon}>→</span>}
505
</div>
506
);
507
}
508
```
509
510
### Advanced Patterns
511
512
#### Custom Cache Strategies
513
514
```typescript
515
import createCache from "@emotion/cache";
516
import { createCssAndCx } from "tss-react/cssAndCx";
517
518
// Performance-optimized cache
519
const performanceCache = createCache({
520
key: "perf",
521
speedy: true, // No text content in style elements (faster)
522
prepend: true // Insert at beginning of head
523
});
524
525
// Development cache with debugging
526
const devCache = createCache({
527
key: "dev",
528
speedy: false, // Include text content for debugging
529
nonce: process.env.CSP_NONCE
530
});
531
532
const { css: perfCss, cx: perfCx } = createCssAndCx({
533
cache: process.env.NODE_ENV === "production" ? performanceCache : devCache
534
});
535
```
536
537
#### Dynamic Style Generation
538
539
```typescript
540
function createDynamicStyles(theme: any) {
541
const { css, cx } = createCssAndCx({ cache: theme.cache });
542
543
return {
544
// Generate styles based on theme configuration
545
generateButtonStyle: (variant: string, size: string) => css({
546
backgroundColor: theme.palette[variant].main,
547
color: theme.palette[variant].contrastText,
548
padding: theme.spacing[size],
549
borderRadius: theme.shape.borderRadius,
550
fontSize: theme.typography[size].fontSize,
551
"&:hover": {
552
backgroundColor: theme.palette[variant].dark
553
}
554
}),
555
556
// Combine multiple dynamic styles
557
combineStyles: (...styles: string[]) => cx(...styles),
558
559
// Generate responsive utilities
560
generateResponsiveStyle: (breakpoints: Record<string, any>) => css(
561
Object.entries(breakpoints).reduce((acc, [bp, styles]) => ({
562
...acc,
563
[`@media (min-width: ${theme.breakpoints[bp]})`]: styles
564
}), {})
565
)
566
};
567
}
568
```