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
```