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

withstyles-hoc.mddocs/

0

# WithStyles HOC

1

2

The WithStyles Higher-Order Component (HOC) provides a pattern for injecting styles into React components. It supports both function and class components with full TypeScript integration and is compatible with Material-UI v4 withStyles patterns.

3

4

## Capabilities

5

6

### WithStyles Factory Function

7

8

Creates a withStyles HOC function with theme support and optional custom cache configuration.

9

10

```typescript { .api }

11

/**

12

* Creates a withStyles HOC function with theme support

13

* @param params - Configuration object with theme provider and optional cache

14

* @returns Object containing withStyles function

15

*/

16

function createWithStyles<Theme>(params: {

17

useTheme: () => Theme;

18

cache?: EmotionCache;

19

}): {

20

withStyles<

21

Component extends ReactComponent<any> | keyof ReactHTML,

22

Props extends ComponentProps<Component>,

23

CssObjectByRuleName extends Record<string, CSSObject>

24

>(

25

Component: Component,

26

cssObjectByRuleNameOrGetCssObjectByRuleName:

27

| CssObjectByRuleName

28

| ((theme: Theme, props: Props, classes: Record<string, string>) => CssObjectByRuleName)

29

): ComponentType<Props>;

30

};

31

32

type ReactComponent<P> = ComponentType<P>;

33

type ComponentProps<T> = T extends ComponentType<infer P> ? P : T extends keyof ReactHTML ? ReactHTML[T] extends ComponentType<infer P> ? P : never : never;

34

```

35

36

**Usage Examples:**

37

38

```typescript

39

import { useTheme } from "@mui/material/styles";

40

import { createWithStyles } from "tss-react";

41

42

// Create withStyles with MUI theme

43

const { withStyles } = createWithStyles({ useTheme });

44

45

// Custom cache configuration

46

import createCache from "@emotion/cache";

47

48

const customCache = createCache({

49

key: "my-styles",

50

prepend: true

51

});

52

53

const { withStyles: withStylesCustomCache } = createWithStyles({

54

useTheme,

55

cache: customCache

56

});

57

```

58

59

### WithStyles HOC Usage

60

61

Higher-order component that wraps components with style injection capabilities.

62

63

```typescript { .api }

64

/**

65

* Higher-order component for injecting styles into React components

66

* @param Component - React component to wrap (function, class, or HTML element)

67

* @param cssObjectByRuleNameOrGetCssObjectByRuleName - Static styles object or function

68

* @returns Enhanced component with injected classes prop

69

*/

70

function withStyles<

71

Component extends ReactComponent<any> | keyof ReactHTML,

72

Props extends ComponentProps<Component>,

73

CssObjectByRuleName extends Record<string, CSSObject>

74

>(

75

Component: Component,

76

cssObjectByRuleNameOrGetCssObjectByRuleName:

77

| CssObjectByRuleName

78

| ((

79

theme: Theme,

80

props: Props,

81

classes: Record<keyof CssObjectByRuleName, string>

82

) => CssObjectByRuleName)

83

): ComponentType<Props & { classes?: Partial<Record<keyof CssObjectByRuleName, string>> }>;

84

```

85

86

**Usage Examples:**

87

88

