@fpkit/acss 1.0.0-beta.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +92 -0
  2. package/docs/README.md +325 -0
  3. package/docs/guides/accessibility.md +764 -0
  4. package/docs/guides/architecture.md +705 -0
  5. package/docs/guides/composition.md +688 -0
  6. package/docs/guides/css-variables.md +522 -0
  7. package/docs/guides/storybook.md +828 -0
  8. package/docs/guides/testing.md +817 -0
  9. package/docs/testing/focus-indicator-testing.md +437 -0
  10. package/libs/{chunk-7XPFW7CB.js → chunk-43TK2ICH.js} +2 -2
  11. package/libs/chunk-5PJYLVFY.cjs +17 -0
  12. package/libs/chunk-5PJYLVFY.cjs.map +1 -0
  13. package/libs/chunk-E4OSROCA.cjs +17 -0
  14. package/libs/chunk-E4OSROCA.cjs.map +1 -0
  15. package/libs/chunk-KVKQLRJG.js +10 -0
  16. package/libs/chunk-KVKQLRJG.js.map +1 -0
  17. package/libs/{chunk-QVW6W76L.cjs → chunk-MGPWZRBX.cjs} +3 -3
  18. package/libs/chunk-NNTBIHSD.js +8 -0
  19. package/libs/chunk-NNTBIHSD.js.map +1 -0
  20. package/libs/{chunk-X3JCTEPD.js → chunk-QKHPHMG2.js} +2 -2
  21. package/libs/{chunk-T4T6GWYQ.cjs → chunk-R7NLLZU2.cjs} +3 -3
  22. package/libs/{chunk-X5LGFCWG.js → chunk-UJAQVHWC.js} +3 -3
  23. package/libs/{chunk-DKTHCQ5P.cjs → chunk-X5RKCLDC.cjs} +3 -3
  24. package/libs/components/breadcrumbs/breadcrumb.cjs +5 -5
  25. package/libs/components/breadcrumbs/breadcrumb.d.cts +1 -1
  26. package/libs/components/breadcrumbs/breadcrumb.d.ts +1 -1
  27. package/libs/components/breadcrumbs/breadcrumb.js +2 -2
  28. package/libs/components/button.cjs +3 -3
  29. package/libs/components/button.d.cts +1 -1
  30. package/libs/components/button.d.ts +1 -1
  31. package/libs/components/button.js +1 -1
  32. package/libs/components/buttons/button.css +1 -1
  33. package/libs/components/buttons/button.css.map +1 -1
  34. package/libs/components/buttons/button.min.css +2 -2
  35. package/libs/components/dialog/dialog.cjs +4 -4
  36. package/libs/components/dialog/dialog.js +2 -2
  37. package/libs/components/icons/icon.d.cts +32 -32
  38. package/libs/components/icons/icon.d.ts +32 -32
  39. package/libs/components/link/link.cjs +11 -3
  40. package/libs/components/link/link.d.cts +131 -3
  41. package/libs/components/link/link.d.ts +131 -3
  42. package/libs/components/link/link.js +1 -1
  43. package/libs/components/list/list.css +1 -1
  44. package/libs/components/list/list.min.css +1 -1
  45. package/libs/components/modal.cjs +3 -3
  46. package/libs/components/modal.js +2 -2
  47. package/libs/hooks.cjs +3 -3
  48. package/libs/hooks.d.cts +1 -1
  49. package/libs/hooks.d.ts +1 -1
  50. package/libs/hooks.js +2 -2
  51. package/libs/index.cjs +12 -12
  52. package/libs/index.css +1 -1
  53. package/libs/index.css.map +1 -1
  54. package/libs/index.d.cts +237 -2
  55. package/libs/index.d.ts +237 -2
  56. package/libs/index.js +5 -5
  57. package/package.json +4 -3
  58. package/src/components/README.mdx +1 -1
  59. package/src/components/breadcrumbs/breadcrumb.test.tsx +1 -2
  60. package/src/components/buttons/README.mdx +19 -9
  61. package/src/components/buttons/button.scss +5 -0
  62. package/src/components/buttons/button.stories.tsx +8 -5
  63. package/src/components/buttons/button.tsx +19 -15
  64. package/src/components/cards/card.stories.tsx +1 -1
  65. package/src/components/details/details.stories.tsx +1 -1
  66. package/src/components/form/form.stories.tsx +1 -1
  67. package/src/components/form/input.stories.tsx +1 -1
  68. package/src/components/form/select.stories.tsx +1 -1
  69. package/src/components/heading/README.mdx +292 -0
  70. package/src/components/icons/icon.stories.tsx +1 -1
  71. package/src/components/link/link.stories.tsx +205 -8
  72. package/src/components/link/link.test.tsx +1 -1
  73. package/src/components/link/link.tsx +22 -0
  74. package/src/components/link/link.types.ts +11 -3
  75. package/src/components/list/list.scss +1 -1
  76. package/src/components/nav/nav.stories.tsx +1 -1
  77. package/src/components/ui.stories.tsx +53 -19
  78. package/src/docs/accessibility.mdx +484 -0
  79. package/src/docs/composition.mdx +549 -0
  80. package/src/docs/css-variables.mdx +380 -0
  81. package/src/docs/fpkit-developer.mdx +623 -0
  82. package/src/introduction.mdx +356 -0
  83. package/src/styles/buttons/button.css +4 -0
  84. package/src/styles/buttons/button.css.map +1 -1
  85. package/src/styles/index.css +9 -3
  86. package/src/styles/index.css.map +1 -1
  87. package/src/styles/list/list.css +1 -1
  88. package/src/styles/utilities/_disabled.scss +5 -4
  89. package/libs/chunk-33PNJ4LO.cjs +0 -15
  90. package/libs/chunk-33PNJ4LO.cjs.map +0 -1
  91. package/libs/chunk-GT77BX4L.cjs +0 -17
  92. package/libs/chunk-GT77BX4L.cjs.map +0 -1
  93. package/libs/chunk-OVWLQYMK.js +0 -10
  94. package/libs/chunk-OVWLQYMK.js.map +0 -1
  95. package/libs/chunk-UEPAWMDF.js +0 -8
  96. package/libs/chunk-UEPAWMDF.js.map +0 -1
  97. package/libs/link-5192f411.d.ts +0 -323
  98. /package/libs/{chunk-7XPFW7CB.js.map → chunk-43TK2ICH.js.map} +0 -0
  99. /package/libs/{chunk-QVW6W76L.cjs.map → chunk-MGPWZRBX.cjs.map} +0 -0
  100. /package/libs/{chunk-X3JCTEPD.js.map → chunk-QKHPHMG2.js.map} +0 -0
  101. /package/libs/{chunk-T4T6GWYQ.cjs.map → chunk-R7NLLZU2.cjs.map} +0 -0
  102. /package/libs/{chunk-X5LGFCWG.js.map → chunk-UJAQVHWC.js.map} +0 -0
  103. /package/libs/{chunk-DKTHCQ5P.cjs.map → chunk-X5RKCLDC.cjs.map} +0 -0
