or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions.mdcli-commands.mdframework-support.mdhighlighting.mdindex.mdmanager-api.mdstory-composition.mdtesting.mdtheming.mdviewport.md

highlighting.mddocs/

0

# Element Highlighting

1

2

Visual highlighting system for emphasizing specific DOM elements within stories, useful for documentation, interactive tutorials, and drawing attention to specific parts of the UI during presentations or testing.

3

4

## Capabilities

5

6

### Highlight Events

7

8

Event constants for controlling element highlighting behavior through Storybook's event system.

9

10

```typescript { .api }

11

/**

12

* Event identifiers for highlight functionality

13

*/

14

const HIGHLIGHT = 'storybook/highlight/add';

15

const REMOVE_HIGHLIGHT = 'storybook/highlight/remove';

16

const RESET_HIGHLIGHT = 'storybook/highlight/reset';

17

const SCROLL_INTO_VIEW = 'storybook/highlight/scroll-into-view';

18

```

19

20

**Usage Example:**

21

22

```typescript

23

import {

24

HIGHLIGHT,

25

REMOVE_HIGHLIGHT,

26

RESET_HIGHLIGHT

27

} from "storybook/highlight";

28

import { useChannel } from "storybook/preview-api";

29

30

export const HighlightableComponent: Story = {

31

render: () => {

32

const emit = useChannel({});

33

34

const highlightElement = () => {

35

emit(HIGHLIGHT, {

36

elements: ['.highlight-target'],

37

color: '#FF6B6B',

38

style: 'solid'

39

});

40

};

41

42

const removeHighlight = () => {

43

emit(REMOVE_HIGHLIGHT);

44

};

45

46

const resetHighlights = () => {

47

emit(RESET_HIGHLIGHT);

48

};

49

50

return (

51

<div>

52

<button onClick={highlightElement}>Highlight Element</button>

53

<button onClick={removeHighlight}>Remove Highlight</button>

54

<button onClick={resetHighlights}>Reset All</button>

55

56

<div className="highlight-target" style={{

57

padding: '20px',

58

margin: '20px',

59

border: '1px solid #ccc'

60

}}>

61

This element can be highlighted

62

</div>

63

</div>

64

);

65

},

66

};

67

```

68

69

## Highlight Configuration Types

70

71

### Highlight Options

72

73

Configuration interface for customizing highlight appearance and behavior.

74

75

```typescript { .api }

76

interface HighlightOptions {

77

/** Elements to highlight - CSS selectors or HTMLElement instances */

78

elements: string[] | HTMLElement[];

79

/** Highlight color (default: theme primary color) */

80

color?: string;

81

/** Border style for highlight outline */

82

style?: 'solid' | 'dashed' | 'dotted';

83

}

84

```

85

86

### Context Menu Integration

87

88

Configuration for highlight-related context menu items.

89

90

```typescript { .api }

91

interface HighlightMenuItem {

92

/** Display title for the menu item */

93

title: string;

94

/** Click handler for the menu item */

95

onClick: () => void;

96

}

97

```

98

99

### Click Event Details

100

101

Event data structure for highlight-related click interactions.

102

103

```typescript { .api }

104

interface ClickEventDetails {

105

/** The element that was clicked */

106

element: HTMLElement;

107

/** The actual event target (may be child of element) */

108

target: HTMLElement;

109

}

110

```

111

112

## Integration with Storybook Addons

113

114

### Using with Actions Addon

115

116

