Contrast Checker (React)

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

app.jsx
index.html
app.css
Copy to CodeMine
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import ReactDOM from 'react-dom/client'; import React, { useState } from 'react'; import useEvent from 'react-use-event-hook'; import { InputColor } from '@mescius/wijmo.react.input'; import { Color, Globalize } from '@mescius/wijmo'; import './app.css'; const getChannel = (rgb) => { rgb /= 255; return rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4); }; // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance const 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; }; const 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 App() { const [fore, setFore] = useState('black'); const [back, setBack] = useState('white'); const [ratio, setRatio] = useState(getRatio(fore, back)); const handleForeChange = useEvent((s) => { setFore(s.value); setRatio(getRatio(s.value, back)); }); const handleBackChange = useEvent((s) => { setBack(s.value); setRatio(getRatio(fore, s.value)); }); return (<div className="container-fluid"> <h1>Contrast Checker</h1> <div className="row"> <div className="col-md-4"> <h3>Foreground Color</h3> <InputColor value={fore} valueChanged={handleForeChange}/> <h3>Background Color</h3> <InputColor value={back} valueChanged={handleBackChange}/> </div> <div className="col-md-4"> <h3>Contrast Ratio</h3> <div id="ratio" style={{ 'borderColor': ratio < 7 ? 'whitesmoke' : 'darkgreen' }}> <b>{Globalize.format(ratio, 'g1')}</b>:1 </div> <h3>Result</h3> <p className="sample" style={{ 'color': fore, 'background': back }}> Normal Text </p> <p> WCAG AA:&nbsp; <span id="aa-normal" className={'result' + (ratio < 4.5 ? ' fail' : '')}> {ratio < 4.5 ? 'Fail' : 'Pass'} </span> WCAG AAA:&nbsp; <span id="aaa-normal" className={'result' + (ratio < 7 ? ' fail' : '')}> {ratio < 7 ? 'Fail' : 'Pass'} </span> </p> <p className="sample large" style={{ 'color': fore, 'background': back }}> Large Text </p> <p> WCAG AA:&nbsp; <span id="aa-large" className={'result' + (ratio < 3 ? ' fail' : '')}> {ratio < 3 ? 'Fail' : 'Pass'} </span> WCAG AAA:&nbsp; <span id="aaa-large" className={'result' + (ratio < 4.5 ? ' fail' : '')}> {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>); } ; const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); }
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import ReactDOM from 'react-dom/client'; import React, { useState } from 'react'; import useEvent from 'react-use-event-hook'; import { InputColor } from '@mescius/wijmo.react.input'; import { Color, Globalize } from '@mescius/wijmo'; import './app.css'; const getChannel = (rgb) => { rgb /= 255; return rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4); }; // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance const 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; }; const 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 App() { const [fore, setFore] = useState('black'); const [back, setBack] = useState('white'); const [ratio, setRatio] = useState(getRatio(fore, back)); const handleForeChange = useEvent((s) => { setFore(s.value); setRatio(getRatio(s.value, back)); }); const handleBackChange = useEvent((s) => { setBack(s.value); setRatio(getRatio(fore, s.value)); }); return (<div className="container-fluid"> <h1>Contrast Checker</h1> <div className="row"> <div className="col-md-4"> <h3>Foreground Color</h3> <InputColor value={fore} valueChanged={handleForeChange}/> <h3>Background Color</h3> <InputColor value={back} valueChanged={handleBackChange}/> </div> <div className="col-md-4"> <h3>Contrast Ratio</h3> <div id="ratio" style={{ 'borderColor': ratio < 7 ? 'whitesmoke' : 'darkgreen' }}> <b>{Globalize.format(ratio, 'g1')}</b>:1 </div> <h3>Result</h3> <p className="sample" style={{ 'color': fore, 'background': back }}> Normal Text </p> <p> WCAG AA:&nbsp; <span id="aa-normal" className={'result' + (ratio < 4.5 ? ' fail' : '')}> {ratio < 4.5 ? 'Fail' : 'Pass'} </span> WCAG AAA:&nbsp; <span id="aaa-normal" className={'result' + (ratio < 7 ? ' fail' : '')}> {ratio < 7 ? 'Fail' : 'Pass'} </span> </p> <p className="sample large" style={{ 'color': fore, 'background': back }}> Large Text </p> <p> WCAG AA:&nbsp; <span id="aa-large" className={'result' + (ratio < 3 ? ' fail' : '')}> {ratio < 3 ? 'Fail' : 'Pass'} </span> WCAG AAA:&nbsp; <span id="aaa-large" className={'result' + (ratio < 4.5 ? ' fail' : '')}> {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>); } ; const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); }
<!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="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.40/system.src.js" integrity="sha512-G6mEj6h18+m3MvzdviSDfPle/TfH0//cXcB33AKlNR/Rha0yQsKefDZKRTkIZos97HEGq2JMV1RT5ybMoQ3WsQ==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html>
#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) { window.process = { env: { NODE_ENV: "production" } } System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.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.grid.immutable': 'npm:@mescius/wijmo.grid.immutable/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/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.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.react.chart.analytics": "npm:@mescius/wijmo.react.chart.analytics/index.js", "@mescius/wijmo.react.chart.animation": "npm:@mescius/wijmo.react.chart.animation/index.js", "@mescius/wijmo.react.chart.annotation": "npm:@mescius/wijmo.react.chart.annotation/index.js", "@mescius/wijmo.react.chart.finance.analytics": "npm:@mescius/wijmo.react.chart.finance.analytics/index.js", "@mescius/wijmo.react.chart.finance": "npm:@mescius/wijmo.react.chart.finance/index.js", "@mescius/wijmo.react.chart.hierarchical": "npm:@mescius/wijmo.react.chart.hierarchical/index.js", "@mescius/wijmo.react.chart.interaction": "npm:@mescius/wijmo.react.chart.interaction/index.js", "@mescius/wijmo.react.chart.radar": "npm:@mescius/wijmo.react.chart.radar/index.js", "@mescius/wijmo.react.chart": "npm:@mescius/wijmo.react.chart/index.js", "@mescius/wijmo.react.core": "npm:@mescius/wijmo.react.core/index.js", '@mescius/wijmo.react.chart.map': 'npm:@mescius/wijmo.react.chart.map/index.js', "@mescius/wijmo.react.gauge": "npm:@mescius/wijmo.react.gauge/index.js", "@mescius/wijmo.react.grid.detail": "npm:@mescius/wijmo.react.grid.detail/index.js", "@mescius/wijmo.react.grid.filter": "npm:@mescius/wijmo.react.grid.filter/index.js", "@mescius/wijmo.react.grid.grouppanel": "npm:@mescius/wijmo.react.grid.grouppanel/index.js", '@mescius/wijmo.react.grid.search': 'npm:@mescius/wijmo.react.grid.search/index.js', "@mescius/wijmo.react.grid.multirow": "npm:@mescius/wijmo.react.grid.multirow/index.js", "@mescius/wijmo.react.grid.sheet": "npm:@mescius/wijmo.react.grid.sheet/index.js", '@mescius/wijmo.react.grid.transposed': 'npm:@mescius/wijmo.react.grid.transposed/index.js', '@mescius/wijmo.react.grid.transposedmultirow': 'npm:@mescius/wijmo.react.grid.transposedmultirow/index.js', '@mescius/wijmo.react.grid.immutable': 'npm:@mescius/wijmo.react.grid.immutable/index.js', "@mescius/wijmo.react.grid": "npm:@mescius/wijmo.react.grid/index.js", "@mescius/wijmo.react.input": "npm:@mescius/wijmo.react.input/index.js", "@mescius/wijmo.react.olap": "npm:@mescius/wijmo.react.olap/index.js", "@mescius/wijmo.react.viewer": "npm:@mescius/wijmo.react.viewer/index.js", "@mescius/wijmo.react.nav": "npm:@mescius/wijmo.react.nav/index.js", "@mescius/wijmo.react.base": "npm:@mescius/wijmo.react.base/index.js", '@mescius/wijmo.react.barcode.common': 'npm:@mescius/wijmo.react.barcode.common/index.js', '@mescius/wijmo.react.barcode.composite': 'npm:@mescius/wijmo.react.barcode.composite/index.js', '@mescius/wijmo.react.barcode.specialized': 'npm:@mescius/wijmo.react.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'react': 'npm:react/index.js', 'react-dom': 'npm:react-dom/index.js', 'react-dom/client': 'npm:react-dom/client.js', "scheduler": "npm:scheduler/index.js", 'redux': 'npm:redux/dist/redux.min.js', 'react-redux': 'npm:react-redux/dist/react-redux.min.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js', "react-use-event-hook": "npm:react-use-event-hook/dist/esm/useEvent.js", }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);