@@ -1,7 +1,8 @@
1
+ import React from "react";
1
2
  import { StoryObj, Meta } from "@storybook/react-vite";
2
3
  import { within, expect, userEvent, fn } from "storybook/test";
3
4
  import { useRef, useEffect } from "react";
4
- import Link from "./link";
5
+ import Link, { IconLink, LinkButton } from "./link";
5
6
  import type { LinkProps } from "./link.types";
6
7
  import "../../styles/link/link.css";
7
8
 
@@ -41,8 +42,30 @@ const meta = {
41
42
  description: {
42
43
  component: `A semantic, accessible anchor component with enhanced security for external links and flexible styling variants.
43
44
 
45
+ ## Features
46
+
47
+ - **Automatic Security**: External links (\`target="_blank"\`) automatically get \`rel="noopener noreferrer"\`
48
+ - **WCAG 2.1 AA Compliant**: Focus indicators, semantic HTML, keyboard navigation
49
+ - **Flexible Styling**: Text links, button-styled links, and pill variants
50
+ - **Performance**: Optional prefetch hints for faster navigation
51
+ - **Ref Forwarding**: Direct DOM access for focus management
52
+ - **Type-Safe**: Full TypeScript support with comprehensive prop types
53
+
54
+ ## Exported Components
55
+
56
+ ### Link (default)
57
+ Main component for creating semantic anchor elements with enhanced features.
58
+
59
+ ### IconLink
60
+ Specialized component for icon-only or icon-with-text links.
61
+
62
+ ### LinkButton
63
+ Convenience component for button-styled links.
64
+
44
65
  ## CSS Variables
45
66
 
67
+ All units use **rem** (not px). Base: 1rem = 16px.
68
+
46
69
  ### Typography & Color
47
70
  - \`--link-color\`: Link text color (default: #085ab7)
48
71
  - \`--link-fw\`: Font weight (default: 400)
@@ -50,32 +73,40 @@ const meta = {
50
73
 
51
74
  ### Text Decoration
52
75
  - \`--link-decoration\`: Text decoration style (default: none, underline on hover/focus)
53
- - \`--link-decoration-offset\`: Underline offset (default: 0.09375rem / 1.5px)
54
- - \`--link-decoration-thickness\`: Underline thickness (default: 0.0625rem / 1px)
76
+ - \`--link-decoration-offset\`: Underline offset (default: 0.09375rem)
77
+ - \`--link-decoration-thickness\`: Underline thickness (default: 0.0625rem)
55
78
  - \`--link-skip-ink\`: Text decoration skip ink (default: auto)
56
79
 
57
80
  ### Background & Border
58
81
  - \`--link-bg\`: Background color (default: transparent)
59
82
  - \`--link-radius\`: Border radius (default: 0.25rem, 99rem for pills)
60
83
 
61
- ### Spacing (Button Variants)
84
+ ### Spacing
62
85
  - \`--link-padding-inline\`: Horizontal padding (default: 0, calculated for button variants)
63
86
  - \`--link-padding-block\`: Vertical padding (default: 0, calculated for button variants)
64
87
 
65
88
  ### Focus Indicators (WCAG 2.4.7)
66
89
  - \`--link-focus-color\`: Focus outline color (default: currentColor)
67
- - \`--link-focus-width\`: Focus outline width (default: 0.125rem / 2px)
68
- - \`--link-focus-offset\`: Focus outline offset (default: 0.125rem / 2px)
90
+ - \`--link-focus-width\`: Focus outline width (default: 0.125rem)
91
+ - \`--link-focus-offset\`: Focus outline offset (default: 0.125rem)
69
92
  - \`--link-focus-style\`: Focus outline style (default: solid)
70
93
 
71
94
  ### Transitions
72
95
  - \`--link-transition\`: Transition timing (default: all 0.75s ease-in-out)
73
96
 
74
- ### Button Variants
97
+ ### Button Variant Variables
98
+ Applied when using \`btnStyle\` prop, \`data-btn\` attribute, or \`<b>\`/\`<i>\` wrappers:
75
99
  - \`--link-button-color\`: Button link text color (default: var(--link-color))
76
- - \`--link-border-width\`: Button border width (default: 0.125rem / 2px)
100
+ - \`--link-border-width\`: Button border width (default: 0.125rem)
77
101
  - \`--link-border-color\`: Button border color (default: currentColor)
78
102
  - \`--link-border-style\`: Button border style (default: solid)
103
+
104
+ ## Usage Patterns
105
+
106
+ ### Button Styling (3 ways)
107
+ 1. **btnStyle prop**: \`<Link href="/signup" btnStyle="btn">Sign Up</Link>\`
108
+ 2. **Wrapper elements**: \`<Link href="/signup"><b>Sign Up</b></Link>\` (button) or \`<Link href="/signup"><i>Sign Up</i></Link>\` (pill)
109
+ 3. **Direct attribute**: \`<Link href="/signup" data-btn>Sign Up</Link>\`
79
110
  `,
80
111
  },
81
112
  },
