Files
leecat.art/_site/backend-accessibility/index.html

1605 lines
37 KiB
HTML
Raw Normal View History

2026-02-20 11:57:19 -08:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
<title>backend accessibility | hello hello</title>
<meta name="description" content="Lee Cattarin... on the internet!">
<link rel="alternate" href="/feed.xml" type="application/atom+xml" title="hello hello">
<meta property="og:title" content="backend accessibility">
<meta property="og:type" content="website">
<meta property="og:description" content="Lee Cattarin... on the internet!">
<meta property="og:site_name" content="hello hello">
<meta property="og:image" content="/img/camelCase-print.jpg">
<meta property="og:image:alt" content="A carved stamp next to its print. The print reads &#39;#camelCase&#39; in a slightly formal-looking italic font.">
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
<meta name="generator" content="Eleventy v3.1.2">
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Mono:ital,wght@0,200..800;1,200..800&family=Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
<script src="https://kit.fontawesome.com/884dded219.js" crossorigin="anonymous"></script>
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
<style>.post-metadata {
2026-02-19 21:09:25 -08:00
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: baseline;
margin: 1.5rem 0 .5rem;
}
.post-metadata p {
font-size: .9rem;
margin: 0;
}
.post-tags {
display: flex;
flex-flow: row wrap;
gap: .5rem;
list-style: none;
margin: 0;
}
.post-tags li {
margin: 0;
}
.post-tags li a {
text-decoration: none;
color: var(--color-teal);
padding: 0 .5rem;
border-radius: 1rem;
box-shadow: .15rem .15rem var(--color-shadow);
border: .08rem solid var(--color-teal);
line-height: 2;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .1s ease-in, left .1s ease-in;
}
.post-tags li a:focus-visible {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
@media (any-hover: hover) {
.post-tags li a:hover {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
}
@media (forced-colors: active) {
.post-tags li a:focus-visible {
outline-offset: .08rem;
outline: .08rem solid;
}
@media (any-hover: hover) {
.post-tags li a:hover {
outline-offset: .08rem;
outline: .08rem solid;
}
}
}
/* Click animation */
.post-tags li a:active {
top: .1rem;
left: .1rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
/* Adapted from PrismJS 1.30.0 Tomorrow Night theme
https://prismjs.com/download
*/
code,
pre,
code[class*=language-],
pre[class*=language-] {
font-family: var(--font-family-code);
background-color: var(--color-bg-alt);
font-size: .9rem;
text-shadow: 0 1px var(--color-shadow);
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre,
pre[class*=language-] {
margin: 1rem 0;
padding: 1rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
border-radius: .5rem;
overflow: auto;
}
:not(pre)>code,
:not(pre)>code[class*=language-] {
padding: .2rem;
border-radius: .25rem;
white-space: normal;
}
/* Selected text */
code ::-moz-selection,
code::-moz-selection,
pre ::-moz-selection,
pre::-moz-selection,
code[class*=language-] ::-moz-selection,
code[class*=language-]::-moz-selection,
pre[class*=language-] ::-moz-selection,
pre[class*=language-]::-moz-selection,
code ::selection,
code::selection,
pre ::selection,
pre::selection,
code[class*=language-] ::selection,
code[class*=language-]::selection,
pre[class*=language-] ::selection,
pre[class*=language-]::selection {
text-shadow: none;
background-color: var(--color-bg);
}
/* Syntax highlighting */
.token.namespace {
opacity: .7;
}
.token.bold,
.token.important {
font-weight:700
}
.token.italic {
font-style:italic
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog,
.token.punctuation {
color: var(--color-grey);
}
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag {
color: var(--color-red);
}
.token.boolean,
.token.function,
.token.number {
color: var(--color-orange);
}
.token.class-name,
.token.constant,
.token.property,
.token.symbol {
color: var(--color-yellow);
}
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable,
.token.inserted {
color: var(--color-green);
}
.token.entity,
.token.operator,
.token.url,
.token.function-name {
color:var(--color-blue);
}
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: var(--color-purple);
}
2026-02-20 08:36:56 -08:00
#postlist,
#taglist {
list-style: none;
}
#postlist, .post,
#taglist, .tag {
margin: 0;
}
/* Odd-numbered posts & tag layout/coloration */
.post:nth-child(odd) .postlink,
.tag:nth-child(odd) .taglink {
grid-template-areas:
'img h2'
'img info'
'img .';
grid-template-columns: 45% auto;;
--color-primary: var(--color-teal);
--color-accent: var(--color-pink);
}
/* Even-numbered posts & tags layout/coloration */
.post:nth-child(even) .postlink,
.tag:nth-child(even) .taglink {
grid-template-areas:
'h2 img'
'info img'
'. img';
grid-template-columns: auto 45%;
--color-primary: var(--color-pink);
--color-accent: var(--color-teal);
}
/* Layout for all posts on mobile */
@media (max-width: 650px) {
.post:nth-child(n) .postlink,
.tag:nth-child(n) .taglink {
grid-template-areas:
'img'
'h2'
'info';
grid-template-columns: auto;
}
}
/* Link */
.postlink,
.taglink {
display: grid;
border: .25rem solid var(--color-primary);
border-radius: 1.25rem;
box-shadow: .35rem .35rem var(--color-shadow);
margin: 2rem 0;
text-decoration: none;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
.postlink:focus-visible,
.taglink:focus-visible {
background-color: var(--color-primary);
outline: none;
}
@media (any-hover: hover) {
.postlink:hover,
.taglink:hover {
background-color: var(--color-primary);
}
}
/* Forced colors */
@media (forced-colors: active) {
.postlink:focus-visible,
.taglink:focus-visible {
outline-offset: .25rem;
outline: .25rem solid;
}
@media (any-hover: hover) {
.postlink:hover,
.taglink:hover {
outline-offset: .25rem;
outline: .25rem solid;
}
}
}
/* Click animation */
.postlink:active,
.taglink:active {
box-shadow: none;
top: .2rem;
left: .2rem;
box-shadow: .15rem .15rem var(--color-shadow);
}
/* Post & tag elements */
.post h2, .post img,
.post ul, .post li,
.tag h2, .tag p,
.tag img {
margin: 0;
}
.post h2,
.tag h2 {
grid-area: h2;
padding: .25rem .5rem;
text-transform: uppercase;
font-size: 1.5rem;
color: var(--color-primary);
border-radius: 1rem 1rem 0 0;
border-bottom: .25rem solid var(--color-accent);
}
.post:nth-child(even) h2,
.tag:nth-child(even) h2 {
text-align: right;
}
.postlink:focus-visible h2,
.taglink:focus-visible h2 {
color: var(--color-bg);
border-color: var(--color-bg);
}
@media (any-hover: hover) {
.postlink:hover h2,
.taglink:hover h2 {
color: var(--color-bg);
border-color: var(--color-bg);
}
}
/* Images */
.post img,
.tag-imgs {
grid-area: img;
}
.tag-imgs {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: .15rem;
}
.tag-imgs img {
aspect-ratio: 3 / 2;
object-fit: cover;
}
.missing-image {
width: 100%;
aspect-ratio: 3 / 2;
background-color: var(--color-bg-alt);
border-radius: calc(1rem);
}
.taglink:focus-visible .missing-image {
opacity: .7;
}
@media (any-hover: hover) {
.taglink:hover .missing-image {
opacity: .7;
}
}
/* Post tags */
.postlist-tags {
grid-area: info;
list-style: none;
display: flex;
flex-flow: row wrap;
gap: .5rem;
padding: .5rem;
}
.post:nth-child(odd) .postlist-tags {
justify-content: flex-end;
}
.postlist-tags li,
.tagcount {
background-color: var(--color-primary);
color: var(--color-bg);
padding: 0 .5rem;
border-radius: 1rem;
}
.postlink:focus-visible .postlist-tags li,
.taglink:focus-visible .tagcount {
background-color: var(--color-bg);
color: var(--color-primary);
}
@media (any-hover: hover) {
.postlink:hover .postlist-tags li,
.taglink:hover .tagcount {
background-color: var(--color-bg);
color: var(--color-primary);
}
}
/* Tag count */
.tag p {
grid-area: info;
padding: .5rem;
}
.tag:nth-child(odd) p {
text-align: right;
}
2026-02-19 21:09:25 -08:00
:root {
color-scheme: light dark;
--font-family: 'Atkinson Hyperlegible Next', sans-serif;
--font-family-code: 'Atkinson Hyperlegible Mono', monospace;
--color-dark: #2e303e;
--color-dark-alt: #3c3f52;
--color-light: #ebeeef;
--color-light-alt: #dbe1e3;
--color-teal-dark: #18737b;
--color-teal-light: #25b0bc;
--color-pink-dark: #94195d;
--color-pink-light: #ee9fcb;
--color-shadow: rgba(2, 10, 40, .25);
/* Used for syntax highlighting */
2026-02-20 12:24:10 -08:00
--color-red-light: #f195aa;
2026-02-19 21:09:25 -08:00
--color-orange-light: #fab795;
--color-yellow-light: #fbe6bc;
--color-green-light: #29d398;
--color-blue-light: #26bbd9;
--color-purple-light: #ddaeea;
--color-grey-light: #b9c3c6;
--color-red-dark: #991433;
--color-orange-dark: #883206;
--color-yellow-dark: #6a4906;
--color-green-dark: #125940;
--color-blue-dark: #125663;
--color-purple-dark: #722999;
--color-grey-dark: #4a4b64;
--color-text: light-dark(var(--color-dark), var(--color-light));
--color-bg: light-dark(var(--color-light), var(--color-dark));
--color-text-alt: light-dark(var(--color-dark-alt), var(--color-light-alt));
--color-bg-alt: light-dark(var(--color-light-alt), var(--color-dark-alt));
--color-teal: light-dark(var(--color-teal-dark), var(--color-teal-light));
--color-pink: light-dark(var(--color-pink-dark), var(--color-pink-light));
--color-red: light-dark(var(--color-red-dark), var(--color-red-light));
--color-orange: light-dark(var(--color-orange-dark), var(--color-orange-light));
--color-yellow: light-dark(var(--color-yellow-dark), var(--color-yellow-light));
--color-green: light-dark(var(--color-green-dark), var(--color-green-light));
--color-blue: light-dark(var(--color-blue-dark), var(--color-blue-light));
--color-purple: light-dark(var(--color-purple-dark), var(--color-purple-light));
--color-grey: light-dark(var(--color-grey-dark), var(--color-grey-light));
}
/* Base */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-bg);
}
main {
width: 60vw;
max-width: 1000px;
margin: 0 auto;
2026-02-20 08:36:56 -08:00
scroll-margin-top: 7rem;
2026-02-19 21:09:25 -08:00
}
@media (max-width: 1050px) {
main {
width: 75vw;
}
}
@media (max-width: 650px) {
main {
width: 92vw;
}
}
/* Headers */
h1, h2, h3, h4, h5, h6 {
line-height: 1.25;
color: var(--color-teal);
}
h1 {
margin-top: 3rem;
font-size: 3.5rem;
text-align: center;
}
2026-02-20 08:36:56 -08:00
h2, h3, h4, h5, h6 {
scroll-margin-top: 5rem;
}
2026-02-19 21:09:25 -08:00
h2 {
margin-top: 2rem;
font-size: 2.2rem;
}
h3 {
margin-top: 1.5rem;
font-size: 1.6rem;
}
@media (max-width: 650px) {
h1 { font-size: 2.8rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.35rem; }
}
h4, h5, h6 {
margin-top: 1rem;
font-size: 1.2rem;
}
/* Images */
img {
display: block;
max-width: 100%;
height: auto;
border-radius: 1rem;
}
/* Paragraphs */
p {
margin: 1.25rem 0;
line-height: 1.4;
}
strong,
b {
font-weight: 900;
}
/* Links */
a {
color: var(--color-text);
border-radius: .25rem;
text-decoration: underline;
text-decoration-style: solid;
text-decoration-thickness: .2em;
text-decoration-color: var(--color-teal);
transition: text-decoration-thickness .5s;
}
a:focus-visible {
text-decoration: none;
outline: .15rem solid var(--color-teal);
}
@media (any-hover: hover) {
a:hover {
text-decoration-thickness: .4em;
}
}
a:active {
text-decoration-thickness: .4em;
}
/* Heading anchors */
a.ha,
span.ha-placeholder {
color: var(--color-pink);
}
span.ha-placeholder {
2026-02-20 12:24:10 -08:00
opacity: .55;
2026-02-19 21:09:25 -08:00
}
/* Lists */
::marker {
color: var(--color-pink);
}
ul, ol, dl, li {
margin-left: 1rem;
}
li {
line-height: 1.2 5;
margin-top: .65rem;
margin-bottom: .65rem;
}
li ul, li ol {
margin: .5rem 0;
}
dt {
font-weight: 900;
margin-top: .5rem;
}
dd {
margin-left: 2rem;
margin-bottom: .75rem;
}
/* Blockquotes */
blockquote {
margin: .5rem 0;
padding: 0 1rem;
border-radius: .25rem 1rem 1rem .25rem;
line-height: 1.25;
border-left: .5rem solid var(--color-pink);
}
blockquote,
blockquote p,
blockquote ol,
blockquote ul {
background-color: var(--color-bg-alt);
padding: .5rem;
}
blockquote p {
margin: 0;
}
/* Tables */
table {
width: 100%;
border-spacing: 0; /* border collapse doesn't play nice with radii */
border-radius: .3rem;
border: thin solid var(--color-pink);
}
th {
color: var(--color-bg);
background-color: var(--color-pink);
}
th code {
color: var(--color-text); /* Yes, I actually do this somewhere */
}
th, td {
padding: .5rem;
text-align: left;
}
tr:nth-child(even) { background-color: var(--color-bg-alt); }
th:not(:first-child) { border-left: thin solid var(--color-bg); }
th:first-child { border-top-left-radius: .25rem; }
th:last-child { border-top-right-radius: .25rem; }
td:not(:first-child) { border-left: thin solid var(--color-pink); }
/* Times */
time {
color: var(--color-grey);
}
/* Horizontal rules */
hr {
2026-02-20 08:36:56 -08:00
border: .25rem solid var(--color-pink);
2026-02-19 21:09:25 -08:00
margin: 2rem 0;
}
/* Used on home, reference, gallery pages */
.centered {
text-align: center;
}
/* Currently only used for resume, but it's generalizable */
.upper {
text-transform: uppercase;
}
/* Header */
header {
position: sticky;
top: 0;
background-color: var(--color-bg);
padding: .75rem 0;
z-index: 10;
2026-02-20 08:46:50 -08:00
border-bottom: thick solid var(--color-teal);
2026-02-20 08:36:56 -08:00
box-shadow: 0 .25rem .15rem var(--color-shadow);
2026-02-19 21:09:25 -08:00
}
/* Header links, pagination links */
header a,
.pagination a,
.webring ul a {
border-radius: 1rem;
border: .125rem solid var(--color-pink);
color: var(--color-pink);
text-decoration: none;
padding: 0 .25rem;
box-shadow: .15rem .15rem var(--color-shadow);
font-size: 1.2rem;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
header a,
.pagination .older a,
.webring .prev a,
.webring .rand a {
padding-right: .35rem;
}
.pagination .newer a,
.webring .next a,
.webring .rand a {
padding-left: .35rem;
}
header a:focus-visible,
.pagination a:focus-visible,
.webring ul a:focus-visible {
color: var(--color-bg);
border-color: var(--color-pink);
background-color: var(--color-pink);
outline: none;
}
@media (any-hover: hover) {
header a:hover,
.pagination a:hover,
.webring ul a:hover {
color: var(--color-bg);
border-color: var(--color-pink);
background-color: var(--color-pink);
}
}
@media (forced-colors: active) {
header a:focus-visible,
.pagination a:focus-visible,
.webring ul a:focus-visible {
outline-offset: .125rem;
outline: .125rem solid;
}
@media (any-hover: hover) {
header a:hover,
.pagination a:hover,
.webring ul a:hover {
outline-offset: .125rem;
outline: .125rem solid;
}
}
}
/* Click animation */
header a:active,
.pagination a:active,
.webring ul a:active {
top: .1rem;
left: .1rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
/* Current page */
header a[aria-current="page"] {
border-color: var(--color-teal);
color: var(--color-teal);
}
header a[aria-current="page"]:focus-visible {
color: var(--color-bg);
border-color: var(--color-teal);
background-color: var(--color-teal);
}
@media (any-hover: hover) {
header a[aria-current="page"]:hover {
color: var(--color-bg);
background-color: var(--color-teal);
border-color: var(--color-teal);
}
}
/* Header link icons, pagination icons */
header i,
.pagination i,
.webring ul i {
color: var(--color-teal);
}
header i,
.pagination .older i,
.webring .prev i,
.webring .rand i:nth-child(1) {
padding-left: .25rem;
}
.pagination .newer i,
.webring .next i,
.webring .rand i:nth-child(2) {
padding-right: .25rem;
}
header a[aria-current="page"] i {
color: var(--color-pink);
}
header a:focus-visible i,
a[aria-current="page"] a:focus-visible i,
.pagination a:focus-visible i,
.webring ul a:focus-visible i {
color: var(--color-bg);
}
@media (any-hover: hover) {
header a:hover i,
header a[aria-current="page"]:hover i,
.pagination a:hover i,
.webring ul a:hover i {
color: var(--color-bg);
}
}
/* Skip link */
#skip {
left: -999px;
position: absolute;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
z-index: -99;
}
#skip:focus-visible {
display: inline-block;
left: auto;
top: auto;
width: auto;
height: auto;
overflow: auto;
margin: 0 10%;
z-index: 999;
}
/* Nav */
header ul {
display: flex;
list-style: none;
gap: 1rem;
justify-content: center;
}
header ul,
header li {
margin: 0;
}
@media (max-width: 650px) {
.menu-text {
display: none; /* Icons only on small screens */
}
header a {
padding: .15rem .5rem;
}
header i {
padding: 0;
}
}
/* Footer */
footer {
padding: 1rem 0;
font-size: .9rem;
2026-02-20 08:46:50 -08:00
border-top: thick solid var(--color-pink);
2026-02-19 21:09:25 -08:00
}
footer ul {
display: flex;
list-style: none;
gap: .5rem;
justify-content: center;
margin: 0;
}
footer li {
margin: 0;
}
footer li:nth-child(2)::before,
footer li:nth-child(2)::after {
content: " ● " / "";
color: var(--color-teal);
}
@media (max-width: 650px) {
footer ul {
flex-flow: column;
text-align: center;
}
footer li:nth-child(2)::before,
footer li:nth-child(2)::after {
content: none;
}
}
footer a {
text-decoration-color: var(--color-pink);
}
footer a:focus-visible {
outline-color: var(--color-pink);
}
/* Pagination */
.pagination,
.pagination li {
margin: 0;
}
.pagination {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-areas: "older newer";
list-style: none;
margin-top: 3rem;
}
@media (max-width: 650px) {
.post-pagination {
grid-template-columns: 1fr;
grid-template-areas:
"older"
"newer";
gap: .75rem;
}
}
.pagination .older {
grid-area: older;
}
.pagination .newer {
grid-area: newer;
text-align: right;
}
/* webring navigation */
.webring {
margin-bottom: 3rem;
}
.webring ul {
display: flex;
flex-flow: row wrap;
list-style: none;
justify-content: space-around;
gap: .35rem;
}
.webring ul,
.webring li {
margin-left: 0;
}
@media print {
/* Nav elements */
header,
footer,
nav {
display: none !important;
}
/* Base */
body {
background-color: #fff;
color: #000;
}
main {
width: 95vw;
}
h1,h2,h3,h4,h5,h6 {
color: #000;
}
/* Links */
/* Hover is not really necessary, but it's annoying when testing otherwise */
a,
a:hover {
text-decoration-style: dotted;
text-decoration-thickness: .1rem;
text-decoration-color: #000;
}
a::after{
content: " (" attr(href) ")";
}
/* Code */
code,
pre,
code[class*=language-],
pre[class*=language-] {
text-shadow: none;
background-color: var(--color-light);
color: #000 !important;
}
.token.namespace {
opacity: 1;
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog,
.token.punctuation,
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag,
.token.boolean,
.token.function,
.token.number,
.token.class-name,
.token.constant,
.token.property,
.token.symbol,
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable,
.token.inserted,
.token.entity,
.token.operator,
.token.url,
.token.function-name,
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: #000;
}
}</style>
2026-02-20 11:57:19 -08:00
<script type="module">// Thank you to https://github.com/daviddarnes/heading-anchors
2026-02-19 21:09:25 -08:00
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
let globalInstanceIndex = 0;
class HeadingAnchors extends HTMLElement {
static register(tagName = "heading-anchors", registry = window.customElements) {
if(registry && !registry.get(tagName)) {
registry.define(tagName, this);
}
}
static attributes = {
exclude: "data-ha-exclude",
prefix: "prefix",
content: "content",
}
static classes = {
anchor: "ha",
placeholder: "ha-placeholder",
srOnly: "ha-visualhide",
}
static defaultSelector = "h2,h3,h4,h5,h6";
static css = `
.${HeadingAnchors.classes.srOnly} {
clip: rect(0 0 0 0);
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}
.${HeadingAnchors.classes.anchor} {
position: absolute;
left: var(--ha_offsetx);
top: var(--ha_offsety);
text-decoration: none;
opacity: 0;
}
.${HeadingAnchors.classes.placeholder} {
opacity: .3;
}
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
opacity: 1;
}
.${HeadingAnchors.classes.anchor},
.${HeadingAnchors.classes.placeholder} {
display: inline-block;
padding: 0 .25em;
/* Disable selection of visually hidden label */
-webkit-user-select: none;
user-select: none;
}
@supports (anchor-name: none) {
.${HeadingAnchors.classes.anchor} {
position: absolute;
left: anchor(left);
top: anchor(top);
}
}`;
get supports() {
return "replaceSync" in CSSStyleSheet.prototype;
}
get supportsAnchorPosition() {
return CSS.supports("anchor-name: none");
}
constructor() {
super();
if(!this.supports) {
return;
}
let sheet = new CSSStyleSheet();
sheet.replaceSync(HeadingAnchors.css);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
this.headingStyles = {};
this.instanceIndex = globalInstanceIndex++;
}
connectedCallback() {
if (!this.supports) {
return;
}
this.headings.forEach((heading, index) => {
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
let anchor = this.getAnchorElement(heading);
let placeholder = this.getPlaceholderElement();
// Prefers anchor position approach for better accessibility
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
if(this.supportsAnchorPosition) {
let anchorName = `--ha_${this.instanceIndex}_${index}`;
placeholder.style.setProperty("anchor-name", anchorName);
anchor.style.positionAnchor = anchorName;
}
heading.appendChild(placeholder);
heading.after(anchor);
}
});
}
// Polyfill-only
positionAnchorFromPlaceholder(placeholder) {
if(!placeholder) {
return;
}
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
if(!heading.nextElementSibling) {
return;
}
// TODO next element could be more defensive
this.positionAnchor(heading.nextElementSibling);
}
// Polyfill-only
positionAnchor(anchor) {
if(!anchor || !anchor.previousElementSibling) {
return;
}
// TODO previous element could be more defensive
let heading = anchor.previousElementSibling;
this.setFontProp(heading, anchor);
if(this.supportsAnchorPosition) {
// quit early
return;
}
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
}
}
setFontProp(heading, anchor) {
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
let style = getComputedStyle(placeholder);
let props = ["font-weight", "font-size", "line-height", "font-family"];
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
let vars = style.getPropertyValue("font-variation-settings");
if(vars) {
anchor.style.setProperty("font-variation-settings", vars);
}
}
}
getAccessibleTextPrefix() {
// Useful for i18n
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
}
getContent() {
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
return this.getAttribute(HeadingAnchors.attributes.content);
}
return "#";
}
// Placeholder nests inside of heading
getPlaceholderElement() {
let ph = document.createElement("span");
ph.setAttribute("aria-hidden", true);
ph.classList.add(HeadingAnchors.classes.placeholder);
let content = this.getContent();
if(content) {
ph.innerHTML = content; // CHANGED HERE
}
ph.addEventListener("mouseover", (e) => {
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
this.positionAnchorFromPlaceholder(placeholder);
}
});
return ph;
}
getAnchorElement(heading) {
let anchor = document.createElement("a");
anchor.href = `#${heading.id}`;
anchor.classList.add(HeadingAnchors.classes.anchor);
let content = this.getContent();
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
anchor.addEventListener("focus", e => {
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
if(anchor) {
this.positionAnchor(anchor);
}
});
anchor.addEventListener("mouseover", (e) => {
// when CSS anchor positioning is supported, this is only used to set the font
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
this.positionAnchor(anchor);
});
return anchor;
}
get headings() {
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
}
get selector() {
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
}
}
HeadingAnchors.register();
export { HeadingAnchors }</script>
2026-02-20 11:57:19 -08:00
</head>
<body>
<header>
2026-02-19 21:09:25 -08:00
2026-02-20 12:24:10 -08:00
<a href="#main" id="skip" title="skip to main content">
2026-02-19 21:09:25 -08:00
<i class="fa-solid fa-forward" aria-hidden="true"></i> skip
</a>
<nav aria-label="main navigation">
<ul>
<li>
2026-02-20 11:57:19 -08:00
<a href="/reference/" title="read reference posts">
2026-02-19 21:09:25 -08:00
<i class="fa-regular fa-folder-open" aria-hidden="true"></i>
<span class="menu-text">reference</span>
</a>
</li>
<li>
2026-02-20 11:57:19 -08:00
<a href="/gallery/" title="view the gallery">
2026-02-19 21:09:25 -08:00
<i class="fa-regular fa-images" aria-hidden="true"></i>
<span class="menu-text">gallery</span>
</a>
</li>
<li>
2026-02-20 11:57:19 -08:00
<a href="/" title="">
2026-02-19 21:09:25 -08:00
<i class="fa fa-solid fa-crow" aria-hidden="true"></i>
<span class="menu-text">home</span>
</a>
</li>
<li>
2026-02-20 11:57:19 -08:00
<a href="/about/" title="about Lee">
2026-02-19 21:09:25 -08:00
<i class="fa-regular fa-user" aria-hidden="true"></i>
<span class="menu-text">about</span>
</a>
</li>
<li>
2026-02-20 11:57:19 -08:00
<a href="/contact/" title="contact Lee">
2026-02-19 21:09:25 -08:00
<i class="fa-solid fa-envelope-open-text" aria-hidden="true"></i>
<span class="menu-text">contact</span>
</a>
</li>
</ul>
</nav>
</header>
2026-02-20 11:57:19 -08:00
<main id="main">
2026-02-19 21:09:25 -08:00
2026-02-20 08:36:56 -08:00
2026-02-19 21:09:25 -08:00
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
<article>
<h1 id="backend-accessibility">backend accessibility</h1>
<div class="post-metadata">
<p>
posted on
<time datetime="2023-05-12">May 12, 2023</time>
by Lee Cattarin
</p>
<ul class="post-tags">
<li>
<a href="/tags/software/">software</a>
</li>
</ul>
</div>
<img src="/img/camelCase-print.jpg" alt="A carved stamp next to its print. The print reads &#39;#camelCase&#39; in a slightly formal-looking italic font." loading="lazy" decoding="async" width="1000" height="750">
<blockquote>
<p>These notes are from a talk I gave at work. If you think something is missing or incorrect, please let me know!</p>
</blockquote>
<p>Backend developers still have users: other developers. We're all human, with human limitations and differences. Design for those limitations and you'll find you improve the end result for everyone.</p>
<h2 id="documentation">Documentation</h2>
<p>The first thing you can do: document. By this I don't just mean standalone text documentation; I also include code comments, clear variable and file naming, and pipeline or script outputs that report success or failure <em>and give details</em>.</p>
<h2 id="rely-on-standards">Rely on standards</h2>
<p>Rely on existing standards where possible. Style guides, spell checkers, linters, and formatters are all great. Make standards easy to follow with linter, editor, etc. config files in your repo, or add a devcontainer so others can easily get started with the project. When there's no standard, create one; for example, set up github issue and PR templates. And whenever you can, automate tests <em>and fixes</em>.</p>
<h2 id="user-stories">User stories</h2>
<h3 id="i-want-to-understand-new-terms-as-they-re-introduced">I want to understand new terms as they're introduced</h3>
<ul>
<li>Abbreviations/initialisms: can stall anyone unfamiliar with them. Spell them out when they're first introduced, and add the abbreviation in parentheses.</li>
<li>Jargon: avoid it as much as possible. Keep a friendly tone.</li>
<li>Neologisms: tech loves em! Often compound words or portmanteaus. For compound words (or, at times, hashtags), use camelCase or another style that distinguishes between words. This helps visually as well as improving screen reader pronunciation.</li>
</ul>
<h3 id="i-want-to-quickly-scan-a-page-for-the-information-i-need">I want to quickly scan a page for the information I need</h3>
<p>Sighted users may take for granted the ability to skim a page by glancing at headers or highlights. Users with screen readers rely on several features for the same functionality.</p>
<p>For example, screen readers can summarize the headers on a page. To leverage this, break up content with headers and avoid using other formatting to achieve similar effects visually. Stick to one h1 per page, and don't skip levels (e.g. go from an h1 to an h3).</p>
<p>Screen readers can also summarize a page's links. In order for this to be effective, links need descriptive text attached to them (and always avoid bare links!). Compare these examples:</p>
<blockquote>
<p>Read <a href="https://www.w3.org/WAI/ARIA/apg/patterns/">more</a> about accessibility patterns on the web</p>
</blockquote>
<p>vs</p>
<blockquote>
<p>Read more about <a href="https://www.w3.org/WAI/ARIA/apg/patterns/">accessibility patterns on the web</a></p>
</blockquote>
<p>The second example has text directly attached to the link that describes its content. This is a vast improvement over the first example, where the link would just be read out as &quot;more&quot;. Since links are visually highlighted, good link text also improves readability for everyone.</p>
<p>A table of contents can be helpful for a broad set of users, from the power user who knows exactly what she needs from the page to the newbie who just wants to see what the major topics are.</p>
<h3 id="i-want-to-be-aware-of-and-able-to-understand-all-content-on-the-page">I want to be aware of and able to understand all content on the page</h3>
<p>Alt text/image descriptions for image and transcription/audio descriptions for videos are essential (and not just for screen readers - they're really useful if you've got poor internet connection). The references section of this document will link to more information on writing good alt text, but in general, focus on why the image/video is there and what it is conveying.</p>
<p>(If you find you are simply transcribing text in an image, remove the image unless it conveys additional detail - images showing text intended to be read are less user-friendly than the same content conveyed as text. If the text is purely decorative and not intended to be read, carry on - just make sure you <em>don't</em> transcribe it since it's not meaningful!)</p>
<p>I find this comes up the most in backend as <em>diagrams</em>. We love diagrams in place of words! Unfortunately, you'll want those words for some users eventually. Avoid alt text like &quot;diagram of components&quot; or &quot;flow chart showing pipeline&quot; - either write out a more direct explanation that mentions all entities contained in the diagram, or direct the reader to a section of text that covers the same content (e.g. &quot;flow chart of the pipeline described below&quot;). If you're writing a direct explanation, don't feel the need to describe each shape or arrow - focus on describing the relationships and entities those shapes and arrows represent.</p>
<h2 id="todo-notes">todo notes</h2>
<p>These are bits of feedback or further thoughts that have yet to be integrated into this.</p>
<ul>
<li>tabs vs spaces (prefer tabs - allows each developer to customize based on eyesight and personal preference)</li>
<li>don't use color alone to convey meaning</li>
<li>autocorrects and confirmation prompts</li>
</ul>
<h2 id="references">References</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/style-guide/accessibility/accessibility-guidelines-requirements" target="_blank" rel="external">Accessibility guidelines and requirements — Microsoft style guide</a></li>
<li><a href="https://developers.google.com/style/accessibility" target="_blank" rel="external">Write accessible documentation — Google docs style guide</a></li>
<li><a href="https://adrianroselli.com/2024/08/things-to-do-before-asking-is-this-accessible.html" target="_blank" rel="external">Is it accessible? — Adrian Roselli</a></li>
<li><a href="https://marconius.com/a11yLinks/" target="_blank" rel="external">Collected accessibility resources — Marco Salsiccia</a></li>
<li><a href="https://a11y.coffee/start-testing/" target="_blank" rel="external">A11y coffee</a> [note: spell out 'accessibility' rather than shortening to 'a11y'!]</li>
</ul>
<h3 id="alt-text-image-descriptions">Alt text/image descriptions</h3>
<ul>
<li><a href="https://uxdesign.cc/how-to-write-an-image-description-2f30d3bf5546" target="_blank" rel="external">How to write an image description</a></li>
<li><a href="https://www.perkins.org/resource/how-write-alt-text-and-image-descriptions-visually-impaired/" target="_blank" rel="external">How to write alt text and image descriptions for the visually impaired — Perkins School for the Blind</a></li>
<li><a href="https://mannequinrentals.help/2024/03/21/an-attempted-guide-to-writing-effective-alt-and-descriptive-text-for-art/" target="_blank" rel="external">An attempted guide to writing effective alt and descriptive text for art</a></li>
<li><a href="https://alt-text-as-poetry.net/" target="_blank" rel="external">Alt text as poetry</a></li>
<li><a href="http://dataabinitio.com/?p=1161" target="_blank" rel="external">Writing alt text for a scientific figure - Kristin Briney</a></li>
<li><a href="https://adrianroselli.com/2024/05/my-approach-to-alt-text.html">My approach to alt text - Adrian Roselli</a></li>
</ul>
<h3 id="other-specific-topics">Other specific topics</h3>
<ul>
<li><a href="https://adamtuttle.codes/blog/2021/tabs-vs-spaces-its-an-accessibility-issue/" target="_blank" rel="external">Tabs vs. spaces - Adam Tuttle</a></li>
<li><a href="https://accessiblenumbers.com" target="_blank" rel="external">Accessible numbers</a></li>
<li><a href="https://chartability.fizz.studio" target="_blank" rel="external">Chartability</a> (data visualization)</li>
</ul>
</article>
<nav aria-label="pagination">
<ol class="pagination post-pagination">
<li class="older">
<a href="/block-printing-transfer-method/">
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> block printing transfer method
</a>
</li>
<li class="newer">
<a href="/stellars-jay/">
stellar&#39;s jay <i class="fa-solid fa-hand-point-right" aria-hidden="true"></i>
</a>
</li>
</ol>
</nav>
2026-02-20 08:36:56 -08:00
<hr>
<section class="related-posts">
<h2 id="related-posts">related posts</h2>
<ol id="postlist">
<li class="post">
2026-02-20 14:12:22 -08:00
<a class="postlink" href="/azure-locations-and-file-crawling/">
2026-02-20 08:36:56 -08:00
2026-02-20 14:12:22 -08:00
<img src="/img/azure-locations.jpg" alt="A Linux terminal. There is a fun rainbow flag in ascii art at the top, and then the user has called a command asking Azure for a list of resources applicable to a specific resource type" loading="lazy" decoding="async" width="1000" height="827">
2026-02-20 08:36:56 -08:00
2026-02-20 14:12:22 -08:00
<h2 id="azure-locations-and-file-crawling">azure locations and file crawling</h2>
2026-02-20 08:36:56 -08:00
<ul class="postlist-tags">
<li>software</li>
2026-02-20 14:12:22 -08:00
<li>highlight</li>
2026-02-20 08:36:56 -08:00
</ul>
</a>
</li>
<li class="post">
2026-02-20 14:35:54 -08:00
<a class="postlink" href="/comparing-text-editors/">
2026-02-20 08:36:56 -08:00
2026-02-20 14:35:54 -08:00
<img src="/img/horsetail.jpg" alt="Image unrelated to post. Close up on a horsetail plant&#39;s stem, with many small needle-like leaves emerging from all sides of the circular stem at each segmented joint." loading="lazy" decoding="async" width="1000" height="666">
2026-02-20 08:36:56 -08:00
2026-02-20 14:35:54 -08:00
<h2 id="comparing-text-editors">comparing text editors</h2>
2026-02-20 08:36:56 -08:00
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
<li class="post">
2026-02-20 14:35:54 -08:00
<a class="postlink" href="/redirections/">
2026-02-20 08:36:56 -08:00
2026-02-20 14:35:54 -08:00
<img src="/img/angle-brackets-uwu.jpg" alt="Ascii art of an emoticon with pinched eyes and a small mouth made with two angle brackets." loading="lazy" decoding="async" width="1000" height="316">
2026-02-20 08:36:56 -08:00
2026-02-20 14:35:54 -08:00
<h2 id="redirections">redirections</h2>
2026-02-20 08:36:56 -08:00
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
</ol>
</section>
</heading-anchors>
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
</main>
2026-02-19 21:09:25 -08:00
2026-02-20 11:57:19 -08:00
<footer>
2026-02-19 21:09:25 -08:00
<ul>
<li>
2026-02-20 12:24:10 -08:00
<a href="/colophon/">
2026-02-19 21:09:25 -08:00
colophon
</a>
</li>
<li>
<a href="/" title="go home" aria-label="go home | hello hello from Lee Cattarin in 2026">
hello hello from Lee Cattarin in 2026</a>
</li>
<li>
<a href="https://heckin.technology/inherentlee/leecat.art" title="source code" aria-label="source code" target="_blank" rel="external">
src
</a>
</li>
</ul>
</footer>
2026-02-20 14:35:54 -08:00
<!-- This page `/backend-accessibility/` was built on 2026-02-20T22:35:37.906Z -->
2026-02-20 11:57:19 -08:00
<body>
</body></body></html>