```typescript

117

import { action } from "storybook/actions";

118

import { HIGHLIGHT, REMOVE_HIGHLIGHT } from "storybook/highlight";

119

import { useChannel } from "storybook/preview-api";

120

121

export const InteractiveHighlighting: Story = {

122

render: () => {

123

const emit = useChannel({});

124

const logAction = action("highlight-action");

125

126

const highlightWithAction = (selector: string) => {

127

logAction(`Highlighting: ${selector}`);

128

emit(HIGHLIGHT, {

129

elements: [selector],

130

color: '#4ECDC4',

131

style: 'dashed'

132

});

133

};

134

135

return (

136

<div>

137

<button onClick={() => highlightWithAction('.card')}>

138

Highlight Card

139

</button>

140

<button onClick={() => highlightWithAction('.button')}>

141

Highlight Button

142

</button>

143

144

<div className="card" style={{

145

padding: '16px',

146

margin: '16px',

147

backgroundColor: '#f5f5f5',

148

borderRadius: '8px'

149

}}>

150

<h3>Sample Card</h3>

151

<button className="button">Sample Button</button>

152

</div>

153

</div>

154

);

155

},

156

};

157

```

158

159

### Sequential Highlighting

160

161

```typescript

162

import { HIGHLIGHT, SCROLL_INTO_VIEW } from "storybook/highlight";

163

import { useChannel } from "storybook/preview-api";

164

import { useState, useEffect } from "react";

165

166

export const TutorialHighlighting: Story = {

167

render: () => {

168

const emit = useChannel({});

169

const [currentStep, setCurrentStep] = useState(0);

170

171

const steps = [

172

{ selector: '.step-1', text: 'First, fill in your name' },

173

{ selector: '.step-2', text: 'Then, enter your email' },

174

{ selector: '.step-3', text: 'Finally, click submit' },

175

];

176

177

useEffect(() => {

178

if (currentStep < steps.length) {

179

const step = steps[currentStep];

180

181

// Scroll element into view first

182

emit(SCROLL_INTO_VIEW, { elements: [step.selector] });

183

184

// Then highlight it

185

setTimeout(() => {

186

emit(HIGHLIGHT, {

187

elements: [step.selector],

188

color: '#FF6B6B',

189

style: 'solid'

190

});

191

}, 500);

192

}

193

}, [currentStep]);

194

195

return (

196

<div>

197

<div style={{ marginBottom: '20px' }}>

198

<button

199

onClick={() => setCurrentStep(Math.max(0, currentStep - 1))}

200

disabled={currentStep === 0}

201

>

202

Previous

203

</button>

204

<span style={{ margin: '0 10px' }}>

205

Step {currentStep + 1} of {steps.length}

206

</span>

207

<button

208

onClick={() => setCurrentStep(Math.min(steps.length - 1, currentStep + 1))}

209

disabled={currentStep === steps.length - 1}

210

>

211

Next

212

</button>

213

</div>

214

215

{currentStep < steps.length && (

216

<p><strong>{steps[currentStep].text}</strong></p>

217

)}

218

219

<form style={{ maxWidth: '400px' }}>

220

<div className="step-1" style={{ marginBottom: '16px' }}>

221

<label htmlFor="name">Name:</label>

222

<input type="text" id="name" style={{ marginLeft: '8px' }} />

223

</div>

224

225

<div className="step-2" style={{ marginBottom: '16px' }}>

226

<label htmlFor="email">Email:</label>

227

<input type="email" id="email" style={{ marginLeft: '8px' }} />

228

</div>

229

230

<div className="step-3">

231

<button type="submit">Submit</button>

232

</div>

233

</form>

234

</div>

235

);

236

},

237

};

238

```

239

240

### Conditional Highlighting

241

242