@@ -458,3 +489,169 @@ export const WithBothHandlers: Story = {
458
489
  },
459
490
  },
460
491
  };
492
+
493
+ /**
494
+ * Button-styled link using btnStyle prop.
495
+ * Demonstrates direct usage of the btnStyle prop instead of wrapper elements.
496
+ */
497
+ export const WithBtnStyleProp: Story = {
498
+ args: {
499
+ href: "/dashboard",
500
+ btnStyle: "btn",
501
+ children: "Go to Dashboard",
502
+ },
503
+ parameters: {
504
+ docs: {
505
+ description: {
506
+ story:
507
+ "Use the `btnStyle` prop to apply button styling directly. This sets the `data-btn` attribute on the link element.",
508
+ },
509
+ },
510
+ },
511
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
512
+ const canvas = within(canvasElement);
513
+ const link = canvas.getByRole("link");
514
+
515
+ // Verify btnStyle creates data-btn attribute
516
+ expect(link).toHaveAttribute("data-btn", "btn");
517
+ expect(link).toHaveTextContent("Go to Dashboard");
518
+ },
519
+ };
520
+
521
+ /**
522
+ * IconLink component for icon-based navigation.
523
+ * Specialized component for links with icons.
524
+ */
525
+ export const IconLinkComponent: Story = {
526
+ render: () => (
527
+ <IconLink
528
+ href="/home"
529
+ aria-label="Return to homepage"
530
+ icon={
531
+ <svg
532
+ aria-hidden="true"
533
+ width="20"
534
+ height="20"
535
+ viewBox="0 0 16 16"
536
+ fill="currentColor"
537
+ >
538
+ <path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.708L2 8.207V13.5A1.5 1.5 0 0 0 3.5 15h9a1.5 1.5 0 0 0 1.5-1.5V8.207l.646.647a.5.5 0 0 0 .708-.708L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.707 1.5ZM13 7.207V13.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V7.207l5-5 5 5Z" />
539
+ </svg>
540
+ }
541
+ >
542
+ {null}
543
+ </IconLink>
544
+ ),
545
+ parameters: {
546
+ docs: {
547
+ description: {
548
+ story:
549
+ "The `IconLink` component is a convenience wrapper for icon-based links. Always include an `aria-label` for accessibility and set `aria-hidden='true'` on the icon.",
550
+ },
551
+ },
552
+ },
553
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
554
+ const canvas = within(canvasElement);
555
+ const link = canvas.getByRole("link");
556
+
557
+ // Verify accessible name
558
+ expect(link).toHaveAccessibleName("Return to homepage");
559
+
560
+ // Verify icon is hidden from screen readers
561
+ const svg = link.querySelector("svg");
562
+ expect(svg).toHaveAttribute("aria-hidden", "true");
563
+ },
564
+ };
565
+
566
+ /**
567
+ * IconLink with text label.
568
+ * Icon link with visible text alongside the icon.
569
+ */
570
+ export const IconLinkWithText: Story = {
571
+ render: () => (
572
+ <IconLink
573
+ href="/downloads"
574
+ icon={
575
+ <svg
576
+ aria-hidden="true"
577
+ width="16"
578
+ height="16"
579
+ viewBox="0 0 16 16"
580
+ fill="currentColor"
581
+ style={{ marginRight: "0.5rem" }}
582
+ >
583
+ <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
584
+ <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
585
+ </svg>
586
+ }
587
+ >
588
+ Download Files
589
+ </IconLink>
590
+ ),
591
+ parameters: {
592
+ docs: {
593
+ description: {
594
+ story:
595
+ "IconLink can contain both an icon and text. The icon is passed via the `icon` prop, and text is provided as children.",
596
+ },
597
+ },
598
+ },
599
+ };
600
+
601
+ /**
602
+ * LinkButton component for call-to-action links.
603
+ * Convenience component that applies button styling automatically.
604
+ */
605
+ export const LinkButtonComponent: Story = {
606
+ render: () => (
607
+ <LinkButton href="/get-started">
608
+ <b>Get Started Free</b>
609
+ </LinkButton>
610
+ ),
611
+ parameters: {
612
+ docs: {
613
+ description: {
614
+ story:
615
+ "The `LinkButton` component is a convenience wrapper for button-styled links. It maintains semantic `<a>` element while providing button appearance.",
616
+ },
617
+ },
618
+ },
619
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
620
+ const canvas = within(canvasElement);
621
+ const link = canvas.getByRole("link");
622
+
623
+ // Verify it's still a semantic anchor
624
+ expect(link.tagName).toBe("A");
625
+ expect(link).toHaveAttribute("href", "/get-started");
626
+ expect(link).toHaveTextContent("Get Started Free");
627
+ },
628
+ };
629
+
630
+ /**
631
+ * LinkButton with custom styling.
632
+ * Demonstrates CSS variable overrides on LinkButton.
633
+ */
634
+ export const LinkButtonCustom: Story = {
635
+ render: () => {
636
+ const customStyles: React.CSSProperties = {
637
+ "--link-button-color": "#ffffff",
638
+ "--link-bg": "#0066cc",
639
+ "--link-border-color": "#0066cc",
640
+ "--link-radius": "0.5rem",
641
+ } as React.CSSProperties;
642
+
643
+ return (
644
+ <LinkButton href="/signup" styles={customStyles}>
645
+ <b>Sign Up Now</b>
646
+ </LinkButton>
647
+ );
648
+ },
649
+ parameters: {
650
+ docs: {
651
+ description: {
652
+ story:
653
+ "LinkButton styling can be customized using CSS custom properties for colors, borders, and border radius.",
654
+ },
655
+ },
656
+ },
657
+ };
@@ -24,7 +24,7 @@ describe("Link Component", () => {
24
24
 
25
25
  it("should render with custom classes via UI component", () => {
26
26
  render(
27
- <Link href="/" classes="custom-link-class">
27
+ <Link href="/" className="custom-link-class">
28
28
  Test
29
29
  </Link>
30
30
  );
@@ -162,6 +162,28 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
162
162
  }
163
163
  );
164
164
 
165
+ export const IconLink = React.forwardRef<HTMLAnchorElement, LinkProps>(
166
+ ({ href, icon, ...props }, ref) => {
167
+ return (
168
+ <Link ref={ref} href={href} {...props}>
169
+ {icon}
170
+ </Link>
171
+ );
172
+ }
173
+ );
174
+
175
+ export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkProps>(
176
+ ({ href, children, ...props }, ref) => {
177
+ return (
178
+ <Link ref={ref} href={href} {...props}>
179
+ {children}
180
+ </Link>
181
+ );
182
+ }
183
+ );
184
+
185
+ IconLink.displayName = "IconLink";
186
+ LinkButton.displayName = "LinkButton";
165
187
  Link.displayName = "Link";
166
188
 
167
189
  export default Link;
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import UI from "#components/ui";
3
2
 
4
3
  /**
5
4
  * Props for the Link component.
@@ -233,8 +232,17 @@ export type LinkProps = {
233
232
  * ```
234
233
  */
235
234
  onPointerDown?: (event: React.PointerEvent<HTMLAnchorElement>) => void;
236
- } & React.ComponentProps<typeof UI> &
237
- React.ComponentPropsWithoutRef<"a">;
235
+
236
+ /**
237
+ * Icon element to display in the link (used by IconLink).
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * <IconLink href="/home" icon={<HomeIcon />} />
242
+ * ```
243
+ */
244
+ icon?: React.ReactNode;
245
+ } & Omit<React.ComponentPropsWithoutRef<"a">, 'style'>;
238
246
 
