Skip to content

Commit 386166e

Browse files
committed
🎨 Improve accessibility of themes
1 parent 2c4327f commit 386166e

14 files changed

Lines changed: 557 additions & 310 deletions

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ The `setup` mixin can also accept the following options:
149149
| `includeUtilities` | `true` | Adds utility classes for CSS. Read more about the available utility classes [here](https://webcoreui.dev/docs/layout). |
150150
| `includeTooltip` | `true` | Adds styles for using tooltips.
151151
| `includeScrollbarStyles` | `true` | Adds styles for scrollbars.
152-
| `includeBreakpoints` | `true` | Exposes breakpoint variables in CSS for JS. Used by components for responsiveness.
152+
| `includeBreakpoints` | `true` | Exposes breakpoint variables in CSS for JS. Used by components for responsiveness. |
153+
| `theme` | `dark` | Sets the default theme. Read more about available themes [here](https://webcoreui.dev/docs/themes). |
154+
| `themes` | `()` | Pass a map to enable multiple themes. Values can be arbitrary CSS selectors that actives the theme. |
153155

154156
Default component styles can be changed by overriding the following CSS variables:
155157

src/data.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,13 @@ export const menuLogo = {
120120

121121
export const themes = {
122122
'#252525': 'dark',
123-
'#FFF': 'light',
124-
'#415a77': 'midnight',
125-
'#d5bdaf': 'vintage',
123+
'#DDD': 'light',
124+
'#415A77': 'midnight',
126125
'#fCBA28': 'amber',
126+
'#C9D4D9': 'nordic',
127+
'#D5BDAF': 'vintage',
127128
'#9D2BD6': 'synthwave',
128-
'#9A1F40': 'velvet'
129+
'#3B9B3E': 'forest'
129130
}
130131

131132
export const toggleThemes = {

src/scss/config.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@forward 'webcore.config.scss';
22
@forward './config/color-palette';
33
@forward './config/css-values';
4+
@forward './config/functions.scss';
45
@forward './config/typography';
56
@forward './config/variables';
67
@forward './config/layout';

src/scss/config/functions.scss

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@use 'sass:math';
2+
@use 'sass:color';
3+
4+
@function _linear-channel($c) {
5+
$c: math.div($c, 255);
6+
7+
@if $c <= 0.04045 {
8+
@return math.div($c, 12.92);
9+
}
10+
11+
@return math.pow(math.div($c + 0.055, 1.055), 2.4);
12+
}
13+
14+
@function luminance($color) {
15+
$r: _linear-channel(color.channel($color, 'red', $space: rgb));
16+
$g: _linear-channel(color.channel($color, 'green', $space: rgb));
17+
$b: _linear-channel(color.channel($color, 'blue', $space: rgb));
18+
19+
@return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
20+
}
21+
22+
@function contrast($c1, $c2) {
23+
$l1: luminance($c1);
24+
$l2: luminance($c2);
25+
26+
@if $l1 < $l2 {
27+
$tmp: $l1;
28+
$l1: $l2;
29+
$l2: $tmp;
30+
}
31+
32+
@return math.div($l1 + 0.05, $l2 + 0.05);
33+
}
34+
35+
@function wcag-required($level) {
36+
@if $level == AAA {
37+
@return 7;
38+
}
39+
40+
@return 4.5;
41+
}

src/scss/config/mixins.scss

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@use 'webcore.config.scss' as *;
77
@use './color-palette.scss' as *;
88
@use './css-values.scss' as *;
9+
@use './functions.scss' as *;
910
@use './layout.scss' as *;
1011
@use './typography.scss' as *;
1112
@use './variables.scss' as *;
@@ -406,3 +407,68 @@
406407
}
407408
}
408409
}
410+
411+
@mixin validate-contrast($themeName, $tokens, $level, $print) {
412+
$required: wcag-required($level);
413+
$pairs: (
414+
primary: (bg: primary-70, fg: primary),
415+
secondary: (bg: primary-70, fg: primary-10),
416+
muted: (bg: primary-70, fg: primary-20),
417+
disabled: (bg: primary-70, fg: primary-30),
418+
info: (bg: info, fg: info-fg),
419+
success: (bg: success, fg: success-fg),
420+
warning: (bg: warning, fg: warning-fg),
421+
alert: (bg: alert, fg: alert-fg)
422+
);
423+
424+
@each $name, $pair in $pairs {
425+
$bg: map.get($tokens, map.get($pair, bg));
426+
$fg: map.get($tokens, map.get($pair, fg));
427+
428+
$ratio: contrast($fg, $bg);
429+
430+
$logName: '#{$themeName} #{$name}';
431+
432+
@if not $print {
433+
$logName: '#{map.get($pair, fg)} against #{map.get($pair, bg)} in theme "#{$themeName}"';
434+
}
435+
436+
@if $ratio < $required {
437+
@if $print {
438+
contrast: '[#{$logName}] - FAILED: actual: #{$ratio}, required: #{$required} (fg: #{$fg}, bg: #{$bg})';
439+
} @else {
440+
@warn '#{$logName} is below WCAG #{$level} (actual: #{$ratio}, required: #{$required})';
441+
}
442+
} @else {
443+
@if $print {
444+
contrast: '[#{$logName}] - PASSED';
445+
}
446+
}
447+
}
448+
}
449+
450+
@mixin validate-tokens($referenceMap, $themeName, $tokens) {
451+
$referenceKeys: map.keys($referenceMap);
452+
$themeKeys: map.keys($tokens);
453+
454+
@each $key in $referenceKeys {
455+
@if not list.index($themeKeys, $key) {
456+
@warn 'Theme `#{$themeName}` is missing key `#{$key}`.';
457+
}
458+
}
459+
460+
@each $key in $themeKeys {
461+
@if not list.index($referenceKeys, $key) {
462+
@warn 'Theme `#{$themeName}` has unknown key `#{$key}`.';
463+
}
464+
}
465+
}
466+
467+
@mixin validate-theme($themes, $selectedTheme, $level, $print: false) {
468+
$referenceName: list.nth(map.keys($themes), 1);
469+
$referenceMap: map.get($themes, $referenceName);
470+
$tokens: map.get($themes, $selectedTheme);
471+
472+
@include validate-contrast($selectedTheme, $tokens, $level, $print);
473+
@include validate-tokens($referenceMap, $selectedTheme, $tokens);
474+
}

0 commit comments

Comments
 (0)