Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 9eac3e6

Browse files
committed
Add highlight button and bump @accesslint/vitest to 0.8.4
- Add Highlight toggle in violation details to outline the failing element in the Storybook preview using built-in highlight events - Define HIGHLIGHT/REMOVE_HIGHLIGHT constants for the manager context - Strip iframe-piercing selector prefix before highlighting - Bump @accesslint/vitest to ^0.1.4 (fixes selector scoping in toBeAccessible matcher)
1 parent 6f9af44 commit 9eac3e6

5 files changed

Lines changed: 65 additions & 13 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export default config;
2626

2727
Restart Storybook and an **AccessLint** panel will appear in the addon bar. Every story is audited automatically after it renders.
2828

29+
Expand any violation to see the failing element, WCAG criteria, and remediation guidance. Click **Highlight** to outline the element in the preview.
30+
2931
## Vitest integration
3032

3133
If you use [`@storybook/addon-vitest`](https://storybook.js.org/docs/writing-tests/vitest-plugin), add the AccessLint plugin next to `storybookTest()` in your Vite config:

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@accesslint/storybook-addon",
3-
"version": "0.8.3",
3+
"version": "0.8.4",
44
"description": "Catch accessibility violations in your Storybook stories as you develop",
55
"license": "MIT",
66
"publishConfig": {
@@ -59,7 +59,7 @@
5959
},
6060
"dependencies": {
6161
"@accesslint/core": "^0.6.5",
62-
"@accesslint/vitest": "^0.1.3"
62+
"@accesslint/vitest": "^0.1.4"
6363
},
6464
"devDependencies": {
6565
"react": "^18.2.0",

src/Panel.tsx

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState, useMemo, useRef, useCallback, type FC } from "react";
22
import { useChannel } from "storybook/internal/manager-api";
33
import { useTheme } from "storybook/internal/theming";
44
import { STORY_CHANGED } from "storybook/internal/core-events";
5-
import { RESULT_EVENT } from "./constants";
5+
import { RESULT_EVENT, HIGHLIGHT, REMOVE_HIGHLIGHT } from "./constants";
66

77
interface EnrichedViolation {
88
ruleId: string;
@@ -70,27 +70,31 @@ export const Panel: FC<PanelProps> = ({ active }) => {
7070
const [ruleCount, setRuleCount] = useState(0);
7171
const [skippedReason, setSkippedReason] = useState<string | null>(null);
7272
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
73+
const [highlightedIndex, setHighlightedIndex] = useState<number | null>(null);
7374
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
7475

75-
useChannel({
76+
const emit = useChannel({
7677
[RESULT_EVENT]: ({ result }: { storyId?: string; result: { violations?: EnrichedViolation[]; ruleCount?: number; skipped?: boolean; reason?: string }; status?: string }) => {
7778
if (result.skipped) {
7879
setViolations([]);
7980
setRuleCount(0);
8081
setSkippedReason(result.reason ?? "skipped");
8182
setExpandedIndex(null);
83+
setHighlightedIndex(null);
8284
return;
8385
}
8486
setViolations(result.violations ?? []);
8587
setRuleCount(result.ruleCount ?? 0);
8688
setSkippedReason(null);
8789
setExpandedIndex(null);
90+
setHighlightedIndex(null);
8891
},
8992
[STORY_CHANGED]: () => {
9093
setViolations([]);
9194
setRuleCount(0);
9295
setSkippedReason(null);
9396
setExpandedIndex(null);
97+
setHighlightedIndex(null);
9498
},
9599
});
96100

@@ -163,7 +167,13 @@ export const Panel: FC<PanelProps> = ({ active }) => {
163167
<button
164168
ref={(el) => { buttonRefs.current[i] = el; }}
165169
type="button"
166-
onClick={() => setExpandedIndex(isOpen ? null : i)}
170+
onClick={() => {
171+
setExpandedIndex(isOpen ? null : i);
172+
if (highlightedIndex !== null) {
173+
emit(REMOVE_HIGHLIGHT);
174+
setHighlightedIndex(null);
175+
}
176+
}}
167177
onKeyDown={(e) => handleKeyDown(e, i)}
168178
aria-expanded={isOpen}
169179
style={{
@@ -252,8 +262,42 @@ export const Panel: FC<PanelProps> = ({ active }) => {
252262
)}
253263
{v.html && (
254264
<div style={{ marginBottom: "8px" }}>
255-
<div style={{ fontSize: "11px", fontWeight: 500, color: colors.textMuted, marginBottom: "4px" }}>
256-
Element
265+
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "4px" }}>
266+
<div style={{ fontSize: "11px", fontWeight: 500, color: colors.textMuted }}>
267+
Element
268+
</div>
269+
<button
270+
type="button"
271+
onClick={() => {
272+
if (highlightedIndex === i) {
273+
emit(REMOVE_HIGHLIGHT);
274+
setHighlightedIndex(null);
275+
} else {
276+
const color = v.impact === "critical" || v.impact === "serious"
277+
? "#d32f2f"
278+
: "#c43e00";
279+
const localSelector = v.selector.replace(/^.*>>>\s*iframe>\s*/, "");
280+
emit(HIGHLIGHT, {
281+
elements: [localSelector],
282+
color,
283+
style: "solid",
284+
});
285+
setHighlightedIndex(i);
286+
}
287+
}}
288+
style={{
289+
padding: "2px 8px",
290+
fontSize: "11px",
291+
fontWeight: 500,
292+
border: `1px solid ${colors.codeBorder}`,
293+
borderRadius: "4px",
294+
background: highlightedIndex === i ? IMPACT_COLOR[v.impact] : colors.codeBg,
295+
color: highlightedIndex === i ? "#fff" : colors.text,
296+
cursor: "pointer",
297+
}}
298+
>
299+
Highlight
300+
</button>
257301
</div>
258302
<pre
259303
style={{

src/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@ export const ADDON_ID = "accesslint/a11y";
22
export const PARAM_KEY = "accesslint";
33
export const STATUS_TYPE_ID = "accesslint/a11y";
44
export const RESULT_EVENT = `${ADDON_ID}/result`;
5+
6+
// Storybook highlight addon events (storybook/highlight is only available on
7+
// the preview side, so we derive the event names the same way Storybook does).
8+
const HIGHLIGHT_ADDON_ID = "storybook/highlight";
9+
export const HIGHLIGHT = `${HIGHLIGHT_ADDON_ID}/add`;
10+
export const REMOVE_HIGHLIGHT = `${HIGHLIGHT_ADDON_ID}/remove`;

0 commit comments

Comments
 (0)