239
247
  /**
240
248
  * Props for the Link component with ref support.
@@ -18,7 +18,7 @@ dl {
18
18
  --list-margin-top: 0;
19
19
  --list-margin-bottom: 1rem; // 16px
20
20
  --list-margin-inline: 0;
21
- --list-padding-inline: 2.5rem; // 40px - default browser indent
21
+ --list-padding-inline: 0.5rem; // 40px - default browser indent
22
22
  --list-gap: 0.5rem; // 8px - gap between items
23
23
 
24
24
  // List marker/bullet styling
@@ -9,7 +9,7 @@ import Link from "../link/link";
9
9
  const meta: Meta<typeof Nav> = {
10
10
  title: "FP.REACT Components/Nav",
11
11
  component: Nav,
12
- tags: ["rc"],
12
+ tags: ["stable"],
13
13
  parameters: {
14
14
  actions: { argTypesRegex: "^on.*" },
15
15
  docs: {
@@ -15,7 +15,7 @@ import UI from "./ui";
15
15
  * - Zero runtime overhead
16
16
  */
17
17
  const meta = {
18
- title: "FP.UI",
18
+ title: "FP/UI",
19
19
  component: UI,
20
20
  tags: ["autodocs", "primitive"],
21
21
  parameters: {
@@ -782,7 +782,9 @@ export const CommonAccessibilityMistakes: Story = {
782
782
 
783
783
  {/* Missing accessible name */}
784
784
  <div>
785
- <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
785
+ <h4
786
+ style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
787
+ >
786
788
  ❌ BAD: Icon button without accessible name
787
789
  </h4>
788
790
  <UI
@@ -800,15 +802,24 @@ export const CommonAccessibilityMistakes: Story = {
800
802
  >
801
803
  ×
802
804
  </UI>
803
- <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
804
- <strong>Problem:</strong> Screen readers cannot identify this button's
805
- purpose. <strong>Fix:</strong> Add <code>aria-label="Close"</code>
805
+ <p
806
+ style={{
807
+ fontSize: "0.875rem",
808
+ color: "#721c24",
809
+ marginTop: "0.5rem",
810
+ }}
811
+ >
812
+ <strong>Problem:</strong> Screen readers cannot identify this
813
+ button's purpose. <strong>Fix:</strong> Add{" "}
814
+ <code>aria-label="Close"</code>
806
815
  </p>
807
816
  </div>
808
817
 
809
818
  {/* Non-semantic clickable div */}
810
819
  <div>
811
- <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
820
+ <h4
821
+ style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
822
+ >
812
823
  ❌ BAD: Clickable div without keyboard support
813
824
  </h4>
814
825
  <UI
@@ -825,16 +836,25 @@ export const CommonAccessibilityMistakes: Story = {
825
836
  >
826
837
  Click me (but you can't use keyboard!)
827
838
  </UI>
828
- <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
829
- <strong>Problem:</strong> Not keyboard accessible or announced to screen
830
- readers. <strong>Fix:</strong> Use <code>as="button"</code> or add{" "}
831
- <code>role="button"</code>, <code>tabIndex=0</code>, and keyboard handlers.
839
+ <p
840
+ style={{
841
+ fontSize: "0.875rem",
842
+ color: "#721c24",
843
+ marginTop: "0.5rem",
844
+ }}
845
+ >
846
+ <strong>Problem:</strong> Not keyboard accessible or announced to
847
+ screen readers. <strong>Fix:</strong> Use <code>as="button"</code>{" "}
848
+ or add <code>role="button"</code>, <code>tabIndex=0</code>, and
849
+ keyboard handlers.
832
850
  </p>
833
851
  </div>
834
852
 
835
853
  {/* Poor contrast focus indicator */}
836
854
  <div>
837
- <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
855
+ <h4
856
+ style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
857
+ >
838
858
  ❌ BAD: Insufficient focus indicator contrast
839
859
  </h4>
840
860
  <UI
@@ -851,16 +871,24 @@ export const CommonAccessibilityMistakes: Story = {
851
871
  >
852
872
  Low contrast focus
853
873
  </UI>
854
- <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
855
- <strong>Problem:</strong> Focus indicator contrast ratio is less than 3:1
856
- (WCAG 2.4.7). <strong>Fix:</strong> Use a contrasting color like dark blue
857
- on light blue background.
874
+ <p
875
+ style={{
876
+ fontSize: "0.875rem",
877
+ color: "#721c24",
878
+ marginTop: "0.5rem",
879
+ }}
880
+ >
881
+ <strong>Problem:</strong> Focus indicator contrast ratio is less
882
+ than 3:1 (WCAG 2.4.7). <strong>Fix:</strong> Use a contrasting color
883
+ like dark blue on light blue background.
858
884
  </p>
859
885
  </div>
860
886
 
861
887
  {/* Vague link text */}
862
888
  <div>
863
- <h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
889
+ <h4
890
+ style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
891
+ >
864
892
  ❌ BAD: Non-descriptive link text
865
893
  </h4>
866
894
  <UI
@@ -873,10 +901,16 @@ export const CommonAccessibilityMistakes: Story = {
873
901
  >
874
902
  Click here
875
903
  </UI>
876
- <p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
904
+ <p
905
+ style={{
906
+ fontSize: "0.875rem",
907
+ color: "#721c24",
908
+ marginTop: "0.5rem",
909
+ }}
910
+ >
877
911
  <strong>Problem:</strong> "Click here" doesn't describe the link's
878
- destination. <strong>Fix:</strong> Use descriptive text like "View product
879
- documentation".
912
+ destination. <strong>Fix:</strong> Use descriptive text like "View
913
+ product documentation".
880
914
  </p>
881
915
  </div>
882
916
  </div>