0
# Global Styles & Keyframes
1
2
TSS-React provides utilities for global CSS injection and animation keyframe definitions, built on Emotion's proven CSS-in-JS infrastructure. These tools enable application-wide styling and complex animations.
3
4
## Capabilities
5
6
### Global Styles Component
7
8
React component for injecting global CSS styles into the document head.
9
10
```typescript { .api }
11
/**
12
* React component for injecting global CSS styles
13
* @param props - Props containing the global styles definition
14
* @returns JSX element that injects global styles
15
*/
16
function GlobalStyles(props: { styles: CSSInterpolation }): JSX.Element;
17
18
type CSSInterpolation =
19
| CSSObject
20
| string
21
| number
22
| false
23
| null
24
| undefined
25
| CSSInterpolation[];
26
27
interface CSSObject {
28
[property: string]: CSSInterpolation;
29
label?: string;
30
}
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
import { GlobalStyles } from "tss-react";
37
38
// Basic global styles
39
function App() {
40
return (
41
<>
42
<GlobalStyles
43
styles={{
44
body: {
45
margin: 0,
46
padding: 0,
47
fontFamily: '"Helvetica Neue", Arial, sans-serif',
48
backgroundColor: "#f5f5f5",
49
color: "#333"
50
},
51
"*": {
52
boxSizing: "border-box"
53
},
54
"*, *::before, *::after": {
55
boxSizing: "border-box"
56
}
57
}}
58
/>
59
<MyAppContent />
60
</>
61
);
62
}
63
64
// Theme-based global styles
65
function ThemedApp({ theme }: { theme: any }) {
66
return (
67
<>
68
<GlobalStyles
69
styles={{
70
body: {
71
backgroundColor: theme.palette.background.default,
72
color: theme.palette.text.primary,
73
fontFamily: theme.typography.fontFamily,
74
fontSize: theme.typography.body1.fontSize,
75
lineHeight: theme.typography.body1.lineHeight
76
},
77
a: {
78
color: theme.palette.primary.main,
79
textDecoration: "none",
80
"&:hover": {
81
textDecoration: "underline"
82
}
83
},
84
"h1, h2, h3, h4, h5, h6": {
85
fontWeight: theme.typography.fontWeightBold,
86
margin: "0 0 16px 0"
87
}
88
}}
89
/>
90
<AppContent />
91
</>
92
);
93
}
94
95
// CSS Reset with GlobalStyles
96
function CSSReset() {
97
return (
98
<GlobalStyles
99
styles={{
100
// Modern CSS reset
101
"*, *::before, *::after": {
102
boxSizing: "border-box"
103
},
104
"*": {
105
margin: 0
106
},
107
"html, body": {
108
height: "100%"
109
},
110
body: {
111
lineHeight: 1.5,
112
"-webkit-font-smoothing": "antialiased"
113
},
114
"img, picture, video, canvas, svg": {
115
display: "block",
116
maxWidth: "100%"
117
},
118
"input, button, textarea, select": {
119
font: "inherit"
120
},
121
"p, h1, h2, h3, h4, h5, h6": {
122
overflowWrap: "break-word"
123
},
124
"#root, #__next": {
125
isolation: "isolate"
126
}
127
}}
128
/>
129
);
130
}
131
132
// Multiple GlobalStyles components
133
function MultiGlobalStyles() {
134
return (
135
<>
136
{/* Base reset */}
137
<GlobalStyles
138
styles={{
139
"*": { boxSizing: "border-box" },
140
body: { margin: 0, fontFamily: "system-ui" }
141
}}
142
/>
143
144
{/* Custom scrollbar */}
145
<GlobalStyles
146
styles={{
147
"::-webkit-scrollbar": {
148
width: 8
149
},
150
"::-webkit-scrollbar-track": {
151
backgroundColor: "#f1f1f1"
152
},
153
"::-webkit-scrollbar-thumb": {
154
backgroundColor: "#888",
155
borderRadius: 4,
156
"&:hover": {
157
backgroundColor: "#555"
158
}
159
}
160
}}
161
/>
162
163
{/* Print styles */}
164
<GlobalStyles
165
styles={{
166
"@media print": {
167
"*": {
168
color: "black !important",
169
backgroundColor: "white !important"
170
},
171
".no-print": {
172
display: "none !important"
173
}
174
}
175
}}
176
/>
177
178
<AppContent />
179
</>
180
);
181
}
182
```
183
184
### Keyframes Function
185
186
Function for defining CSS animation keyframes, re-exported from @emotion/react.
187
188
```typescript { .api }
189
/**
190
* Creates CSS animation keyframes (template literal version)
191
* @param template - Template strings array containing keyframe definitions
192
* @param args - Interpolated values for keyframes
193
* @returns Animation name string for use in CSS animations
194
*/
195
function keyframes(
196
template: TemplateStringsArray,
197
...args: CSSInterpolation[]
198
): string;
199
200
/**
201
* Creates CSS animation keyframes (function version)
202
* @param args - CSS interpolation values containing keyframe definitions
203
* @returns Animation name string for use in CSS animations
204
*/
205
function keyframes(...args: CSSInterpolation[]): string;
206
```
207
208
**Usage Examples:**
209
210
```typescript
211
import { keyframes, tss } from "tss-react";
212
213
// Template literal keyframes
214
const fadeIn = keyframes`
215
from {
216
opacity: 0;
217
transform: translateY(10px);
218
}
219
to {
220
opacity: 1;
221
transform: translateY(0);
222
}
223
`;
224
225
const slideIn = keyframes`
226
0% {
227
transform: translateX(-100%);
228
opacity: 0;
229
}
230
50% {
231
opacity: 0.5;
232
}
233
100% {
234
transform: translateX(0);
235
opacity: 1;
236
}
237
`;
238
239
// Object-based keyframes
240
const bounce = keyframes({
241
"0%, 20%, 53%, 80%, 100%": {
242
transform: "translate3d(0, 0, 0)"
243
},
244
"40%, 43%": {
245
transform: "translate3d(0, -30px, 0)"
246
},
247
"70%": {
248
transform: "translate3d(0, -15px, 0)"
249
},
250
"90%": {
251
transform: "translate3d(0, -4px, 0)"
252
}
253
});
254
255
const pulse = keyframes({
256
"0%": {
257
transform: "scale(1)",
258
opacity: 1
259
},
260
"50%": {
261
transform: "scale(1.05)",
262
opacity: 0.8
263
},
264
"100%": {
265
transform: "scale(1)",
266
opacity: 1
267
}
268
});
269
270
// Using keyframes in TSS styles
271
const useAnimatedStyles = tss
272
.withParams<{
273
isVisible: boolean;
274
animationType: "fade" | "slide" | "bounce";
275
}>()
276
.create(({}, { isVisible, animationType }) => {
277
const animations = {
278
fade: fadeIn,
279
slide: slideIn,
280
bounce: bounce
281
};
282
283
return {
284
root: {
285
animation: isVisible
286
? `${animations[animationType]} 0.5s ease-out forwards`
287
: "none",
288
opacity: isVisible ? 1 : 0
289
},
290
pulsingButton: {
291
animation: `${pulse} 2s infinite`,
292
cursor: "pointer",
293
border: "none",
294
borderRadius: 4,
295
padding: "12px 24px",
296
backgroundColor: "#007bff",
297
color: "white",
298
fontSize: 16
299
}
300
};
301
});
302
303
function AnimatedComponent({
304
isVisible,
305
animationType
306
}: {
307
isVisible: boolean;
308
animationType: "fade" | "slide" | "bounce";
309
}) {
310
const { classes } = useAnimatedStyles({ isVisible, animationType });
311
312
return (
313
<div className={classes.root}>
314
<h2>Animated Content</h2>
315
<p>This content animates based on the selected animation type.</p>
316
<button className={classes.pulsingButton}>
317
Pulsing Button
318
</button>
319
</div>
320
);
321
}
322
```
323
324
### Complex Animation Patterns
325
326
#### Loading Animations
327
328
```typescript
329
import { keyframes, tss } from "tss-react";
330
331
// Spinner keyframes
332
const spin = keyframes({
333
"0%": { transform: "rotate(0deg)" },
334
"100%": { transform: "rotate(360deg)" }
335
});
336
337
const dots = keyframes({
338
"0%, 80%, 100%": {
339
transform: "scale(0)",
340
opacity: 0.5
341
},
342
"40%": {
343
transform: "scale(1)",
344
opacity: 1
345
}
346
});
347
348
const shimmer = keyframes({
349
"0%": {
350
backgroundPosition: "-200px 0"
351
},
352
"100%": {
353
backgroundPosition: "calc(200px + 100%) 0"
354
}
355
});
356
357
const useLoadingStyles = tss.create({
358
spinner: {
359
width: 40,
360
height: 40,
361
border: "4px solid #f3f3f3",
362
borderTop: "4px solid #3498db",
363
borderRadius: "50%",
364
animation: `${spin} 1s linear infinite`
365
},
366
367
dotsLoader: {
368
display: "inline-block",
369
position: "relative",
370
width: 64,
371
height: 64,
372
"& div": {
373
position: "absolute",
374
top: 27,
375
width: 11,
376
height: 11,
377
borderRadius: "50%",
378
backgroundColor: "#3498db",
379
animationTimingFunction: "cubic-bezier(0, 1, 1, 0)"
380
},
381
"& div:nth-child(1)": {
382
left: 6,
383
animation: `${dots} 0.6s infinite`
384
},
385
"& div:nth-child(2)": {
386
left: 6,
387
animation: `${dots} 0.6s infinite`,
388
animationDelay: "-0.2s"
389
},
390
"& div:nth-child(3)": {
391
left: 26,
392
animation: `${dots} 0.6s infinite`,
393
animationDelay: "-0.4s"
394
}
395
},
396
397
shimmerCard: {
398
background: "#f6f7f8",
399
backgroundImage: `linear-gradient(
400
90deg,
401
#f6f7f8 0px,
402
rgba(255, 255, 255, 0.8) 40px,
403
#f6f7f8 80px
404
)`,
405
backgroundSize: "200px 100%",
406
backgroundRepeat: "no-repeat",
407
borderRadius: 4,
408
display: "inline-block",
409
lineHeight: 1,
410
width: "100%",
411
animation: `${shimmer} 1.2s ease-in-out infinite`
412
}
413
});
414
415
function LoadingComponents() {
416
const { classes } = useLoadingStyles();
417
418
return (
419
<div>
420
<div className={classes.spinner} />
421
<div className={classes.dotsLoader}>
422
<div></div>
423
<div></div>
424
<div></div>
425
</div>
426
<div className={classes.shimmerCard} style={{ height: 200 }} />
427
</div>
428
);
429
}
430
```
431
432
#### Interactive Animations
433
434
```typescript
435
import { keyframes, tss } from "tss-react";
436
437
// Interactive animation keyframes
438
const wiggle = keyframes({
439
"0%, 7%": { transform: "rotateZ(0)" },
440
"15%": { transform: "rotateZ(-15deg)" },
441
"20%": { transform: "rotateZ(10deg)" },
442
"25%": { transform: "rotateZ(-10deg)" },
443
"30%": { transform: "rotateZ(6deg)" },
444
"35%": { transform: "rotateZ(-4deg)" },
445
"40%, 100%": { transform: "rotateZ(0)" }
446
});
447
448
const heartbeat = keyframes({
449
"0%": { transform: "scale(1)" },
450
"14%": { transform: "scale(1.3)" },
451
"28%": { transform: "scale(1)" },
452
"42%": { transform: "scale(1.3)" },
453
"70%": { transform: "scale(1)" }
454
});
455
456
const rubber = keyframes({
457
"0%": { transform: "scale3d(1, 1, 1)" },
458
"30%": { transform: "scale3d(1.25, 0.75, 1)" },
459
"40%": { transform: "scale3d(0.75, 1.25, 1)" },
460
"50%": { transform: "scale3d(1.15, 0.85, 1)" },
461
"65%": { transform: "scale3d(0.95, 1.05, 1)" },
462
"75%": { transform: "scale3d(1.05, 0.95, 1)" },
463
"100%": { transform: "scale3d(1, 1, 1)" }
464
});
465
466
const useInteractiveStyles = tss
467
.withParams<{
468
isHovered: boolean;
469
isClicked: boolean;
470
animationStyle: "wiggle" | "heartbeat" | "rubber";
471
}>()
472
.create(({}, { isHovered, isClicked, animationStyle }) => {
473
const animations = {
474
wiggle: wiggle,
475
heartbeat: heartbeat,
476
rubber: rubber
477
};
478
479
const durations = {
480
wiggle: "0.5s",
481
heartbeat: "1.2s",
482
rubber: "1s"
483
};
484
485
return {
486
interactiveButton: {
487
padding: "12px 24px",
488
backgroundColor: "#28a745",
489
color: "white",
490
border: "none",
491
borderRadius: 8,
492
cursor: "pointer",
493
fontSize: 16,
494
fontWeight: 600,
495
transition: "all 0.2s ease",
496
transform: isClicked ? "scale(0.95)" : "scale(1)",
497
animation: isHovered
498
? `${animations[animationStyle]} ${durations[animationStyle]} ease-in-out`
499
: "none",
500
"&:hover": {
501
backgroundColor: "#218838",
502
boxShadow: "0 4px 8px rgba(0,0,0,0.2)"
503
}
504
}
505
};
506
});
507
508
function InteractiveButton({
509
animationStyle = "wiggle",
510
children,
511
onClick
512
}: {
513
animationStyle?: "wiggle" | "heartbeat" | "rubber";
514
children: React.ReactNode;
515
onClick?: () => void;
516
}) {
517
const [isHovered, setIsHovered] = useState(false);
518
const [isClicked, setIsClicked] = useState(false);
519
520
const { classes } = useInteractiveStyles({
521
isHovered,
522
isClicked,
523
animationStyle
524
});
525
526
const handleClick = () => {
527
setIsClicked(true);
528
setTimeout(() => setIsClicked(false), 150);
529
onClick?.();
530
};
531
532
return (
533
<button
534
className={classes.interactiveButton}
535
onMouseEnter={() => setIsHovered(true)}
536
onMouseLeave={() => setIsHovered(false)}
537
onClick={handleClick}
538
>
539
{children}
540
</button>
541
);
542
}
543
```
544
545
### Advanced Global Styling Patterns
546
547
#### CSS Custom Properties (CSS Variables)
548
549
```typescript
550
import { GlobalStyles } from "tss-react";
551
552
function CSSVariablesSetup({ theme }: { theme: any }) {
553
return (
554
<GlobalStyles
555
styles={{
556
":root": {
557
// Color palette
558
"--color-primary": theme.palette.primary.main,
559
"--color-primary-dark": theme.palette.primary.dark,
560
"--color-primary-light": theme.palette.primary.light,
561
"--color-secondary": theme.palette.secondary.main,
562
"--color-error": theme.palette.error.main,
563
"--color-warning": theme.palette.warning.main,
564
"--color-success": theme.palette.success.main,
565
566
// Spacing scale
567
"--spacing-xs": theme.spacing(0.5),
568
"--spacing-sm": theme.spacing(1),
569
"--spacing-md": theme.spacing(2),
570
"--spacing-lg": theme.spacing(3),
571
"--spacing-xl": theme.spacing(4),
572
573
// Typography
574
"--font-family": theme.typography.fontFamily,
575
"--font-size-sm": theme.typography.body2.fontSize,
576
"--font-size-md": theme.typography.body1.fontSize,
577
"--font-size-lg": theme.typography.h6.fontSize,
578
579
// Shadows and elevation
580
"--shadow-sm": theme.shadows[1],
581
"--shadow-md": theme.shadows[4],
582
"--shadow-lg": theme.shadows[8],
583
584
// Border radius
585
"--border-radius": theme.shape.borderRadius,
586
"--border-radius-lg": theme.shape.borderRadius * 2,
587
588
// Transitions
589
"--transition-fast": "0.15s ease",
590
"--transition-normal": "0.3s ease",
591
"--transition-slow": "0.5s ease"
592
},
593
594
// Dark mode variables
595
"[data-theme='dark']": {
596
"--color-background": "#1a1a1a",
597
"--color-surface": "#2d2d2d",
598
"--color-text": "#ffffff",
599
"--color-text-secondary": "#b3b3b3"
600
},
601
602
// Light mode variables
603
"[data-theme='light']": {
604
"--color-background": "#ffffff",
605
"--color-surface": "#f5f5f5",
606
"--color-text": "#333333",
607
"--color-text-secondary": "#666666"
608
}
609
}}
610
/>
611
);
612
}
613
614
// Using CSS variables in TSS styles
615
const useCSSVariableStyles = tss.create({
616
card: {
617
backgroundColor: "var(--color-surface)",
618
color: "var(--color-text)",
619
padding: "var(--spacing-lg)",
620
borderRadius: "var(--border-radius-lg)",
621
boxShadow: "var(--shadow-md)",
622
transition: "var(--transition-normal)",
623
"&:hover": {
624
boxShadow: "var(--shadow-lg)",
625
transform: "translateY(-2px)"
626
}
627
},
628
629
button: {
630
backgroundColor: "var(--color-primary)",
631
color: "white",
632
border: "none",
633
padding: "var(--spacing-sm) var(--spacing-md)",
634
borderRadius: "var(--border-radius)",
635
cursor: "pointer",
636
fontSize: "var(--font-size-md)",
637
transition: "var(--transition-fast)",
638
"&:hover": {
639
backgroundColor: "var(--color-primary-dark)"
640
}
641
}
642
});
643
```
644
645
#### Responsive Global Styles
646
647
```typescript
648
import { GlobalStyles } from "tss-react";
649
650
function ResponsiveGlobalStyles({ theme }: { theme: any }) {
651
return (
652
<GlobalStyles
653
styles={{
654
// Base typography scale
655
html: {
656
fontSize: 14,
657
[theme.breakpoints.up("sm")]: {
658
fontSize: 16
659
},
660
[theme.breakpoints.up("lg")]: {
661
fontSize: 18
662
}
663
},
664
665
// Container widths
666
".container": {
667
width: "100%",
668
maxWidth: "100%",
669
paddingLeft: theme.spacing(2),
670
paddingRight: theme.spacing(2),
671
marginLeft: "auto",
672
marginRight: "auto",
673
[theme.breakpoints.up("sm")]: {
674
maxWidth: 540,
675
paddingLeft: theme.spacing(3),
676
paddingRight: theme.spacing(3)
677
},
678
[theme.breakpoints.up("md")]: {
679
maxWidth: 720
680
},
681
[theme.breakpoints.up("lg")]: {
682
maxWidth: 960
683
},
684
[theme.breakpoints.up("xl")]: {
685
maxWidth: 1140
686
}
687
},
688
689
// Responsive grid system
690
".grid": {
691
display: "grid",
692
gap: theme.spacing(2),
693
gridTemplateColumns: "1fr",
694
[theme.breakpoints.up("sm")]: {
695
gridTemplateColumns: "repeat(2, 1fr)"
696
},
697
[theme.breakpoints.up("md")]: {
698
gridTemplateColumns: "repeat(3, 1fr)",
699
gap: theme.spacing(3)
700
},
701
[theme.breakpoints.up("lg")]: {
702
gridTemplateColumns: "repeat(4, 1fr)"
703
}
704
},
705
706
// Responsive utilities
707
".hide-mobile": {
708
display: "none",
709
[theme.breakpoints.up("md")]: {
710
display: "block"
711
}
712
},
713
714
".hide-desktop": {
715
display: "block",
716
[theme.breakpoints.up("md")]: {
717
display: "none"
718
}
719
}
720
}}
721
/>
722
);
723
}
724
```