or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compatibility.mdcore-tss-api.mdcss-utilities.mddsfr-integration.mdglobal-styles-keyframes.mdindex.mdmakestyles-api.mdmui-integration.mdnextjs-ssr.mdwithstyles-hoc.md

global-styles-keyframes.mddocs/

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

```