```typescript

243

import { HIGHLIGHT, REMOVE_HIGHLIGHT } from "storybook/highlight";

244

import { useChannel, useArgs } from "storybook/preview-api";

245

import { useEffect } from "react";

246

247

export const ConditionalHighlighting: Story = {

248

args: {

249

showErrors: false,

250

highlightMode: 'none',

251

},

252

argTypes: {

253

showErrors: {

254

control: 'boolean',

255

description: 'Show validation errors'

256

},

257

highlightMode: {

258

control: 'select',

259

options: ['none', 'errors', 'required', 'all'],

260

description: 'Elements to highlight'

261

},

262

},

263

render: (args) => {

264

const emit = useChannel({});

265

266

useEffect(() => {

267

// Clear any existing highlights

268

emit(REMOVE_HIGHLIGHT);

269

270

// Apply highlights based on mode

271

switch (args.highlightMode) {

272

case 'errors':

273

if (args.showErrors) {

274

emit(HIGHLIGHT, {

275

elements: ['.error'],

276

color: '#EF4444',

277

style: 'solid'

278

});

279

}

280

break;

281

282

case 'required':

283

emit(HIGHLIGHT, {

284

elements: ['[required]'],

285

color: '#F59E0B',

286

style: 'dashed'

287

});

288

break;

289

290

case 'all':

291

emit(HIGHLIGHT, {

292

elements: ['input', 'button'],

293

color: '#10B981',

294

style: 'dotted'

295

});

296

break;

297

}

298

}, [args.showErrors, args.highlightMode]);

299

300

return (

301

<form style={{ maxWidth: '400px' }}>

302

<div style={{ marginBottom: '16px' }}>

303

<label htmlFor="name">Name (required):</label>

304

<input type="text" id="name" required />

305

{args.showErrors && (

306

<div className="error" style={{ color: '#EF4444', fontSize: '14px' }}>

307

Name is required

308

</div>

309

)}

310

</div>

311

312

<div style={{ marginBottom: '16px' }}>

313

<label htmlFor="email">Email (required):</label>

314

<input type="email" id="email" required />

315

{args.showErrors && (

316

<div className="error" style={{ color: '#EF4444', fontSize: '14px' }}>

317

Please enter a valid email

318

</div>

319

)}

320

</div>

321

322

<div style={{ marginBottom: '16px' }}>

323

<label htmlFor="phone">Phone (optional):</label>

324

<input type="tel" id="phone" />

325

</div>

326

327

<button type="submit">Submit</button>

328

</form>

329

);

330

},

331

};

332

```

333

334

## Best Practices

335

336

### Performance Considerations

337

338

- Use CSS selectors instead of HTMLElement references when possible for better performance

339

- Debounce rapid highlight changes to avoid excessive DOM manipulation

340

- Clean up highlights when components unmount or stories change

341

342

### Accessibility

343

344

- Ensure highlighted elements remain accessible to screen readers

345

- Don't rely solely on color for important information

346

- Provide alternative ways to identify highlighted elements

347

348

### Visual Design

349

350

- Choose highlight colors that work well with your component themes

351

- Use consistent highlight styles across related stories

352

- Consider animation duration and easing for smooth transitions

353

354

**Example of Accessible Highlighting:**

355

356

```typescript

357

export const AccessibleHighlighting: Story = {

358

render: () => {

359

const emit = useChannel({});

360

361

const highlightWithAnnouncement = (selector: string, description: string) => {

362

// Highlight the element

363

emit(HIGHLIGHT, {

364

elements: [selector],

365

color: '#3B82F6',

366

style: 'solid'

367

});

368

369

// Announce to screen readers

370

const announcement = document.createElement('div');

371

announcement.setAttribute('aria-live', 'polite');

372

announcement.setAttribute('aria-atomic', 'true');

373

announcement.style.position = 'absolute';

374

announcement.style.left = '-10000px';

375

announcement.textContent = `Highlighted: ${description}`;

376

document.body.appendChild(announcement);

377

378

setTimeout(() => {

379

document.body.removeChild(announcement);

380

}, 1000);

381

};

382

383

return (

384

<div>

385

<button

386

onClick={() => highlightWithAnnouncement('.important', 'Important section')}

387

>

388

Highlight Important Section

389

</button>

390

391

<div

392

className="important"

393

style={{

394

padding: '20px',

395

margin: '20px',

396

border: '1px solid #ccc'

397

}}

398

role="region"

399

aria-label="Important information"

400

>

401

This is an important section that can be highlighted

402

</div>

403

</div>

404

);

405

},

406

};

407

```