```typescript

89

import React from "react";

90

import { useTheme } from "@mui/material/styles";

91

import { createWithStyles } from "tss-react";

92

93

const { withStyles } = createWithStyles({ useTheme });

94

95

// Function component with static styles

96

const Button = withStyles(

97

({ children, classes, ...props }: {

98

children: React.ReactNode;

99

classes?: { root?: string; label?: string };

100

}) => (

101

<button className={classes?.root} {...props}>

102

<span className={classes?.label}>{children}</span>

103

</button>

104

),

105

{

106

root: {

107

backgroundColor: "blue",

108

color: "white",

109

border: "none",

110

borderRadius: 4,

111

padding: "8px 16px",

112

cursor: "pointer",

113

"&:hover": {

114

backgroundColor: "darkblue"

115

}

116

},

117

label: {

118

fontWeight: "bold"

119

}

120

}

121

);

122

123

// Function component with dynamic styles

124

interface CardProps {

125

title: string;

126

elevated: boolean;

127

classes?: { root?: string; title?: string; content?: string };

128

children: React.ReactNode;

129

}

130

131

const Card = withStyles(

132

({ title, children, classes, elevated, ...props }: CardProps) => (

133

<div className={classes?.root} {...props}>

134

<h3 className={classes?.title}>{title}</h3>

135

<div className={classes?.content}>{children}</div>

136

</div>

137

),

138

(theme, { elevated }) => ({

139

root: {

140

backgroundColor: theme.palette.background.paper,

141

borderRadius: theme.shape.borderRadius,

142

padding: theme.spacing(2),

143

boxShadow: elevated ? theme.shadows[4] : theme.shadows[1],

144

transition: theme.transitions.create("box-shadow")

145

},

146

title: {

147

margin: 0,

148

marginBottom: theme.spacing(1),

149

color: theme.palette.text.primary,

150

fontSize: theme.typography.h6.fontSize

151

},

152

content: {

153

color: theme.palette.text.secondary

154

}

155

})

156

);

157

158

// HTML element enhancement

159

const StyledDiv = withStyles(

160

"div",

161

theme => ({

162

root: {

163

backgroundColor: theme.palette.background.default,

164

padding: theme.spacing(3),

165

minHeight: "100vh"

166

}

167

})

168

);

169

170

// Usage

171

function App() {

172

return (

173

<StyledDiv>

174

<Card title="Welcome" elevated={true}>

175

<p>This is a styled card component.</p>

176

<Button>Click me</Button>

177

</Card>

178

</StyledDiv>

179

);

180

}

181

```

182

183

### Class Component Support

184

185

WithStyles works seamlessly with React class components:

186

187

```typescript

188

import React, { Component } from "react";

189

190

interface MyClassComponentProps {

191

title: string;

192

classes?: {

193

root?: string;

194

title?: string;

195

button?: string;

196

};

197

}

198

199

interface MyClassComponentState {

200

count: number;

201

}

202

203

class MyClassComponent extends Component<MyClassComponentProps, MyClassComponentState> {

204

state = { count: 0 };

205

206

handleClick = () => {

207

this.setState(prev => ({ count: prev.count + 1 }));

208

};

209

210

render() {

211

const { title, classes } = this.props;

212

const { count } = this.state;

213

214

return (

215

<div className={classes?.root}>

216

<h2 className={classes?.title}>{title}</h2>

217

<p>Count: {count}</p>

218

<button className={classes?.button} onClick={this.handleClick}>

219

Increment

220

</button>

221

</div>

222

);

223

}

224

}

225

226

const StyledClassComponent = withStyles(

227

MyClassComponent,

228

theme => ({

229

root: {

230

padding: theme.spacing(2),

231

backgroundColor: theme.palette.background.paper,

232

borderRadius: theme.shape.borderRadius

233

},

234

title: {

235

color: theme.palette.primary.main,

236

marginBottom: theme.spacing(1)

237

},

238

button: {

239

backgroundColor: theme.palette.secondary.main,

240

color: theme.palette.secondary.contrastText,

241

border: "none",

242

padding: theme.spacing(1, 2),

243

borderRadius: theme.shape.borderRadius,

244

cursor: "pointer"

245

}

246

})

247

);

248

```

249

250

### Style Overrides

251

252

Components wrapped with withStyles accept a `classes` prop for style customization:

253

254

```typescript

255

function CustomizedCard() {

256

return (

257

<Card

258

title="Custom Card"

259

elevated={false}

260

classes={{

261

root: "my-custom-root-class",

262

title: "my-custom-title-class"

263

}}

264

>

265

<p>This card has custom styling applied.</p>

266

</Card>

267

);

268

}

269

270

// CSS-in-JS style overrides

271

const useOverrideStyles = makeStyles()(theme => ({

272

customRoot: {

273

backgroundColor: theme.palette.warning.light,

274

border: `2px solid ${theme.palette.warning.main}`

275

},

276

customTitle: {

277

color: theme.palette.warning.contrastText,

278

textTransform: "uppercase"

279

}

280

}));

281

282

function CssInJsOverrides() {

283

const { classes } = useOverrideStyles();

284

285

return (

286

<Card

287

title="CSS-in-JS Overrides"

288

elevated={false}

289

classes={{

290

root: classes.customRoot,

291

title: classes.customTitle

292

}}

293

>

294

<p>Styled with CSS-in-JS overrides.</p>

295

</Card>

296

);

297

}

298

```

299

300

### Migration from Material-UI v4

