Contrast Checker (Angular)

Use the InputColor control to select colors and the Color class to inspect the colors and check if they provide enough contrast to comply with accessibility standards.

Learn about Input Controls | InputColor API Reference

This example uses Angular.

app.component.ts
index.html
app.component.html
styles.css
Copy to CodeMine
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, enableProdMode, Inject, ViewChild, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { WjInputModule } from '@mescius/wijmo.angular2.input'; import { Color, Globalize } from '@mescius/wijmo'; // @Component({ standalone: true, imports: [WjInputModule, FormsModule, BrowserModule], selector: 'app-component', templateUrl: 'src/app.component.html', }) export class AppComponent { fore = 'black'; back = 'white'; ratio = this.getRatio(this.fore, this.back); getRatio(fore: string, back: string): number { let lFore = this.getRelativeLuminance(fore); let lBack = this.getRelativeLuminance(back); return (Math.max(lFore, lBack) + 0.05) / (Math.min(lFore, lBack) + 0.05); } colorChanged(sender: any) { this.fore = sender.value; this.ratio = this.getRatio(this.fore, this.back); } backChanged(sender: any) { this.back = sender.value; this.ratio = this.getRatio(this.fore, this.back); } format(value: any, format: string): string { return Globalize.format(value, format); } // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance getRelativeLuminance(color: string): number { let c = new Color(color); let r = this.getChannel(c.r); let g = this.getChannel(c.g); let b = this.getChannel(c.b); return r * 0.2126 + g * 0.7152 + b * 0.0722; } getChannel(rgb: number): number { rgb /= 255; return rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4); } } // // enableProdMode(); // Resolve resources (templateUrl, styleUrls etc), After resolution all URLs have been converted into `template` strings. ɵresolveComponentResources(fetch).then(() => { // Bootstrap application bootstrapApplication(AppComponent).catch((err) => console.error(err)); });
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, enableProdMode, Inject, ViewChild, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { WjInputModule } from '@mescius/wijmo.angular2.input'; import { Color, Globalize } from '@mescius/wijmo'; // @Component({ standalone: true, imports: [WjInputModule, FormsModule, BrowserModule], selector: 'app-component', templateUrl: 'src/app.component.html', }) export class AppComponent { fore = 'black'; back = 'white'; ratio = this.getRatio(this.fore, this.back); getRatio(fore: string, back: string): number { let lFore = this.getRelativeLuminance(fore); let lBack = this.getRelativeLuminance(back); return (Math.max(lFore, lBack) + 0.05) / (Math.min(lFore, lBack) + 0.05); } colorChanged(sender: any) { this.fore = sender.value; this.ratio = this.getRatio(this.fore, this.back); } backChanged(sender: any) { this.back = sender.value; this.ratio = this.getRatio(this.fore, this.back); } format(value: any, format: string): string { return Globalize.format(value, format); } // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance getRelativeLuminance(color: string): number { let c = new Color(color); let r = this.getChannel(c.r); let g = this.getChannel(c.g); let b = this.getChannel(c.b); return r * 0.2126 + g * 0.7152 + b * 0.0722; } getChannel(rgb: number): number { rgb /= 255; return rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4); } } // // enableProdMode(); // Resolve resources (templateUrl, styleUrls etc), After resolution all URLs have been converted into `template` strings. ɵresolveComponentResources(fetch).then(() => { // Bootstrap application bootstrapApplication(AppComponent).catch((err) => console.error(err)); });
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo InputColor Contrast Checker</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <h1> Contrast Checker </h1> <div class="row"> <div class="col-md-4"> <h3> Foreground Color </h3> <wj-input-color #icFore [value]="fore" (valueChanged)="colorChanged(icFore)"> </wj-input-color> <h3> Background Color </h3> <wj-input-color #icBack [value]="back" (valueChanged)="backChanged(icBack)"> </wj-input-color> </div> <div class="col-md-4"> <h3> Contrast Ratio </h3> <div id="ratio" [ngStyle]="{'border-color': ratio < 7 ? 'whitesmoke' : 'darkgreen'}"> <b>{{ format(ratio, 'g1') }}</b>:1 </div> <h3> Result </h3> <p class="sample" [ngStyle]="{'color': fore, 'background': back}"> Normal Text </p> <p> WCAG AA: <span id="aa-normal" class="result" [ngClass]="{'fail': ratio < 4.5 }"> {{ ratio < 4.5 ? 'Fail' : 'Pass' }} </span> WCAG AAA: <span id="aaa-normal" class="result" [ngClass]="{'fail': ratio < 7 }"> {{ ratio < 7 ? 'Fail' : 'Pass' }} </span> </p> <p class="sample large" [ngStyle]="{'color': fore, 'background': back}"> Large Text </p> <p> WCAG AA: <span id="aa-large" class="result" [ngClass]="{'fail': ratio < 3 }"> {{ ratio < 3 ? 'Fail' : 'Pass' }} </span> WCAG AAA: <span id="aaa-large" class="result" [ngClass]="{'fail': ratio < 4.5 }"> {{ ratio < 4.5 ? 'Fail' : 'Pass' }} </span> </p> </div> </div> <h3> Explanation </h3> <p> WCAG 2.0 level AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. </p> <p> WCAG 2.1 requires a contrast ratio of at least 3:1 for graphics and user interface components (such as form input borders). </p> <p> WCAG Level AAA requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text. </p> <p> For more details, please see <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance" target="_blank"> The Science of Color Contrast </a>. </p> </div>
#sample { padding: 12px; font-style: bold; } #ratio { font-size: 200%; padding: 12px; font-style: bold; text-align: center; border-radius: 4px; border: 2px solid whitesmoke; } .sample { padding: 6px; margin: 6px 0; border: 2px solid whitesmoke; } .large { font-size: 14pt; font-weight: bold; } .result { font-weight: bold; margin-right: 2em; padding: 3px 6px; border-radius: 6px; color: white; background: darkgreen; } .result.fail { background: darkred; }
(function (global) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' }, '*.mjs': { format: 'esm' }, }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', '@angular/core': 'npm:@angular/core/fesm2022/core.mjs', '@angular/core/primitives/signals': 'npm:@angular/core/fesm2022/primitives/signals.mjs', '@angular/core/primitives/event-dispatch': 'npm:@angular/core/fesm2022/primitives/event-dispatch.mjs', '@angular/common': 'npm:@angular/common/fesm2022/common.mjs', '@angular/common/http': 'npm:@angular/common/fesm2022/http.mjs', '@angular/compiler': 'npm:@angular/compiler/fesm2022/compiler.mjs', '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2022/platform-browser.mjs', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2022/platform-browser-dynamic.mjs', '@angular/http': 'npm:@angular/http/fesm2022/http.mjs', '@angular/router': 'npm:@angular/router/fesm2022/router.mjs', '@angular/forms': 'npm:@angular/forms/fesm2022/forms.mjs', }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);