@annoyingmouse/wc-risk-matrix
v1.0.2
Published
This web component creates a highly configurable, accessible risk matrix.
Maintainers
Readme
This web component creates a highly configurable, accessible risk matrix, with likelihood on the vertical axis and impact on the horizontal axis.
Demo
<wc-risk-matrix likelihood="3" impact="4"></wc-risk-matrix>See index.html in the repository for a fuller live demo, including a non-square matrix, custom colours, and an externally-restyled example.
Installation
<link rel="stylesheet" href="https://unpkg.com/@annoyingmouse/[email protected]/wc-risk-matrix.css">
<script type="module" src="https://unpkg.com/@annoyingmouse/[email protected]/wc-risk-matrix.js"></script>Or, minified:
<script type="module" src="https://unpkg.com/@annoyingmouse/[email protected]/dist/wc-risk-matrix.min.js"></script>Or from npm:
npm install @annoyingmouse/wc-risk-matrixUsage
<link rel="stylesheet" href="wc-risk-matrix.css">
<wc-risk-matrix
likelihood="3"
impact="4"
likelihood-steps="5"
impact-steps="5"
></wc-risk-matrix>
<script type="module" src="wc-risk-matrix.js"></script>Configuration
The matrix's dimensions, current selection and labels can all be changed using attributes, or the equivalent JavaScript properties.
| Attribute | Property | Type | Default | Description |
| ------------------- | ------------------ | -------------------- | ------------- | ----------------------------------------------------- |
| likelihood | likelihood | integer 1–steps, or null | null | Currently selected likelihood |
| impact | impact | integer 1–steps, or null | null | Currently selected impact |
| likelihood-steps | likelihoodSteps | positive integer | 5 | Number of likelihood levels (rows) |
| impact-steps | impactSteps | positive integer | 5 | Number of impact levels (columns) |
| likelihood-label | likelihoodLabel | string | Likelihood | Vertical axis label |
| impact-label | impactLabel | string | Impact | Horizontal axis label |
| colours | colours | JSON string / array | generated | Score-to-colour mapping, see Colours |
Both likelihood-steps and impact-steps are independent, so the matrix does not need to be square:
<wc-risk-matrix likelihood-steps="3" impact-steps="5"></wc-risk-matrix>Validation
Attributes and properties are validated differently, deliberately:
- Attributes are forgiving. A missing or out-of-range
likelihood/impactreads back asnull(no selection) rather than breaking rendering, and invalidlikelihood-steps/impact-stepsfall back to the default of5. - Properties, being an explicit call from your own code, are strict. Setting
matrix.likelihood = 99on a 5-step matrix throws aRangeError, and settingmatrix.likelihoodSteps = 0throws too.
Score
matrix.scoreA read-only, derived property equal to likelihood * impact. It is null whenever either value is unset. There is no score attribute — the score is always calculated, never stored.
Value
matrix.valueReturns null if there is no complete selection, otherwise an aggregate object:
{
likelihood: 3,
impact: 4,
score: 12,
colour: '#fce5cd'
}It can also be assigned as a convenience for setting both axes at once:
matrix.value = { likelihood: 3, impact: 4 }
matrix.value = null // clears the selectionColours
Each score maps to a colour using:
colours[score - 1]The array needs likelihoodSteps * impactSteps entries (one per possible score). Several adjacent scores commonly share a colour:
matrix.colours = [
'#d9ead3', '#d9ead3', '#d9ead3', '#d9ead3',
'#fff2cc', '#fff2cc', '#fff2cc', '#fff2cc', '#fff2cc',
'#fce5cd', '#fce5cd', '#fce5cd', '#fce5cd', '#fce5cd',
'#f4cccc', '#f4cccc', '#f4cccc', '#f4cccc', '#f4cccc',
'#e06666', '#e06666', '#e06666', '#e06666', '#e06666', '#e06666'
]Colours can also be supplied declaratively as a JSON attribute:
<wc-risk-matrix colours='["#d9ead3","#fff2cc","#fce5cd","#f4cccc"]'></wc-risk-matrix>Rich values like arrays are more naturally supplied through the JavaScript property; the attribute is a convenience for static, declarative markup.
Colours must be valid 3- or 6-digit hex CSS colours (#fff or #ffffff). Setting the colours property to something invalid throws a TypeError. An invalid colours attribute (bad JSON, non-array, or invalid colours) is handled gracefully: a warning is logged to the console and generated default colours are used instead, so a typo in markup never breaks the matrix.
If no colours are supplied, defaults are generated for any matrix size by bucketing each score's percentage of the maximum possible score (likelihoodSteps * impactSteps) into a five-colour progression from green through yellow, amber and red to a stronger red.
Events
Activating a cell (by click, or keyboard Enter/Space, since cells are real <button> elements) updates the selection and fires:
matrix.addEventListener('risk-select', event => {
console.log(event.detail)
// { likelihood: 3, impact: 4, score: 12, colour: '#fce5cd' }
})risk-select is dispatched with { bubbles: true, composed: true }, so it can also be caught via delegation higher up the document. It fires only on genuine user activation — setting likelihood, impact, value, or the corresponding attributes from your own code never dispatches it.
CSS classes and data attributes
Because the component renders in light DOM, everything below is a stable target for ordinary CSS, with no ::part or shadow-DOM API needed:
wc-risk-matrix .risk-matrix /* the <table> */
wc-risk-matrix .risk-matrix__row /* each <tr> */
wc-risk-matrix .risk-matrix__cell /* each <td> */
wc-risk-matrix .risk-matrix__button /* each cell's <button> */
wc-risk-matrix .risk-matrix__button--selected
wc-risk-matrix .risk-matrix__likelihood /* row <th scope="row"> */
wc-risk-matrix .risk-matrix__impact /* column <th scope="col"> */
wc-risk-matrix .risk-matrix__corner /* empty corner <th> cells */
wc-risk-matrix .risk-matrix__label /* the "Likelihood"/"Impact" axis headings */Every button also carries data-* attributes for styling or inspection:
wc-risk-matrix [data-score='25'] { }
wc-risk-matrix [data-likelihood='5'] { }
wc-risk-matrix [data-impact='4'] { }CSS custom properties
--risk-colour /* set per-button from the calculated cell colour */
--risk-border-colour
--risk-border-radius
--risk-text-colour
--risk-focus-colour
--risk-selected-colourwc-risk-matrix.css sets each button's background from --risk-colour rather than an inline background declaration, so consumers stay free to restyle the whole component:
wc-risk-matrix .risk-matrix__button {
border-radius: 50%;
}Accessibility
- The matrix is a real
<table>, with<th scope="col">for impact headings and<th scope="row">for likelihood headings. - Each cell is a real
<button type="button">, so keyboard activation (Enter/Space), focus and tab order all come from the browser, not bespoke JavaScript. - Risk is never communicated by colour alone: every button displays its numeric score and has an
aria-labelsuch as"Likelihood 3, impact 4, risk score 12". - The selected cell exposes
aria-pressed="true"; every other cell exposesaria-pressed="false". - Focus indication is left to the browser's default (or your own CSS via
--risk-focus-colour/:focus-visible), never suppressed outright.
Light DOM
<wc-risk-matrix> deliberately does not use Shadow DOM. All rendered markup is ordinary light-DOM content, so it can be styled with plain page CSS, inspected with normal devtools, and integrated into an existing stylesheet without ::part.
Form association
<wc-risk-matrix> is a form-associated custom element (static formAssociated = true), so it can participate in a <form> like a native control:
<form>
<wc-risk-matrix name="risk" likelihood="3" impact="4"></wc-risk-matrix>
</form>The submitted value is the numeric score as a string (e.g. "12"), not a likelihood,impact pair or a FormData-style multi-field submission. A single risk score is the value most consumers actually need on submission; if you need the individual likelihood/impact values server-side too, read matrix.value client-side and submit them as separate hidden fields.
Browser support
Targets current evergreen browsers (native Custom Elements v1, ElementInternals/form-associated custom elements). No polyfills are included.
Examples
<link rel="stylesheet" href="wc-risk-matrix.css">
<wc-risk-matrix
id="risk"
likelihood="3"
impact="4"
likelihood-steps="5"
impact-steps="5"
></wc-risk-matrix>
<script type="module" src="wc-risk-matrix.js"></script>
<script>
const risk = document.querySelector('#risk')
risk.colours = [
'#d9ead3', '#d9ead3', '#d9ead3', '#d9ead3',
'#fff2cc', '#fff2cc', '#fff2cc', '#fff2cc', '#fff2cc',
'#fce5cd', '#fce5cd', '#fce5cd', '#fce5cd', '#fce5cd',
'#f4cccc', '#f4cccc', '#f4cccc', '#f4cccc', '#f4cccc',
'#e06666', '#e06666', '#e06666', '#e06666', '#e06666', '#e06666'
]
risk.addEventListener('risk-select', event => {
console.log(event.detail)
})
</script>See index.html for a fuller live demo, including a non-square matrix and an externally-restyled example.
License
MIT