301

302

The withStyles API provides seamless migration from @material-ui/core v4:

303

304

```typescript

305

// Before (Material-UI v4)

306

import { withStyles } from "@material-ui/core/styles";

307

308

const StyledComponent = withStyles(theme => ({

309

root: {

310

backgroundColor: theme.palette.background.paper,

311

padding: theme.spacing(2)

312

}

313

}))(({ classes }) => (

314

<div className={classes.root}>Content</div>

315

));

316

317

// After (TSS-React)

318

import { createWithStyles } from "tss-react";

319

import { useTheme } from "@mui/material/styles";

320

321

const { withStyles } = createWithStyles({ useTheme });

322

323

const StyledComponent = withStyles(

324

({ classes }: { classes?: { root?: string } }) => (

325

<div className={classes?.root}>Content</div>

326

),

327

theme => ({

328

root: {

329

backgroundColor: theme.palette.background.paper,

330

padding: theme.spacing(2)

331

}

332

})

333

);

334

```

335

336

### Advanced Patterns

337

338

#### Nested Selectors with Classes Reference

339

340

```typescript

341

const NestedComponent = withStyles(

342

({ classes }: { classes?: { root?: string; item?: string; selected?: string } }) => (

343

<div className={classes?.root}>

344

<div className={classes?.item}>Item 1</div>

345

<div className={`${classes?.item} ${classes?.selected}`}>Item 2 (Selected)</div>

346

</div>

347

),

348

(theme, props, classes) => ({

349

root: {

350

padding: theme.spacing(2),

351

[`& .${classes.item}`]: {

352

padding: theme.spacing(1),

353

borderBottom: `1px solid ${theme.palette.divider}`,

354

"&:last-child": {

355

borderBottom: "none"

356

}

357

},

358

[`& .${classes.selected}`]: {

359

backgroundColor: theme.palette.action.selected,

360

fontWeight: theme.typography.fontWeightBold

361

}

362

},

363

item: {},

364

selected: {}

365

})

366

);

367

```

368

369

#### Conditional Styling with Props

370

371

```typescript

372

interface AlertProps {

373

severity: "info" | "warning" | "error" | "success";

374

message: string;

375

classes?: { root?: string; icon?: string; message?: string };

376

}

377

378

const Alert = withStyles(

379

({ severity, message, classes }: AlertProps) => (

380

<div className={classes?.root}>

381

<span className={classes?.icon}>⚠️</span>

382

<span className={classes?.message}>{message}</span>

383

</div>

384

),

385

(theme, { severity }) => {

386

const colors = {

387

info: theme.palette.info,

388

warning: theme.palette.warning,

389

error: theme.palette.error,

390

success: theme.palette.success

391

};

392

393

const color = colors[severity];

394

395

return {

396

root: {

397

display: "flex",

398

alignItems: "center",

399

padding: theme.spacing(1, 2),

400

backgroundColor: color.light,

401

color: color.contrastText,

402

borderRadius: theme.shape.borderRadius,

403

border: `1px solid ${color.main}`

404

},

405

icon: {

406

marginRight: theme.spacing(1),

407

fontSize: "1.2em"

408

},

409

message: {

410

flex: 1

411

}

412

};

413

}

414

);

415

```

416

417

### TypeScript Integration

418

419

WithStyles provides full TypeScript support with proper prop inference:

420

421

```typescript

422

// Component with strict typing

423

interface StrictButtonProps {

424

variant: "primary" | "secondary";

425

size: "small" | "medium" | "large";

426

disabled?: boolean;

427

children: React.ReactNode;

428

onClick?: () => void;

429

classes?: {

430

root?: string;

431

label?: string;

432

};

433

}

434

435

const StrictButton = withStyles(

436

({ variant, size, disabled, children, classes, onClick }: StrictButtonProps) => (

437

<button

438

className={classes?.root}

439

disabled={disabled}

440

onClick={onClick}

441

>

442

<span className={classes?.label}>{children}</span>

443

</button>

444

),

445

(theme, { variant, size, disabled }) => ({

446

root: {

447

backgroundColor: variant === "primary" ? theme.palette.primary.main : theme.palette.secondary.main,

448

color: variant === "primary" ? theme.palette.primary.contrastText : theme.palette.secondary.contrastText,

449

padding: {

450

small: theme.spacing(0.5, 1),

451

medium: theme.spacing(1, 2),

452

large: theme.spacing(1.5, 3)

453

}[size],

454

fontSize: {

455

small: theme.typography.body2.fontSize,

456

medium: theme.typography.body1.fontSize,

457

large: theme.typography.h6.fontSize

458

}[size],

459

opacity: disabled ? 0.5 : 1,

460

cursor: disabled ? "not-allowed" : "pointer",

461

border: "none",

462

borderRadius: theme.shape.borderRadius,

463

transition: theme.transitions.create(["background-color", "opacity"])

464

},

465

label: {

466

fontWeight: theme.typography.fontWeightMedium

467

}

468

})

469

);

470

471

// Usage with full type checking

472

function TypedExample() {

473

return (

474

<StrictButton

475

variant="primary"

476

size="medium"

477

onClick={() => console.log("Clicked!")}

478

>

479

Click me

480

</StrictButton>

481

);

482

}

483

```

