Skip to content

Commit 8a6d776

Browse files
committed
fix: support alpha channel in brand color customization with WCAG validation
Restores backward compatibility for 8-digit hex colors (#RRGGBBAA) in brand color customization while maintaining proper WCAG contrast validation. Alpha channels are now preserved in CSS output but stripped for contrast calculations to ensure accurate accessibility compliance. Key changes: - Accept 3-digit (#RGB), 6-digit (#RRGGBB), and 8-digit (#RRGGBBAA) hex formats - Preserve alpha channel in CSS variables for semi-transparent brand colors - Strip alpha for WCAG contrast calculations (validates opaque RGB values) - Warn users when alpha affects contrast validation accuracy - Add comprehensive test coverage for all color formats This fix prevents regression where existing alpha-based themes would break.
1 parent 059235d commit 8a6d776

File tree

18 files changed

+2661
-457
lines changed

18 files changed

+2661
-457
lines changed

packages/ui/src/components/Alert/Alert.module.css

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,25 @@
5454
justify-content: center;
5555
}
5656

57-
/* Severity Variants */
57+
/* Severity Variants - Use brand colors */
5858
.alert--error {
59-
background-color: rgba(239, 68, 68, 0.1);
60-
border: 1px solid rgba(239, 68, 68, 0.2);
59+
background-color: color-mix(in srgb, var(--ai-color-brand-error) 10%, transparent);
60+
border: 1px solid color-mix(in srgb, var(--ai-color-brand-error) 20%, transparent);
6161
}
6262

6363
.alert--warning {
64-
background-color: rgba(245, 158, 11, 0.1);
65-
border: 1px solid rgba(245, 158, 11, 0.2);
64+
background-color: color-mix(in srgb, var(--ai-color-brand-warning) 10%, transparent);
65+
border: 1px solid color-mix(in srgb, var(--ai-color-brand-warning) 20%, transparent);
6666
}
6767

6868
.alert--info {
69-
background-color: rgba(2, 133, 255, 0.1);
70-
border: 1px solid rgba(2, 133, 255, 0.2);
69+
background-color: color-mix(in srgb, var(--ai-color-brand-primary) 10%, transparent);
70+
border: 1px solid color-mix(in srgb, var(--ai-color-brand-primary) 20%, transparent);
7171
}
7272

7373
.alert--success {
74-
background-color: rgba(0, 134, 53, 0.1);
75-
border: 1px solid rgba(0, 134, 53, 0.2);
74+
background-color: color-mix(in srgb, var(--ai-color-brand-success) 10%, transparent);
75+
border: 1px solid color-mix(in srgb, var(--ai-color-brand-success) 20%, transparent);
7676
}
7777

7878
/* Icon - Left */
@@ -91,19 +91,19 @@
9191
}
9292

