@trishchuk/coolors-mcp 1.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.
- package/.claude/settings.local.json +39 -0
- package/.env +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
- package/.github/pull_request_template.md +97 -0
- package/.github/workflows/ci.yml +127 -0
- package/.github/workflows/deploy-docs.yml +56 -0
- package/.github/workflows/release.yml +99 -0
- package/.mcp.json +12 -0
- package/.prettierignore +1 -0
- package/CLAUDE.md +201 -0
- package/DOCUMENTATION.md +274 -0
- package/GEMINI.md +54 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +621 -0
- package/demo/podcast-player.html +903 -0
- package/dist/bin/coolors-mcp.d.ts +1 -0
- package/dist/bin/coolors-mcp.js +154 -0
- package/dist/bin/coolors-mcp.js.map +1 -0
- package/dist/bin/server.d.ts +1 -0
- package/dist/bin/server.js +3292 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/chunk-IQ7NN26V.js +114 -0
- package/dist/chunk-IQ7NN26V.js.map +1 -0
- package/dist/chunk-P3ARRKLS.js +1214 -0
- package/dist/chunk-P3ARRKLS.js.map +1 -0
- package/dist/color/index.d.ts +716 -0
- package/dist/color/index.js +153 -0
- package/dist/color/index.js.map +1 -0
- package/dist/coolors-mcp.d.ts +136 -0
- package/dist/coolors-mcp.js +7 -0
- package/dist/coolors-mcp.js.map +1 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
- package/docs/.vitepress/cache/deps/_metadata.json +127 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
- package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
- package/docs/.vitepress/cache/deps/dayjs.js +285 -0
- package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/debug.js +468 -0
- package/docs/.vitepress/cache/deps/debug.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
- package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +344 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/components/ClientGrid.vue +125 -0
- package/docs/.vitepress/components/CodeBlock.vue +231 -0
- package/docs/.vitepress/components/ConfigModal.vue +477 -0
- package/docs/.vitepress/components/DiagramModal.vue +528 -0
- package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
- package/docs/.vitepress/config.js +162 -0
- package/docs/.vitepress/theme/FundingLayout.vue +251 -0
- package/docs/.vitepress/theme/Layout.vue +134 -0
- package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
- package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
- package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
- package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
- package/docs/.vitepress/theme/custom-app.css +339 -0
- package/docs/.vitepress/theme/custom.css +699 -0
- package/docs/.vitepress/theme/index.js +25 -0
- package/docs/README.md +198 -0
- package/docs/concepts/accessibility.md +473 -0
- package/docs/concepts/color-spaces.md +222 -0
- package/docs/concepts/distance-metrics.md +384 -0
- package/docs/concepts/hct.md +261 -0
- package/docs/concepts/image-analysis.md +396 -0
- package/docs/concepts/material-design.md +306 -0
- package/docs/concepts/theme-matching.md +399 -0
- package/docs/examples/basic-colors.md +490 -0
- package/docs/examples/creating-themes.md +898 -0
- package/docs/examples/css-refactoring.md +824 -0
- package/docs/examples/image-extraction.md +882 -0
- package/docs/getting-started.md +366 -0
- package/docs/index.md +190 -0
- package/docs/installation.md +157 -0
- package/docs/tools/README.md +234 -0
- package/docs/tools/accessibility.md +614 -0
- package/docs/tools/color-operations.md +374 -0
- package/docs/tools/image-extraction.md +624 -0
- package/docs/tools/material-design.md +347 -0
- package/docs/tools/theme-matching.md +552 -0
- package/eslint.config.ts +14 -0
- package/examples/theme-matching.md +113 -0
- package/jsr.json +7 -0
- package/mcp-config.json +8 -0
- package/note.md +35 -0
- package/package.json +122 -0
- package/research_results.md +53 -0
- package/src/bin/coolors-mcp.ts +194 -0
- package/src/bin/server.ts +61 -0
- package/src/color/__tests__/conversions-argb.test.ts +198 -0
- package/src/color/__tests__/extract-colors.test.ts +360 -0
- package/src/color/__tests__/image-utils.test.ts +242 -0
- package/src/color/__tests__/reference-colors.test.ts +278 -0
- package/src/color/__tests__/round-trip.test.ts +197 -0
- package/src/color/conversions.test.ts +402 -0
- package/src/color/conversions.ts +393 -0
- package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
- package/src/color/dislike/dislike-analyzer.ts +114 -0
- package/src/color/extract-colors.ts +228 -0
- package/src/color/hct/__tests__/hct-class.test.ts +232 -0
- package/src/color/hct/harmonization.ts +204 -0
- package/src/color/hct/hct-class.ts +109 -0
- package/src/color/hct/hct-solver.ts +168 -0
- package/src/color/hct/index.ts +39 -0
- package/src/color/hct/tonal-palette.ts +211 -0
- package/src/color/hct/types.ts +88 -0
- package/src/color/image-utils.ts +79 -0
- package/src/color/index.ts +87 -0
- package/src/color/material-theme.ts +157 -0
- package/src/color/metrics.test.ts +276 -0
- package/src/color/metrics.ts +281 -0
- package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
- package/src/color/quantize/lab_point_provider.ts +55 -0
- package/src/color/quantize/point_provider.ts +27 -0
- package/src/color/quantize/quantizer_celebi.ts +51 -0
- package/src/color/quantize/quantizer_celebi_test.ts +71 -0
- package/src/color/quantize/quantizer_map.ts +47 -0
- package/src/color/quantize/quantizer_wsmeans.ts +232 -0
- package/src/color/quantize/quantizer_wu.ts +472 -0
- package/src/color/score/__tests__/score.test.ts +224 -0
- package/src/color/score/score.ts +175 -0
- package/src/color/types.ts +151 -0
- package/src/color/utils/color_utils.ts +292 -0
- package/src/color/utils/math_utils.ts +145 -0
- package/src/color/utils.test.ts +403 -0
- package/src/color/utils.ts +315 -0
- package/src/constants.ts +5 -0
- package/src/coolors-mcp.ts +37 -0
- package/src/examples/addition.ts +333 -0
- package/src/examples/color-demo.ts +125 -0
- package/src/examples/custom-logger.ts +201 -0
- package/src/examples/oauth-server.ts +113 -0
- package/src/examples/session-context.ts +269 -0
- package/src/session.ts +116 -0
- package/src/theme/__tests__/matcher.test.ts +180 -0
- package/src/theme/__tests__/parser.test.ts +148 -0
- package/src/theme/__tests__/refactor.test.ts +224 -0
- package/src/theme/index.ts +34 -0
- package/src/theme/matcher.ts +395 -0
- package/src/theme/parser.ts +392 -0
- package/src/theme/refactor.ts +360 -0
- package/src/theme/types.ts +152 -0
- package/src/tools/__tests__/gradient-generator.test.ts +206 -0
- package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
- package/src/tools/color-conversion.tool.ts +54 -0
- package/src/tools/color-distance.tool.ts +41 -0
- package/src/tools/colors.ts +31 -0
- package/src/tools/contrast-checker.tool.ts +37 -0
- package/src/tools/dislike-analyzer.tool.ts +247 -0
- package/src/tools/gradient-generator.tool.ts +250 -0
- package/src/tools/image-extraction.tools.ts +289 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/material-theme.tools.ts +250 -0
- package/src/tools/palette-generator.tool.ts +135 -0
- package/src/tools/palette-with-locks.tool.ts +221 -0
- package/src/tools/registry.ts +142 -0
- package/src/tools/simple-tools.ts +37 -0
- package/src/tools/theme-matching.tools.ts +334 -0
- package/src/types.ts +182 -0
- package/src/utils.ts +22 -0
- package/tsconfig.json +8 -0
- package/vitest.config.js +15 -0
package/docs/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Coolors MCP Documentation
|
|
2
|
+
|
|
3
|
+
Coolors MCP is a Model Context Protocol server for color operations, Material Design theme generation, and CSS theme matching.
|
|
4
|
+
|
|
5
|
+
## Quick Navigation
|
|
6
|
+
|
|
7
|
+
### Getting Started
|
|
8
|
+
|
|
9
|
+
- [Installation & Setup](../README.md#installation)
|
|
10
|
+
- [Configuration](../README.md#configure-with-claude-desktop)
|
|
11
|
+
- [Quick Start Guide](../README.md#quick-start)
|
|
12
|
+
|
|
13
|
+
### Tools Reference
|
|
14
|
+
|
|
15
|
+
- [Tools Overview](./tools/README.md) - Complete list of available tools
|
|
16
|
+
- [Color Operations](./tools/color-operations.md) - Conversion, distance, contrast
|
|
17
|
+
- [Material Design](./tools/material-design.md) - Theme generation, tonal palettes
|
|
18
|
+
- [Theme Matching](./tools/theme-matching.md) - CSS refactoring, variable matching
|
|
19
|
+
- [Image Extraction](./tools/image-extraction.md) - Extract colors from images
|
|
20
|
+
- [Color Psychology](./tools/color-psychology.md) - Dislike analysis and fixes
|
|
21
|
+
|
|
22
|
+
### API Documentation
|
|
23
|
+
|
|
24
|
+
- [API Reference](./api/README.md) - Complete API documentation
|
|
25
|
+
- [TypeScript Types](./api/types.md) - Type definitions and interfaces
|
|
26
|
+
|
|
27
|
+
### Concepts
|
|
28
|
+
|
|
29
|
+
- [HCT Color Space](./concepts/hct-color-space.md) - Understanding HCT vs traditional color spaces
|
|
30
|
+
- [Distance Metrics](./concepts/distance-metrics.md) - Color difference calculations
|
|
31
|
+
- [Material Design 3](./concepts/material-design-3.md) - Core concepts and principles
|
|
32
|
+
- [Theme Matching Algorithm](./concepts/theme-matching-algorithm.md) - How matching works
|
|
33
|
+
|
|
34
|
+
### Examples & Guides
|
|
35
|
+
|
|
36
|
+
- [Usage Examples](./examples/README.md) - Practical code examples
|
|
37
|
+
- [Design System Migration](./examples/design-system-migration.md) - Converting legacy CSS
|
|
38
|
+
- [Accessibility Compliance](./examples/accessibility-compliance.md) - WCAG guidelines
|
|
39
|
+
- [Dynamic Theming](./examples/dynamic-theming.md) - Image-based themes
|
|
40
|
+
- [Color Harmonization](./examples/color-harmonization.md) - Creating palettes
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
### Color Space Support
|
|
45
|
+
|
|
46
|
+
- RGB, HSL, HSV color models
|
|
47
|
+
- LAB, XYZ perceptual spaces
|
|
48
|
+
- HCT (Hue, Chroma, Tone) - Google's color space for UI
|
|
49
|
+
|
|
50
|
+
### Distance Metrics
|
|
51
|
+
|
|
52
|
+
- Delta E 76 - Basic perceptual difference
|
|
53
|
+
- Delta E 94 - Improved for graphics arts
|
|
54
|
+
- Delta E 2000 - Most accurate perceptual metric
|
|
55
|
+
- HCT distance - Optimized for UI components
|
|
56
|
+
|
|
57
|
+
### Material Design Integration
|
|
58
|
+
|
|
59
|
+
- Complete Material Design 3 theme generation
|
|
60
|
+
- Tonal palette creation (0-100 tones)
|
|
61
|
+
- Color harmonization algorithms
|
|
62
|
+
- Light and dark theme variants
|
|
63
|
+
|
|
64
|
+
### CSS Theme Tools
|
|
65
|
+
|
|
66
|
+
- Extract theme variables from CSS
|
|
67
|
+
- Match colors to closest theme variables
|
|
68
|
+
- Automated CSS refactoring
|
|
69
|
+
- Context-aware matching (text, background, borders)
|
|
70
|
+
|
|
71
|
+
### Image Processing
|
|
72
|
+
|
|
73
|
+
- Extract dominant colors from images
|
|
74
|
+
- Generate themes from photographs
|
|
75
|
+
- Quantization using Celebi algorithm
|
|
76
|
+
- Color scoring for UI suitability
|
|
77
|
+
|
|
78
|
+
### Color Psychology
|
|
79
|
+
|
|
80
|
+
- Detect universally disliked colors
|
|
81
|
+
- Automatic color correction
|
|
82
|
+
- Batch processing for palettes
|
|
83
|
+
- Based on research into color perception
|
|
84
|
+
|
|
85
|
+
## Tool Categories
|
|
86
|
+
|
|
87
|
+
### 1. Basic Color Operations
|
|
88
|
+
|
|
89
|
+
Tools for fundamental color manipulation:
|
|
90
|
+
|
|
91
|
+
- `convert_color` - Format conversion
|
|
92
|
+
- `color_distance` - Perceptual difference
|
|
93
|
+
- `check_contrast` - WCAG compliance
|
|
94
|
+
- `generate_palette` - Create color schemes
|
|
95
|
+
|
|
96
|
+
### 2. Material Design Tools
|
|
97
|
+
|
|
98
|
+
Google's Material Design 3 implementation:
|
|
99
|
+
|
|
100
|
+
- `generate_material_theme` - Complete theme from source color
|
|
101
|
+
- `generate_tonal_palette` - Create tonal variations
|
|
102
|
+
- `harmonize_colors` - Blend colors together
|
|
103
|
+
|
|
104
|
+
### 3. Theme Matching & Refactoring
|
|
105
|
+
|
|
106
|
+
CSS and design system integration:
|
|
107
|
+
|
|
108
|
+
- `match_theme_color` - Find closest variable
|
|
109
|
+
- `refactor_css_with_theme` - Automate CSS updates
|
|
110
|
+
- `match_theme_colors_batch` - Process multiple colors
|
|
111
|
+
- `generate_theme_css` - Create CSS custom properties
|
|
112
|
+
|
|
113
|
+
### 4. Image-Based Tools
|
|
114
|
+
|
|
115
|
+
Extract and generate from images:
|
|
116
|
+
|
|
117
|
+
- `extract_image_colors` - Get dominant colors
|
|
118
|
+
- `generate_theme_from_image` - Create theme from photo
|
|
119
|
+
|
|
120
|
+
### 5. Color Analysis
|
|
121
|
+
|
|
122
|
+
Psychology and perception:
|
|
123
|
+
|
|
124
|
+
- `analyze_color_likability` - Check for problematic colors
|
|
125
|
+
- `fix_disliked_colors_batch` - Correct multiple colors
|
|
126
|
+
|
|
127
|
+
## Use Cases
|
|
128
|
+
|
|
129
|
+
### Design Systems
|
|
130
|
+
|
|
131
|
+
- Migrate from hardcoded colors to CSS variables
|
|
132
|
+
- Ensure consistency across components
|
|
133
|
+
- Generate comprehensive color palettes
|
|
134
|
+
- Maintain brand guidelines
|
|
135
|
+
|
|
136
|
+
### Accessibility
|
|
137
|
+
|
|
138
|
+
- Verify WCAG AA/AAA compliance
|
|
139
|
+
- Find accessible color combinations
|
|
140
|
+
- Fix contrast issues automatically
|
|
141
|
+
- Test entire themes for compliance
|
|
142
|
+
|
|
143
|
+
### Dynamic Theming
|
|
144
|
+
|
|
145
|
+
- Generate themes from user images
|
|
146
|
+
- Create seasonal color schemes
|
|
147
|
+
- Match brand colors automatically
|
|
148
|
+
- Adapt to content dynamically
|
|
149
|
+
|
|
150
|
+
### Development Workflow
|
|
151
|
+
|
|
152
|
+
- Refactor legacy CSS efficiently
|
|
153
|
+
- Validate color choices during development
|
|
154
|
+
- Generate Material Design themes
|
|
155
|
+
- Ensure psychological appropriateness
|
|
156
|
+
|
|
157
|
+
## Architecture Overview
|
|
158
|
+
|
|
159
|
+
The server is organized into logical modules:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/
|
|
163
|
+
├── tools/ # MCP tool implementations
|
|
164
|
+
├── color/ # Core color algorithms
|
|
165
|
+
│ ├── conversions # Color space conversions
|
|
166
|
+
│ ├── metrics # Distance calculations
|
|
167
|
+
│ ├── hct/ # HCT color space
|
|
168
|
+
│ └── quantize/ # Image quantization
|
|
169
|
+
├── theme/ # Theme matching system
|
|
170
|
+
│ ├── parser # CSS parsing
|
|
171
|
+
│ ├── matcher # Matching algorithm
|
|
172
|
+
│ └── refactor # CSS transformation
|
|
173
|
+
└── bin/ # Server entry point
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Performance Considerations
|
|
177
|
+
|
|
178
|
+
- Color conversions are cached for repeated operations
|
|
179
|
+
- Batch operations available for processing multiple colors
|
|
180
|
+
- Quantization quality settings for image processing
|
|
181
|
+
- Confidence thresholds for theme matching
|
|
182
|
+
|
|
183
|
+
## Error Handling
|
|
184
|
+
|
|
185
|
+
All tools include error handling for:
|
|
186
|
+
|
|
187
|
+
- Invalid color formats
|
|
188
|
+
- Out-of-range values
|
|
189
|
+
- Missing required parameters
|
|
190
|
+
- Malformed CSS input
|
|
191
|
+
|
|
192
|
+
## Contributing
|
|
193
|
+
|
|
194
|
+
See the main [README](../README.md#contributing) for contribution guidelines.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT License - See [LICENSE](../LICENSE) file for details.
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
Coolors MCP ensures color choices meet accessibility standards through comprehensive contrast checking and automatic adjustments based on WCAG guidelines.
|
|
4
|
+
|
|
5
|
+
## Contrast Standards
|
|
6
|
+
|
|
7
|
+
### WCAG Overview
|
|
8
|
+
|
|
9
|
+
The Web Content Accessibility Guidelines (WCAG) define contrast requirements for readable content:
|
|
10
|
+
|
|
11
|
+
| Level | Normal Text | Large Text | Non-Text |
|
|
12
|
+
|-------|------------|------------|----------|
|
|
13
|
+
| **AA** | 4.5:1 | 3:1 | 3:1 |
|
|
14
|
+
| **AAA** | 7:1 | 4.5:1 | N/A |
|
|
15
|
+
|
|
16
|
+
**Large text** is defined as:
|
|
17
|
+
- 18pt (24px) or larger
|
|
18
|
+
- 14pt (18.5px) or larger if bold
|
|
19
|
+
|
|
20
|
+
### Contrast Ratio Calculation
|
|
21
|
+
|
|
22
|
+
Contrast ratio is based on relative luminance:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// Relative luminance calculation
|
|
26
|
+
function getLuminance(rgb) {
|
|
27
|
+
const [r, g, b] = rgb.map(val => {
|
|
28
|
+
val = val / 255;
|
|
29
|
+
return val <= 0.03928
|
|
30
|
+
? val / 12.92
|
|
31
|
+
: Math.pow((val + 0.055) / 1.055, 2.4);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Contrast ratio
|
|
38
|
+
function getContrastRatio(color1, color2) {
|
|
39
|
+
const l1 = getLuminance(color1);
|
|
40
|
+
const l2 = getLuminance(color2);
|
|
41
|
+
|
|
42
|
+
const lighter = Math.max(l1, l2);
|
|
43
|
+
const darker = Math.min(l1, l2);
|
|
44
|
+
|
|
45
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Understanding Contrast Ratios
|
|
50
|
+
|
|
51
|
+
- **1:1** - No contrast (same color)
|
|
52
|
+
- **4.5:1** - Minimum for normal text (AA)
|
|
53
|
+
- **7:1** - Enhanced contrast (AAA)
|
|
54
|
+
- **21:1** - Maximum (black on white)
|
|
55
|
+
|
|
56
|
+
## HCT and Accessibility
|
|
57
|
+
|
|
58
|
+
### Tone-Based Contrast
|
|
59
|
+
|
|
60
|
+
HCT's tone component directly correlates with contrast:
|
|
61
|
+
|
|
62
|
+
| Tone Difference | Approximate Contrast | Use Case |
|
|
63
|
+
|-----------------|---------------------|----------|
|
|
64
|
+
| 40 | ~3:1 | Large text, UI elements |
|
|
65
|
+
| 50 | ~4.5:1 | Normal text (AA) |
|
|
66
|
+
| 70 | ~7:1 | Enhanced (AAA) |
|
|
67
|
+
| 90 | ~15:1 | Maximum readability |
|
|
68
|
+
|
|
69
|
+
### Predictable Relationships
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// Guaranteed accessible pairs in HCT
|
|
73
|
+
const background = { h: 200, c: 20, t: 95 }; // Light surface
|
|
74
|
+
const text = { h: 200, c: 20, t: 20 }; // Dark text
|
|
75
|
+
// Tone difference: 75 = guaranteed 7:1+ contrast
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Checking Contrast
|
|
79
|
+
|
|
80
|
+
### Basic Contrast Check
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
{
|
|
84
|
+
"name": "check_contrast",
|
|
85
|
+
"arguments": {
|
|
86
|
+
"foreground": "#1f2937",
|
|
87
|
+
"background": "#ffffff"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Returns
|
|
92
|
+
{
|
|
93
|
+
"ratio": 15.74,
|
|
94
|
+
"passes": {
|
|
95
|
+
"AA": { "normal": true, "large": true },
|
|
96
|
+
"AAA": { "normal": true, "large": true }
|
|
97
|
+
},
|
|
98
|
+
"recommendation": "Excellent contrast for all uses"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Context-Aware Checking
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// Check with specific requirements
|
|
106
|
+
{
|
|
107
|
+
"name": "check_contrast",
|
|
108
|
+
"arguments": {
|
|
109
|
+
"foreground": "#6366f1",
|
|
110
|
+
"background": "#f3f4f6",
|
|
111
|
+
"fontSize": 14,
|
|
112
|
+
"fontWeight": 400
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Material Design Contrast Levels
|
|
118
|
+
|
|
119
|
+
### Default Contrast (0.0)
|
|
120
|
+
Meets AA accessibility:
|
|
121
|
+
- **Guaranteed minimums**:
|
|
122
|
+
- 4.5:1 for all text and icons
|
|
123
|
+
- 3:1 for required non-text elements
|
|
124
|
+
- **Targets**:
|
|
125
|
+
- 7:1 for high emphasis text
|
|
126
|
+
|
|
127
|
+
### Medium Contrast (+0.5)
|
|
128
|
+
Exceeds AA requirements:
|
|
129
|
+
- **Guaranteed minimums**:
|
|
130
|
+
- 4.5:1 for all text
|
|
131
|
+
- 3:1 for all non-text elements
|
|
132
|
+
- **Targets**:
|
|
133
|
+
- 7:1 for normal text
|
|
134
|
+
- 11:1 for high emphasis
|
|
135
|
+
|
|
136
|
+
### High Contrast (+1.0)
|
|
137
|
+
Meets AAA accessibility:
|
|
138
|
+
- **Guaranteed minimums**:
|
|
139
|
+
- 7:1 for all text and icons
|
|
140
|
+
- 4.5:1 for all non-text elements
|
|
141
|
+
- **Targets**:
|
|
142
|
+
- 11:1 for normal text
|
|
143
|
+
- 21:1 for high emphasis
|
|
144
|
+
|
|
145
|
+
## Automatic Adjustments
|
|
146
|
+
|
|
147
|
+
### Tone Adjustment Algorithm
|
|
148
|
+
|
|
149
|
+
When colors don't meet contrast requirements:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
function adjustForContrast(foreground, background, targetRatio) {
|
|
153
|
+
const currentRatio = getContrastRatio(foreground, background);
|
|
154
|
+
|
|
155
|
+
if (currentRatio >= targetRatio) {
|
|
156
|
+
return foreground; // Already accessible
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Convert to HCT for tone adjustment
|
|
160
|
+
const fgHct = toHct(foreground);
|
|
161
|
+
const bgHct = toHct(background);
|
|
162
|
+
|
|
163
|
+
// Determine adjustment direction
|
|
164
|
+
const lighten = bgHct.tone < 50;
|
|
165
|
+
|
|
166
|
+
// Binary search for optimal tone
|
|
167
|
+
let low = lighten ? bgHct.tone : 0;
|
|
168
|
+
let high = lighten ? 100 : bgHct.tone;
|
|
169
|
+
|
|
170
|
+
while (high - low > 1) {
|
|
171
|
+
const mid = (low + high) / 2;
|
|
172
|
+
const testColor = { ...fgHct, tone: mid };
|
|
173
|
+
const ratio = getContrastRatio(toRgb(testColor), background);
|
|
174
|
+
|
|
175
|
+
if (ratio >= targetRatio) {
|
|
176
|
+
if (lighten) high = mid;
|
|
177
|
+
else low = mid;
|
|
178
|
+
} else {
|
|
179
|
+
if (lighten) low = mid;
|
|
180
|
+
else high = mid;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return toRgb({ ...fgHct, tone: lighten ? high : low });
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Contrast Libraries
|
|
189
|
+
|
|
190
|
+
Pre-calculated adjustments for common scenarios:
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
// Ensure text is readable on any background
|
|
194
|
+
function getAccessibleTextColor(background) {
|
|
195
|
+
const bgLuminance = getLuminance(background);
|
|
196
|
+
|
|
197
|
+
// Use white or black based on background
|
|
198
|
+
if (bgLuminance > 0.5) {
|
|
199
|
+
return '#000000'; // Dark text on light background
|
|
200
|
+
} else {
|
|
201
|
+
return '#ffffff'; // Light text on dark background
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Color Roles and Accessibility
|
|
207
|
+
|
|
208
|
+
### Guaranteed Accessible Pairs
|
|
209
|
+
|
|
210
|
+
Material Design ensures these pairs always meet contrast requirements:
|
|
211
|
+
|
|
212
|
+
| Background | Foreground | Min Contrast | Use Case |
|
|
213
|
+
|------------|------------|--------------|----------|
|
|
214
|
+
| surface | onSurface | 4.5:1 | Main content |
|
|
215
|
+
| primary | onPrimary | 4.5:1 | Primary actions |
|
|
216
|
+
| primaryContainer | onPrimaryContainer | 4.5:1 | Containers |
|
|
217
|
+
| error | onError | 4.5:1 | Error states |
|
|
218
|
+
|
|
219
|
+
### Role-Based Adjustments
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
// Automatic role assignment with contrast guarantee
|
|
223
|
+
function assignColorRoles(sourceColor, contrastLevel = 0) {
|
|
224
|
+
const palette = generateTonalPalette(sourceColor);
|
|
225
|
+
|
|
226
|
+
// Adjust tone mappings based on contrast level
|
|
227
|
+
const toneMap = {
|
|
228
|
+
primary: 40 - (contrastLevel * 10),
|
|
229
|
+
onPrimary: 100,
|
|
230
|
+
primaryContainer: 90 + (contrastLevel * 5),
|
|
231
|
+
onPrimaryContainer: 10 - (contrastLevel * 5)
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Ensure minimum contrast
|
|
235
|
+
return Object.entries(toneMap).map(([role, tone]) => ({
|
|
236
|
+
role,
|
|
237
|
+
color: palette[tone],
|
|
238
|
+
meetsContrast: true
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Best Practices
|
|
244
|
+
|
|
245
|
+
### Design Guidelines
|
|
246
|
+
|
|
247
|
+
#### DO's
|
|
248
|
+
- ✅ Test all color combinations for contrast
|
|
249
|
+
- ✅ Provide high contrast mode options
|
|
250
|
+
- ✅ Use semantic color roles consistently
|
|
251
|
+
- ✅ Consider different types of color blindness
|
|
252
|
+
- ✅ Test with real content, not just color swatches
|
|
253
|
+
|
|
254
|
+
#### DON'Ts
|
|
255
|
+
- ❌ Rely on color alone to convey information
|
|
256
|
+
- ❌ Use low contrast for aesthetic reasons
|
|
257
|
+
- ❌ Assume large text allows poor contrast
|
|
258
|
+
- ❌ Ignore user preference for high contrast
|
|
259
|
+
- ❌ Use pure black on pure white (too harsh)
|
|
260
|
+
|
|
261
|
+
### Testing Strategies
|
|
262
|
+
|
|
263
|
+
#### Automated Testing
|
|
264
|
+
```javascript
|
|
265
|
+
// Test all color combinations in theme
|
|
266
|
+
function testThemeAccessibility(theme) {
|
|
267
|
+
const results = [];
|
|
268
|
+
|
|
269
|
+
// Test each foreground/background pair
|
|
270
|
+
theme.pairs.forEach(({ fg, bg, context }) => {
|
|
271
|
+
const ratio = getContrastRatio(fg, bg);
|
|
272
|
+
const required = getRequiredRatio(context);
|
|
273
|
+
|
|
274
|
+
results.push({
|
|
275
|
+
pair: `${fg} on ${bg}`,
|
|
276
|
+
context,
|
|
277
|
+
ratio,
|
|
278
|
+
passes: ratio >= required,
|
|
279
|
+
recommendation: getRecommendation(ratio, context)
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### Manual Testing
|
|
288
|
+
1. **Blur test**: Blur your vision - can you still read it?
|
|
289
|
+
2. **Grayscale test**: Convert to grayscale - still distinguishable?
|
|
290
|
+
3. **Sunlight test**: View in bright light conditions
|
|
291
|
+
4. **Distance test**: Step back from screen
|
|
292
|
+
5. **Squint test**: Squint eyes - text still readable?
|
|
293
|
+
|
|
294
|
+
## Special Considerations
|
|
295
|
+
|
|
296
|
+
### Color Blindness
|
|
297
|
+
|
|
298
|
+
Approximately 8% of men and 0.5% of women have color vision deficiency:
|
|
299
|
+
|
|
300
|
+
| Type | Frequency | Description | Design Impact |
|
|
301
|
+
|------|-----------|-------------|---------------|
|
|
302
|
+
| Protanopia | 1.3% | No red cones | Red/green confusion |
|
|
303
|
+
| Deuteranopia | 1.2% | No green cones | Red/green confusion |
|
|
304
|
+
| Tritanopia | 0.001% | No blue cones | Blue/yellow confusion |
|
|
305
|
+
| Monochromacy | Rare | No color vision | Rely on contrast only |
|
|
306
|
+
|
|
307
|
+
#### Design Strategies
|
|
308
|
+
```javascript
|
|
309
|
+
// Ensure information isn't conveyed by color alone
|
|
310
|
+
✓ Error: "❌ Invalid input" (icon + text)
|
|
311
|
+
✗ Error: Red border only
|
|
312
|
+
|
|
313
|
+
// Use patterns or icons
|
|
314
|
+
✓ Status: "🟢 Active" / "🔴 Inactive"
|
|
315
|
+
✗ Status: Green/Red backgrounds only
|
|
316
|
+
|
|
317
|
+
// Provide sufficient contrast
|
|
318
|
+
✓ Different tones for different states
|
|
319
|
+
✗ Same tone, different hues only
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Dark Mode Considerations
|
|
323
|
+
|
|
324
|
+
Dark themes require special attention:
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
// Light theme
|
|
328
|
+
const lightTheme = {
|
|
329
|
+
surface: { tone: 99 }, // Very light
|
|
330
|
+
onSurface: { tone: 10 }, // Very dark
|
|
331
|
+
// Contrast: ~15:1
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Dark theme - NOT just inverted
|
|
335
|
+
const darkTheme = {
|
|
336
|
+
surface: { tone: 10 }, // Very dark
|
|
337
|
+
onSurface: { tone: 90 }, // Light, not white
|
|
338
|
+
// Contrast: ~13:1 (slightly less harsh)
|
|
339
|
+
};
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Transparency and Overlays
|
|
343
|
+
|
|
344
|
+
Colors with transparency need special handling:
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
function getEffectiveColor(foreground, background, alpha) {
|
|
348
|
+
// Composite the colors
|
|
349
|
+
const composite = blendColors(foreground, background, alpha);
|
|
350
|
+
|
|
351
|
+
// Check contrast of composite
|
|
352
|
+
return getContrastRatio(composite, background);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Ensure minimum opacity for text
|
|
356
|
+
const minOpacity = {
|
|
357
|
+
normalText: 0.87, // ~87% opacity minimum
|
|
358
|
+
secondaryText: 0.60, // ~60% for secondary
|
|
359
|
+
disabledText: 0.38 // ~38% for disabled
|
|
360
|
+
};
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Implementation Examples
|
|
364
|
+
|
|
365
|
+
### React Component
|
|
366
|
+
|
|
367
|
+
```jsx
|
|
368
|
+
function AccessibleButton({ color, children }) {
|
|
369
|
+
const [textColor, setTextColor] = useState('#ffffff');
|
|
370
|
+
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
// Automatically choose accessible text color
|
|
373
|
+
const accessible = getAccessibleTextColor(color);
|
|
374
|
+
setTextColor(accessible);
|
|
375
|
+
}, [color]);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<button
|
|
379
|
+
style={{
|
|
380
|
+
backgroundColor: color,
|
|
381
|
+
color: textColor
|
|
382
|
+
}}
|
|
383
|
+
aria-label={children}
|
|
384
|
+
>
|
|
385
|
+
{children}
|
|
386
|
+
</button>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### CSS Custom Properties
|
|
392
|
+
|
|
393
|
+
```css
|
|
394
|
+
:root {
|
|
395
|
+
/* Define with guaranteed contrast */
|
|
396
|
+
--color-background: hsl(0, 0%, 98%);
|
|
397
|
+
--color-text: hsl(0, 0%, 13%);
|
|
398
|
+
/* Contrast ratio: 15.3:1 ✓ */
|
|
399
|
+
|
|
400
|
+
--color-primary: hsl(239, 84%, 67%);
|
|
401
|
+
--color-on-primary: hsl(0, 0%, 100%);
|
|
402
|
+
/* Contrast ratio: 4.6:1 ✓ */
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* High contrast mode */
|
|
406
|
+
@media (prefers-contrast: high) {
|
|
407
|
+
:root {
|
|
408
|
+
--color-background: hsl(0, 0%, 100%);
|
|
409
|
+
--color-text: hsl(0, 0%, 0%);
|
|
410
|
+
/* Contrast ratio: 21:1 ✓ */
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Validation Tool
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
class AccessibilityValidator {
|
|
419
|
+
constructor(minRatio = 4.5) {
|
|
420
|
+
this.minRatio = minRatio;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
validate(theme) {
|
|
424
|
+
const issues = [];
|
|
425
|
+
|
|
426
|
+
Object.entries(theme).forEach(([role, color]) => {
|
|
427
|
+
const background = this.getBackground(role, theme);
|
|
428
|
+
const ratio = getContrastRatio(color, background);
|
|
429
|
+
|
|
430
|
+
if (ratio < this.minRatio) {
|
|
431
|
+
issues.push({
|
|
432
|
+
role,
|
|
433
|
+
color,
|
|
434
|
+
background,
|
|
435
|
+
ratio,
|
|
436
|
+
required: this.minRatio,
|
|
437
|
+
suggestion: this.suggestFix(color, background)
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
valid: issues.length === 0,
|
|
444
|
+
issues
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
suggestFix(fg, bg) {
|
|
449
|
+
return adjustForContrast(fg, bg, this.minRatio);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Resources
|
|
455
|
+
|
|
456
|
+
### Testing Tools
|
|
457
|
+
- Chrome DevTools (Lighthouse)
|
|
458
|
+
- Firefox Accessibility Inspector
|
|
459
|
+
- axe DevTools
|
|
460
|
+
- WAVE (WebAIM)
|
|
461
|
+
- Stark (Figma/Sketch plugin)
|
|
462
|
+
|
|
463
|
+
### Guidelines
|
|
464
|
+
- [WCAG 2.1](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
465
|
+
- [Material Design Accessibility](https://m3.material.io/foundations/accessible-design)
|
|
466
|
+
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
|
467
|
+
|
|
468
|
+
## See Also
|
|
469
|
+
|
|
470
|
+
- [HCT System](./hct.md) - Tone-based contrast
|
|
471
|
+
- [Material Design](./material-design.md) - Contrast levels
|
|
472
|
+
- [Theme Matching](./theme-matching.md) - Accessibility scoring
|
|
473
|
+
- [Color Spaces](./color-spaces.md) - Understanding luminance
|