Skip to content

Commit 4aed7c7

Browse files
Copilotneilime
authored andcommitted
fix(theme): color contrast violations in gradient buttons and UI borders for WCAG AA compliance
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent 0cfc650 commit 4aed7c7

9 files changed

Lines changed: 126 additions & 79 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
"esbenp.prettier-vscode",
1818
"msjsdiag.vscode-react-native"
1919
],
20-
"mcpServers": {
21-
"playwright": {
22-
"command": "npx",
23-
"args": ["@playwright/mcp@latest"]
24-
},
25-
"lighthouse": {
26-
"command": "npx",
27-
"args": ["lighthouse-mcp"],
28-
"disabled": false,
29-
"autoApprove": []
20+
"mcp": {
21+
"servers": {
22+
"playwright": {
23+
"command": "npx",
24+
"args": ["@playwright/mcp@latest"]
25+
},
26+
"lighthouse": {
27+
"command": "npx",
28+
"args": ["lighthouse-mcp"]
29+
}
3030
}
3131
}
3232
}

ACCESSIBILITY.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ All colors tested on white background (#ffffff):
2323
- **Warning** (#8a6b00): 5.02:1 contrast ratio ✓
2424
- **Danger** (#d1242a): 5.25:1 contrast ratio ✓
2525

26+
### Button Contrast
27+
28+
- **Primary button gradient** (linear-gradient(135deg, #0073cc 0%, #cc4400 100%) on white text):
29+
- Start color: 4.86:1 contrast ratio ✓
30+
- End color: 4.78:1 contrast ratio ✓
31+
- Minimum: 4.78:1 (exceeds WCAG AA)
32+
- **Outline buttons**: Use solid primary color borders (4.86:1) for proper contrast
33+
2634
### Dark Mode Color Palette
2735

2836
All colors tested on dark background (#1d2026):
@@ -34,6 +42,12 @@ All colors tested on dark background (#1d2026):
3442
- **Primary Light** (#66b3ff): 7.35:1 contrast ratio ✓
3543
- **Primary Lighter** (#73baff): 7.92:1 contrast ratio ✓
3644
- **Primary Lightest** (#99ccff): 9.66:1 contrast ratio ✓
45+
- **Secondary text** (#d3d7e2): 11.34:1 contrast ratio ✓
46+
- **White text** (#ffffff): 16.32:1 contrast ratio ✓
47+
48+
### Button Contrast in Dark Mode
49+
50+
- **Outline buttons**: Use primary-light color (#66b3ff) for borders (7.35:1 contrast ratio) ✓
3751

3852
### Requirements
3953

@@ -57,7 +71,10 @@ All interactive elements are fully accessible via keyboard:
5771
- Focus indicators use 3px solid outlines with 3px offset
5872
- `:focus-visible` pseudo-class for keyboard-only focus states
5973
- Mouse clicks don't show focus rings (better UX)
60-
- Enhanced visibility in both light and dark modes
74+
- Enhanced visibility in both light and dark modes:
75+
- Light mode: Uses primary color (#0073cc) for focus outlines
76+
- Dark mode: Uses primary-light color (#66b3ff) for better visibility
77+
- Focus outlines are visible on all interactive elements
6178

6279
## Screen Reader Support
6380

@@ -96,8 +113,10 @@ The theme respects `prefers-reduced-motion` settings:
96113

97114
The theme adapts to high contrast preferences:
98115

99-
- Darker primary colors in high contrast mode
116+
- Enhanced primary colors in high contrast mode (#005299 for primary, #993300 for secondary)
117+
- Solid backgrounds for gradient buttons to ensure maximum contrast
100118
- Thicker focus outlines (4px instead of 3px)
119+
- Thicker borders on outline buttons (3px instead of 2px)
101120
- Enhanced borders for better element distinction
102121
- Maintains usability for users with low vision
103122

packages/docs/src/pages/index.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
.swatch code {
4040
font-family: var(--ifm-font-family-monospace);
4141
font-size: 0.85rem;
42+
color: var(--hk-color-dark);
43+
}
44+
45+
[data-theme="dark"] .swatch code {
46+
color: #fff;
4247
}
4348

4449
.callout {

packages/theme/src/styles/hoverkraft.css

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
--ifm-navbar-shadow: 0 1px 3px rgba(29, 32, 38, 0.1);
4242
--ifm-button-border-radius: 8px;
43-
--docusaurus-highlighted-code-line-bg: rgba(25, 152, 255, 0.1);
43+
--docusaurus-highlighted-code-line-bg: rgba(0, 115, 204, 0.1);
4444
}
4545

4646
[data-theme="dark"] {
@@ -58,7 +58,7 @@
5858
--ifm-font-color-secondary: #d3d7e2;
5959
--ifm-navbar-background-color: rgba(37, 40, 50, 0.95);
6060
--ifm-navbar-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
61-
--docusaurus-highlighted-code-line-bg: rgba(25, 152, 255, 0.2);
61+
--docusaurus-highlighted-code-line-bg: rgba(77, 166, 255, 0.2);
6262
}
6363

6464
html,
@@ -82,7 +82,8 @@ body {
8282
}
8383

8484
[data-theme="dark"] :focus-visible {
85-
outline: 3px solid var(--ifm-color-primary);
85+
outline: 3px solid var(--ifm-color-primary-light);
86+
outline-offset: 3px;
8687
}
8788

8889
.navbar {
@@ -180,7 +181,7 @@ body {
180181

181182
.button.button--primary,
182183
.button--primary {
183-
background: linear-gradient(135deg, #1998ff 0%, #ff5a02 100%);
184+
background: linear-gradient(135deg, #0073cc 0%, #cc4400 100%);
184185
border: none;
185186
border-radius: 999px;
186187
color: #ffffff !important;
@@ -191,7 +192,7 @@ body {
191192
padding: 0.875rem 2.25rem;
192193
font-weight: 600;
193194
text-decoration: none !important;
194-
box-shadow: 0 8px 24px rgba(25, 152, 255, 0.35);
195+
box-shadow: 0 8px 24px rgba(0, 115, 204, 0.35);
195196
transition:
196197
transform 0.2s ease,
197198
box-shadow 0.2s ease,
@@ -201,21 +202,21 @@ body {
201202
.button.button--primary:hover,
202203
.button--primary:hover {
203204
transform: translateY(-2px);
204-
box-shadow: 0 14px 32px rgba(25, 152, 255, 0.45);
205-
filter: brightness(1.03);
205+
box-shadow: 0 14px 32px rgba(0, 115, 204, 0.45);
206+
filter: brightness(1.1);
206207
}
207208

208209
.button.button--primary:active,
209210
.button--primary:active {
210211
transform: translateY(0);
211-
box-shadow: 0 6px 18px rgba(25, 152, 255, 0.4);
212+
box-shadow: 0 6px 18px rgba(0, 115, 204, 0.4);
212213
}
213214

214215
.button.button--primary:focus-visible,
215216
.button--primary:focus-visible {
216217
box-shadow:
217-
0 0 0 4px rgba(25, 152, 255, 0.25),
218-
0 12px 28px rgba(25, 152, 255, 0.45);
218+
0 0 0 4px rgba(0, 115, 204, 0.25),
219+
0 12px 28px rgba(0, 115, 204, 0.45);
219220
}
220221

221222
.button.button--secondary,
@@ -224,7 +225,7 @@ body {
224225
.button--outline {
225226
background: transparent;
226227
border-radius: 999px;
227-
border: 2px solid rgba(25, 152, 255, 0.35);
228+
border: 2px solid var(--hk-color-primary);
228229
color: var(--hk-color-dark);
229230
display: inline-flex;
230231
align-items: center;
@@ -245,31 +246,31 @@ body {
245246
[data-theme="dark"] .button.button--outline,
246247
[data-theme="dark"] .button--outline {
247248
color: #ffffff;
248-
border-color: rgba(255, 255, 255, 0.4);
249+
border-color: var(--ifm-color-primary);
249250
}
250251

251252
.button.button--secondary:hover,
252253
.button--secondary:hover,
253254
.button.button--outline:hover,
254255
.button--outline:hover {
255256
transform: translateY(-2px);
256-
background: rgba(25, 152, 255, 0.08);
257-
border-color: rgba(25, 152, 255, 0.5);
257+
background: rgba(0, 115, 204, 0.08);
258+
border-color: var(--hk-color-primary);
258259
}
259260

260261
[data-theme="dark"] .button.button--secondary:hover,
261262
[data-theme="dark"] .button--secondary:hover,
262263
[data-theme="dark"] .button.button--outline:hover,
263264
[data-theme="dark"] .button--outline:hover {
264-
background: rgba(255, 255, 255, 0.12);
265-
border-color: rgba(255, 255, 255, 0.6);
265+
background: rgba(77, 166, 255, 0.12);
266+
border-color: var(--ifm-color-primary-light);
266267
}
267268

268269
.button.button--secondary:focus-visible,
269270
.button--secondary:focus-visible,
270271
.button.button--outline:focus-visible,
271272
.button--outline:focus-visible {
272-
box-shadow: 0 0 0 4px rgba(25, 152, 255, 0.25);
273+
box-shadow: 0 0 0 4px rgba(0, 115, 204, 0.25);
273274
}
274275

275276
a {
@@ -350,18 +351,18 @@ a:hover {
350351

351352
.hero .button.button--primary,
352353
.hero .button--primary {
353-
box-shadow: 0 12px 30px rgba(25, 152, 255, 0.45);
354+
box-shadow: 0 12px 30px rgba(0, 115, 204, 0.45);
354355
}
355356

356357
.hero .button.button--primary:hover,
357358
.hero .button--primary:hover {
358-
box-shadow: 0 18px 40px rgba(25, 152, 255, 0.5);
359+
box-shadow: 0 18px 40px rgba(0, 115, 204, 0.5);
359360
}
360361

361362
@media (prefers-contrast: high) {
362363
:root {
363-
--hk-color-primary: #0056b3;
364-
--hk-color-secondary: #b33d00;
364+
--hk-color-primary: #005299;
365+
--hk-color-secondary: #993300;
365366
}
366367

367368
:focus-visible {
@@ -371,6 +372,19 @@ a:hover {
371372
.navbar {
372373
border-bottom: 2px solid currentColor;
373374
}
375+
376+
.button.button--primary,
377+
.button--primary {
378+
background: #005299;
379+
border: 2px solid #003d73;
380+
}
381+
382+
.button.button--secondary,
383+
.button--secondary,
384+
.button.button--outline,
385+
.button--outline {
386+
border-width: 3px;
387+
}
374388
}
375389

376390
@media (prefers-reduced-motion: reduce) {

packages/theme/src/theme/hoverscape/HoverkraftBrandHighlight.module.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
.highlight {
2-
background: linear-gradient(45deg, #1998ff, #ff5a02);
2+
background: linear-gradient(
3+
45deg,
4+
var(--hk-color-primary, #0073cc),
5+
var(--hk-color-secondary, #cc4400)
6+
);
37
background-clip: text;
48
-webkit-background-clip: text;
59
-webkit-text-fill-color: transparent;

packages/theme/src/theme/hoverscape/HoverkraftButton.module.css

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
}
1919

2020
.button:focus-visible {
21-
outline: 3px solid rgba(25, 152, 255, 0.4);
21+
outline: 3px solid var(--hk-color-primary, #0073cc);
22+
outline-offset: 2px;
23+
}
24+
25+
[data-theme="dark"] .button:focus-visible {
26+
outline: 3px solid var(--ifm-color-primary-light, #66b3ff);
2227
outline-offset: 2px;
2328
}
2429

@@ -43,70 +48,70 @@
4348
}
4449

4550
.primary {
46-
background: linear-gradient(135deg, #1998ff 0%, #ff5a02 100%);
47-
box-shadow: 0 8px 24px rgba(25, 152, 255, 0.35);
51+
background: linear-gradient(135deg, #0073cc 0%, #cc4400 100%);
52+
box-shadow: 0 8px 24px rgba(0, 115, 204, 0.35);
4853
}
4954

5055
.primary:hover {
5156
transform: translateY(-2px);
52-
box-shadow: 0 14px 32px rgba(25, 152, 255, 0.45);
53-
filter: brightness(1.03);
57+
box-shadow: 0 14px 32px rgba(0, 115, 204, 0.45);
58+
filter: brightness(1.1);
5459
color: #ffffff;
55-
text-shadow: 0 3px 12px rgba(8, 43, 80, 0.35);
60+
text-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
5661
}
5762

5863
.primary:active {
5964
transform: translateY(0);
60-
box-shadow: 0 6px 18px rgba(25, 152, 255, 0.35);
65+
box-shadow: 0 6px 18px rgba(0, 115, 204, 0.35);
6166
}
6267

6368
.secondary {
64-
background: rgba(25, 152, 255, 0.15);
65-
border: 2px solid rgba(25, 152, 255, 0.5);
69+
background: rgba(0, 115, 204, 0.15);
70+
border: 2px solid var(--hk-color-primary, #0073cc);
6671
color: var(--hk-color-dark, #1d2026);
67-
box-shadow: 0 2px 8px rgba(25, 152, 255, 0.15);
72+
box-shadow: 0 2px 8px rgba(0, 115, 204, 0.15);
6873
}
6974

7075
.secondary:hover {
7176
transform: translateY(-2px);
72-
background: rgba(25, 152, 255, 0.22);
73-
border-color: rgba(25, 152, 255, 0.65);
74-
box-shadow: 0 4px 12px rgba(25, 152, 255, 0.25);
75-
text-shadow: 0 3px 12px rgba(8, 43, 80, 0.35);
77+
background: rgba(0, 115, 204, 0.22);
78+
border-color: var(--hk-color-primary-dark, #0066b8);
79+
box-shadow: 0 4px 12px rgba(0, 115, 204, 0.25);
80+
text-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
7681
}
7782

7883
.outline {
7984
background: transparent;
80-
border: 2px solid rgba(25, 152, 255, 0.35);
85+
border: 2px solid var(--hk-color-primary, #0073cc);
8186
color: var(--hk-color-dark, #1d2026);
8287
}
8388

8489
.outline:hover {
8590
transform: translateY(-2px);
86-
background: rgba(25, 152, 255, 0.08);
87-
border-color: rgba(25, 152, 255, 0.5);
88-
text-shadow: 0 3px 12px rgba(8, 43, 80, 0.35);
91+
background: rgba(0, 115, 204, 0.08);
92+
border-color: var(--hk-color-primary, #0073cc);
93+
text-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
8994
}
9095

9196
[data-theme="dark"] .secondary {
92-
background: rgba(25, 152, 255, 0.25);
93-
border-color: rgba(25, 152, 255, 0.6);
97+
background: rgba(77, 166, 255, 0.25);
98+
border-color: var(--ifm-color-primary, #4da6ff);
9499
color: #ffffff;
95-
box-shadow: 0 2px 8px rgba(25, 152, 255, 0.2);
100+
box-shadow: 0 2px 8px rgba(77, 166, 255, 0.2);
96101
}
97102

98103
[data-theme="dark"] .secondary:hover {
99-
background: rgba(25, 152, 255, 0.35);
100-
border-color: rgba(25, 152, 255, 0.75);
101-
box-shadow: 0 4px 12px rgba(25, 152, 255, 0.3);
104+
background: rgba(77, 166, 255, 0.35);
105+
border-color: var(--ifm-color-primary-light, #66b3ff);
106+
box-shadow: 0 4px 12px rgba(77, 166, 255, 0.3);
102107
}
103108

104109
[data-theme="dark"] .outline {
105110
color: #ffffff;
106-
border-color: rgba(255, 255, 255, 0.4);
111+
border-color: var(--ifm-color-primary-light, #66b3ff);
107112
}
108113

109114
[data-theme="dark"] .outline:hover {
110-
background: rgba(255, 255, 255, 0.12);
111-
border-color: rgba(255, 255, 255, 0.6);
115+
background: rgba(77, 166, 255, 0.12);
116+
border-color: var(--ifm-color-primary-light, #66b3ff);
112117
}

packages/theme/src/theme/hoverscape/HoverkraftFeatureList.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222

2323
.featureCard:hover {
2424
transform: translateY(-4px);
25-
box-shadow: 0 16px 30px rgba(25, 152, 255, 0.2);
26-
border-color: #1998ff;
25+
box-shadow: 0 16px 30px rgba(0, 115, 204, 0.2);
26+
border-color: var(--hk-color-primary, #0073cc);
2727
}
2828

2929
[data-theme="dark"] .featureCard {

0 commit comments

Comments
 (0)