9393
.alert--error .alert__icon svg {
94-
color: var(--ai-color-error-default, #EF4444);
94+
color: var(--ai-color-brand-error);
9595
}
9696

9797
.alert--warning .alert__icon svg {
98-
color: var(--ai-color-warning-default, #F59E0B);
98+
color: var(--ai-color-brand-warning);
9999
}
100100

101101
.alert--info .alert__icon svg {
102-
color: var(--ai-color-info-default, #0285FF);
102+
color: var(--ai-color-brand-primary);
103103
}
104104

105105
.alert--success .alert__icon svg {
106-
color: var(--ai-color-success-default, #008635);
106+
color: var(--ai-color-brand-success);
107107
}
108108

109109
.alert--card .alert__icon svg {
@@ -149,36 +149,22 @@
149149
}
150150

151151
/* Dark theme adjustments */
152-
@media (prefers-color-scheme: dark) {
153-
.alert--error {
154-
background-color: rgba(255, 133, 131, 0.1);
155-
border-color: rgba(255, 133, 131, 0.2);
156-
}
157-
158-
.alert--error .alert__icon svg {
159-
color: var(--ai-color-error-light, #FF8583);
160-
}
161-
162-
.alert--warning {
163-
background-color: rgba(255, 158, 108, 0.1);
164-
border-color: rgba(255, 158, 108, 0.2);
165-
}
166-
167-
.alert--warning .alert__icon svg {
168-
color: var(--ai-color-warning-light, #FF9E6C);
169-
}
170-
171-
.alert--info {
172-
background-color: rgba(2, 133, 255, 0.1);
173-
border-color: rgba(2, 133, 255, 0.2);
174-
}
175-
176-
.alert--success {
177-
background-color: rgba(64, 201, 119, 0.1);
178-
border-color: rgba(64, 201, 119, 0.2);
179-
}
180-
181-
.alert--success .alert__icon svg {
182-
color: var(--ai-color-success-light, #40C977);
183-
}
152+
:global([data-theme='dark']) .alert--error {
153+
background-color: color-mix(in srgb, var(--ai-color-brand-error) 15%, transparent);
154+
border-color: color-mix(in srgb, var(--ai-color-brand-error) 25%, transparent);
155+
}
156+
157+
:global([data-theme='dark']) .alert--warning {
158+
background-color: color-mix(in srgb, var(--ai-color-brand-warning) 15%, transparent);
159+
border-color: color-mix(in srgb, var(--ai-color-brand-warning) 25%, transparent);
160+
}
161+
162+
:global([data-theme='dark']) .alert--info {
163+
background-color: color-mix(in srgb, var(--ai-color-brand-primary) 15%, transparent);
164+
border-color: color-mix(in srgb, var(--ai-color-brand-primary) 25%, transparent);
165+
}
166+
167+
:global([data-theme='dark']) .alert--success {
168+
background-color: color-mix(in srgb, var(--ai-color-brand-success) 15%, transparent);
169+
border-color: color-mix(in srgb, var(--ai-color-brand-success) 25%, transparent);
184170
}

packages/ui/src/components/Badge/Badge.module.css

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,50 +42,50 @@
4242

4343
/* ========== VARIANT STYLES ========== */
4444

45-
/* Default Variant - Light background with accent color */
45+
/* Default Variant - Light background with brand primary color */
4646
.badgeDefault {
47-
background-color: color-mix(in srgb, var(--ai-color-accent-blue) 10%, transparent);
48-
color: var(--ai-color-accent-blue);
47+
background-color: color-mix(in srgb, var(--ai-color-brand-primary) 10%, transparent);
48+
color: var(--ai-color-brand-primary);
4949
}
5050

5151
:global([data-theme='dark']) .badgeDefault {
52-
background-color: color-mix(in srgb, var(--ai-color-accent-blue) 15%, transparent);
52+
background-color: color-mix(in srgb, var(--ai-color-brand-primary) 15%, transparent);
5353
}
5454

55-
/* Filled Variant - Solid accent background */
55+
/* Filled Variant - Solid brand primary background */
5656
.badgeFilled {
57-
background-color: var(--ai-color-accent-blue);
58-
color: #ffffff; /* White text for contrast */
57+
background-color: var(--ai-color-brand-primary);
58+
color: var(--ai-color-brand-on-primary);
5959
}
6060

61-
/* Success Variant - Green for positive states */
61+
/* Success Variant - Brand success color for positive states */
6262
.badgeSuccess {
63-
background-color: color-mix(in srgb, var(--ai-color-accent-green) 10%, transparent);
64-
color: var(--ai-color-accent-green);
63+
background-color: color-mix(in srgb, var(--ai-color-brand-success) 10%, transparent);
64+
color: var(--ai-color-brand-success);
6565
}
6666

6767
:global([data-theme='dark']) .badgeSuccess {
68-
background-color: color-mix(in srgb, var(--ai-color-accent-green) 15%, transparent);
68+
background-color: color-mix(in srgb, var(--ai-color-brand-success) 15%, transparent);
6969
}
7070

71-
/* Warning Variant - Orange for attention states */
71+
/* Warning Variant - Brand warning color for attention states */
7272
.badgeWarning {
73-
background-color: color-mix(in srgb, var(--ai-color-accent-orange) 10%, transparent);
74-
color: var(--ai-color-accent-orange);
73+
background-color: color-mix(in srgb, var(--ai-color-brand-warning) 10%, transparent);
74+
color: var(--ai-color-brand-warning);
7575
}
7676

7777
:global([data-theme='dark']) .badgeWarning {
78-
background-color: color-mix(in srgb, var(--ai-color-accent-orange) 15%, transparent);
78+
background-color: color-mix(in srgb, var(--ai-color-brand-warning) 15%, transparent);
7979
}
8080

81-
/* Error Variant - Red for error states */
81+
/* Error Variant - Brand error color for error states */
8282
.badgeError {
83-
background-color: color-mix(in srgb, var(--ai-color-accent-red) 10%, transparent);
84-
color: var(--ai-color-accent-red);
83+
background-color: color-mix(in srgb, var(--ai-color-brand-error) 10%, transparent);
84+
color: var(--ai-color-brand-error);
8585
}
8686

8787
:global([data-theme='dark']) .badgeError {
88-
background-color: color-mix(in srgb, var(--ai-color-accent-red) 15%, transparent);
88+
background-color: color-mix(in srgb, var(--ai-color-brand-error) 15%, transparent);
8989
}
9090

9191
/* Neutral Variant - Subtle for tags and categories */

packages/ui/src/components/Button/Button.module.css

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,92 @@
168168
.rightIcon {
169169
flex-shrink: 0;
170170
}
171+
172+
/* ========== COLOR VARIANTS ========== */
173+
/* Color variants use brand colors from ThemeProvider */
174+
/* These classes work with the primary variant to provide semantic coloring */
175+
176+
/* Primary Color (default) */
177+
.buttonPrimary.colorPrimary {
178+
background-color: var(--ai-color-brand-primary);
179+
color: var(--ai-color-brand-on-primary);
180+
}
181+
182+
.buttonPrimary.colorPrimary:not(:disabled):hover {
183+
background-color: var(--ai-color-brand-primary-hover);
184+
}
185+
186+
.buttonPrimary.colorPrimary:not(:disabled):active {
187+
background-color: var(--ai-color-brand-primary-active);
188+
}
189+
190+
.buttonPrimary.colorPrimary .leftIcon,
191+
.buttonPrimary.colorPrimary .rightIcon,
192+
.buttonPrimary.colorPrimary .iconOnlyIcon {
193+
color: var(--ai-color-brand-on-primary);
194+
}
195+
196+
/* Success Color */
197+
.buttonPrimary.colorSuccess {
198+
background-color: var(--ai-color-brand-success);
199+
color: var(--ai-color-brand-on-success);
200+
}
201+
202+
.buttonPrimary.colorSuccess:not(:disabled):hover {
203+
background-color: var(--ai-color-brand-success-hover);
204+
}
205+
206+
.buttonPrimary.colorSuccess:not(:disabled):active {
207+
background-color: var(--ai-color-brand-success-active);
208+
}
209+
210+
.buttonPrimary.colorSuccess .leftIcon,
211+
.buttonPrimary.colorSuccess .rightIcon,
212+
.buttonPrimary.colorSuccess .iconOnlyIcon {
213+
color: var(--ai-color-brand-on-success);
214+
}
215+
216+
/* Warning Color */
217+
.buttonPrimary.colorWarning {
218+
background-color: var(--ai-color-brand-warning);
219+
color: var(--ai-color-brand-on-warning);
220+
}
221+
222+
.buttonPrimary.colorWarning:not(:disabled):hover {
223+
background-color: var(--ai-color-brand-warning-hover);
224+
}
225+
226+
.buttonPrimary.colorWarning:not(:disabled):active {
227+
background-color: var(--ai-color-brand-warning-active);
228+
}
229+
230+
.buttonPrimary.colorWarning .leftIcon,
231+
.buttonPrimary.colorWarning .rightIcon,
232+
.buttonPrimary.colorWarning .iconOnlyIcon {
233+
color: var(--ai-color-brand-on-warning);
234+
}
235+
236+
/* Error Color */
237+
.buttonPrimary.colorError {
238+
background-color: var(--ai-color-brand-error);
239+
color: var(--ai-color-brand-on-error);
240+
}
241+
242+
.buttonPrimary.colorError:not(:disabled):hover {
243+
background-color: var(--ai-color-brand-error-hover);
244+
}
245+
246+
.buttonPrimary.colorError:not(:disabled):active {
247+
background-color: var(--ai-color-brand-error-active);
248+
}
249+
250+
.buttonPrimary.colorError .leftIcon,
251+
.buttonPrimary.colorError .rightIcon,
252+
.buttonPrimary.colorError .iconOnlyIcon {
253+
color: var(--ai-color-brand-on-error);
254+
}
255+
256+
/* Neutral Color - uses default text colors for all variants */
257+
.colorNeutral {
258+
/* Neutral doesn't override colors, uses variant defaults */
259+
}

packages/ui/src/components/Button/Button.module.css.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ declare const styles: {
99
readonly leftIcon: string;
1010
readonly rightIcon: string;
1111
readonly iconOnlyIcon: string;
12+
readonly colorPrimary: string;
13+
readonly colorSuccess: string;
14+
readonly colorWarning: string;
15+
readonly colorError: string;
16+
readonly colorNeutral: string;
1217
};
1318

1419
export default styles;

0 commit comments

Comments
 (0)