484

485

### GetClasses Utility

486

487

The withStyles HOC includes a `getClasses` utility function for accessing generated class names within component render functions. This is particularly useful for programmatic access to styles.

488

489

```typescript { .api }

490

/**

491

* Utility function attached to withStyles for accessing class names

492

* @param props - Component props containing className and classes

493

* @returns Generated class names object

494

*/

495

withStyles.getClasses = function getClasses<Classes>(props: {

496

className?: string;

497

classes?: Classes;

498

}): Classes extends Record<string, unknown>

499

? Classes extends Partial<Record<infer K, any>>

500

? Record<K, string>

501

: Classes

502

: { root: string };

503

```

504

505

**Usage Examples:**

506

507

```typescript

508

import { createWithStyles } from "tss-react";

509

import { useTheme } from "@mui/material/styles";

510

511

const { withStyles } = createWithStyles({ useTheme });

512

513

// Component that uses getClasses utility

514

interface CardProps {

515

title: string;

516

content: string;

517

className?: string;

518

classes?: {

519

root?: string;

520

header?: string;

521

title?: string;

522

content?: string;

523

footer?: string;

524

};

525

}

526

527

const Card = withStyles(

528

(props: CardProps) => {

529

// Access classes programmatically

530

const classes = withStyles.getClasses(props);

531

532

return (

533

<div className={classes.root}>

534

<header className={classes.header}>

535

<h2 className={classes.title}>{props.title}</h2>

536

</header>

537

<div className={classes.content}>

538

{props.content}

539

</div>

540

<footer className={classes.footer}>

541

<button>Action</button>

542

</footer>

543

</div>

544

);

545

},

546

theme => ({

547

root: {

548

backgroundColor: theme.palette.background.paper,

549

borderRadius: theme.shape.borderRadius,

550

boxShadow: theme.shadows[2],

551

overflow: "hidden"

552

},

553

header: {

554

backgroundColor: theme.palette.primary.main,

555

color: theme.palette.primary.contrastText,

556

padding: theme.spacing(2)

557

},

558

title: {

559

margin: 0,

560

fontSize: theme.typography.h5.fontSize,

561

fontWeight: theme.typography.fontWeightMedium

562

},

563

content: {

564

padding: theme.spacing(2),

565

color: theme.palette.text.primary

566

},

567

footer: {

568

padding: theme.spacing(1, 2),

569

borderTop: `1px solid ${theme.palette.divider}`,

570

backgroundColor: theme.palette.background.default

571

}

572

})

573

);

574

575

// Usage

576

function App() {

577

return (

578

<Card

579

title="My Card"

580

content="This is the card content"

581

classes={{

582

root: "custom-card-root",

583

title: "custom-card-title"

584

}}

585

/>

586

);

587

}

588

```

589

590

**Important Notes:**

591

592

- `getClasses` should only be used within components wrapped by withStyles

593

- The function expects props to contain a `classes` object provided by withStyles

594

- It returns the actual generated CSS class names for programmatic use

595

- Useful for complex conditional styling logic that needs access to class names

596

- The returned classes object maintains type safety based on the component's classes interface

597

598

**Error Handling:**

599

600

```typescript

601

// getClasses will throw an error if used incorrectly

602

try {

603

const classes = withStyles.getClasses({ classes: undefined });

604

} catch (error) {

605

console.error("getClasses should only be used in conjunction with withStyles");

606

}

607

```