Contrast Checker (Vue)

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 Vue.

app.vue
index.html
Copy to CodeMine
<template> <div class="container-fluid"> <h1> Contrast Checker </h1> <div class="row"> <div class="col-md-4"> <h3> Foreground Color </h3> <wj-input-color :value="fore" :value-changed="foreChanged" /> <h3> Background Color </h3> <wj-input-color :value="back" :value-changed="backChanged" /> </div> <div class="col-md-4"> <h3> Contrast Ratio </h3> <div id="ratio" v-bind:style="{ borderColor: ratio < 7 ? 'whitesmoke' : 'darkgreen' }"> <b>{{ format(ratio, 'g1') }}</b>:1 </div> <h3> Result </h3> <p class="sample" v-bind:style="{ color: fore, background: back }"> Normal Text </p> <p> WCAG AA: <span id="aa-normal" class="result" v-bind:class="{ fail: ratio < 4.5 }"> {{ ratio < 4.5 ? 'Fail' : 'Pass' }} </span> WCAG AAA: <span id="aaa-normal" class="result" v-bind:class="{ fail: ratio < 7 }"> {{ ratio < 7 ? 'Fail' : 'Pass' }} </span> </p> <p class="sample large" v-bind:style="{ color: fore, background: back }"> Large Text </p> <p> WCAG AA: <span id="aa-large" class="result" v-bind:class="{ fail: ratio < 3 }"> {{ ratio < 3 ? 'Fail' : 'Pass' }} </span> WCAG AAA: <span id="aaa-large" class="result" v-bind:class="{ 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> </template> <script setup> import { Color, Globalize } from "@mescius/wijmo"; import { ref } from "vue"; const fore = ref("black"); const back = ref("white"); const ratio = ref(getRatio(fore.value, back.value)); function getRatio(fore, back) { let lFore = getRelativeLuminance(fore); let lBack = getRelativeLuminance(back); return (Math.max(lFore, lBack) + 0.05) / (Math.min(lFore, lBack) + 0.05); } function foreChanged(sender) { fore.value = sender.value; ratio.value = getRatio(fore.value, back.value); } function backChanged(sender) { back.value = sender.value; ratio.value = getRatio(fore.value, back.value); } function format(value, format) { return Globalize.format(value, format); } // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance function getRelativeLuminance(color) { let c = new Color(color); let r = getChannel(c.r); let g = getChannel(c.g); let b = getChannel(c.b); return r * 0.2126 + g * 0.7152 + b * 0.0722; } function getChannel(rgb) { rgb /= 255; return rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4); } </script> <style> #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; } </style>
<template> <div class="container-fluid"> <h1> Contrast Checker </h1> <div class="row"> <div class="col-md-4"> <h3> Foreground Color </h3> <wj-input-color :value="fore" :value-changed="foreChanged" /> <h3> Background Color </h3> <wj-input-color :value="back" :value-changed="backChanged" /> </div> <div class="col-md-4"> <h3> Contrast Ratio </h3> <div id="ratio" v-bind:style="{ borderColor: ratio < 7 ? 'whitesmoke' : 'darkgreen' }"> <b>{{ format(ratio, 'g1') }}</b>:1 </div> <h3> Result </h3> <p class="sample" v-bind:style="{ color: fore, background: back }"> Normal Text </p> <p> WCAG AA: <span id="aa-normal" class="result" v-bind:class="{ fail: ratio < 4.5 }"> {{ ratio < 4.5 ? 'Fail' : 'Pass' }} </span> WCAG AAA: <span id="aaa-normal" class="result" v-bind:class="{ fail: ratio < 7 }"> {{ ratio < 7 ? 'Fail' : 'Pass' }} </span> </p> <p class="sample large" v-bind:style="{ color: fore, background: back }"> Large Text </p> <p> WCAG AA: <span id="aa-large" class="result" v-bind:class="{ fail: ratio < 3 }"> {{ ratio < 3 ? 'Fail' : 'Pass' }} </span> WCAG AAA: <span id="aaa-large" class="result" v-bind:class="{ 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> </template> <script setup> import { Color, Globalize } from "@mescius/wijmo"; import { ref } from "vue"; const fore = ref("black"); const back = ref("white"); const ratio = ref(getRatio(fore.value, back.value)); function getRatio(fore, back) { let lFore = getRelativeLuminance(fore); let lBack = getRelativeLuminance(back); return (Math.max(lFore, lBack) + 0.05) / (Math.min(lFore, lBack) + 0.05); } function foreChanged(sender) { fore.value = sender.value; ratio.value = getRatio(fore.value, back.value); } function backChanged(sender) { back.value = sender.value; ratio.value = getRatio(fore.value, back.value); } function format(value, format) { return Globalize.format(value, format); } // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance function getRelativeLuminance(color) { let c = new Color(color); let r = getChannel(c.r); let g = getChannel(c.g); let b = getChannel(c.b); return r * 0.2126 + g * 0.7152 + b * 0.0722; } function getChannel(rgb) { rgb /= 255; return rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4); } </script> <style> #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; } </style>
<!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"> <!-- SystemJS --> <script src="compiler.js" type="module"></script> <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> System.import('./src/app.js'); </script> </head> <body> <div id="app"></div> </body> </html>
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, meta: { '*.css': { loader: 'css' }, '*.vue': { loader: '../plugin-vue/index.js' } //'*.vue': { loader: 'systemjs-plugin-vue' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { '@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.vue2.chart.analytics": "npm:@mescius/wijmo.vue2.chart.analytics/index.js", "@mescius/wijmo.vue2.chart.animation": "npm:@mescius/wijmo.vue2.chart.animation/index.js", "@mescius/wijmo.vue2.chart.annotation": "npm:@mescius/wijmo.vue2.chart.annotation/index.js", "@mescius/wijmo.vue2.chart.finance.analytics": "npm:@mescius/wijmo.vue2.chart.finance.analytics/index.js", "@mescius/wijmo.vue2.chart.finance": "npm:@mescius/wijmo.vue2.chart.finance/index.js", "@mescius/wijmo.vue2.chart.hierarchical": "npm:@mescius/wijmo.vue2.chart.hierarchical/index.js", "@mescius/wijmo.vue2.chart.interaction": "npm:@mescius/wijmo.vue2.chart.interaction/index.js", "@mescius/wijmo.vue2.chart.radar": "npm:@mescius/wijmo.vue2.chart.radar/index.js", '@mescius/wijmo.vue2.chart.map': 'npm:@mescius/wijmo.vue2.chart.map/index.js', "@mescius/wijmo.vue2.chart": "npm:@mescius/wijmo.vue2.chart/index.js", "@mescius/wijmo.vue2.core": "npm:@mescius/wijmo.vue2.core/index.js", "@mescius/wijmo.vue2.gauge": "npm:@mescius/wijmo.vue2.gauge/index.js", "@mescius/wijmo.vue2.grid.detail": "npm:@mescius/wijmo.vue2.grid.detail/index.js", "@mescius/wijmo.vue2.grid.filter": "npm:@mescius/wijmo.vue2.grid.filter/index.js", "@mescius/wijmo.vue2.grid.grouppanel": "npm:@mescius/wijmo.vue2.grid.grouppanel/index.js", '@mescius/wijmo.vue2.grid.search': 'npm:@mescius/wijmo.vue2.grid.search/index.js', "@mescius/wijmo.vue2.grid.multirow": "npm:@mescius/wijmo.vue2.grid.multirow/index.js", "@mescius/wijmo.vue2.grid.sheet": "npm:@mescius/wijmo.vue2.grid.sheet/index.js", '@mescius/wijmo.vue2.grid.transposed': 'npm:@mescius/wijmo.vue2.grid.transposed/index.js', '@mescius/wijmo.vue2.grid.transposedmultirow': 'npm:@mescius/wijmo.vue2.grid.transposedmultirow/index.js', "@mescius/wijmo.vue2.grid": "npm:@mescius/wijmo.vue2.grid/index.js", "@mescius/wijmo.vue2.input": "npm:@mescius/wijmo.vue2.input/index.js", "@mescius/wijmo.vue2.olap": "npm:@mescius/wijmo.vue2.olap/index.js", "@mescius/wijmo.vue2.viewer": "npm:@mescius/wijmo.vue2.viewer/index.js", "@mescius/wijmo.vue2.nav": "npm:@mescius/wijmo.vue2.nav/index.js", "@mescius/wijmo.vue2.base": "npm:@mescius/wijmo.vue2.base/index.js", '@mescius/wijmo.vue2.barcode.common': 'npm:@mescius/wijmo.vue2.barcode.common/index.js', '@mescius/wijmo.vue2.barcode.composite': 'npm:@mescius/wijmo.vue2.barcode.composite/index.js', '@mescius/wijmo.vue2.barcode.specialized': 'npm:@mescius/wijmo.vue2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'npm:jszip/dist/jszip.js', 'css': 'npm:systemjs-plugin-css/css.js', 'vue': 'npm:vue/dist/vue.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', '@vue/compiler-dom':'npm:@vue/compiler-dom/dist/compiler-dom.global.prod.js', '@vue/runtime-dom':'npm:@vue/runtime-dom/dist/runtime-dom.global.prod.js', '@vue/shared':'npm:@vue/shared/dist/shared.cjs.js', 'vue-loader': 'npm:systemjs-vue-browser/index.js', //'systemjs-plugin-vue': 'npm:systemjs-plugin-vue/index.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js' }, rxjs: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);