1598 lines
39 KiB
HTML
1598 lines
39 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<title>screen reader optimizations | 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="screen reader optimizations">
|
|
<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/crow.jpg">
|
|
<meta property="og:image:alt" content="Image unrelated to post. A crow poses on driftwood against a whitish sky.">
|
|
|
|
|
|
<meta name="generator" content="Eleventy v3.1.2">
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<script src="https://kit.fontawesome.com/884dded219.js" crossorigin="anonymous"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style>.post-metadata {
|
|
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);
|
|
}
|
|
#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;
|
|
}
|
|
: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 */
|
|
--color-red-light: #f195aa;
|
|
--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;
|
|
scroll-margin-top: 7rem;
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
h2, h3, h4, h5, h6 {
|
|
scroll-margin-top: 5rem;
|
|
}
|
|
|
|
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 {
|
|
opacity: .55;
|
|
}
|
|
|
|
|
|
/* 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 {
|
|
border: .25rem solid var(--color-pink);
|
|
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;
|
|
border-bottom: thick solid var(--color-teal);
|
|
box-shadow: 0 .25rem .15rem var(--color-shadow);
|
|
}
|
|
|
|
/* 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;
|
|
border-top: thick solid var(--color-pink);
|
|
}
|
|
|
|
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>
|
|
|
|
<script type="module">// Thank you to https://github.com/daviddarnes/heading-anchors
|
|
// 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>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
|
|
<a href="#main" id="skip" title="skip to main content">
|
|
<i class="fa-solid fa-forward" aria-hidden="true"></i> skip
|
|
</a>
|
|
|
|
<nav aria-label="main navigation">
|
|
<ul>
|
|
|
|
<li>
|
|
<a href="/reference/" title="read reference posts">
|
|
<i class="fa-regular fa-folder-open" aria-hidden="true"></i>
|
|
<span class="menu-text">reference</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/gallery/" title="view the gallery">
|
|
<i class="fa-regular fa-images" aria-hidden="true"></i>
|
|
<span class="menu-text">gallery</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/" title="">
|
|
<i class="fa fa-solid fa-crow" aria-hidden="true"></i>
|
|
<span class="menu-text">home</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/about/" title="about Lee">
|
|
<i class="fa-regular fa-user" aria-hidden="true"></i>
|
|
<span class="menu-text">about</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/contact/" title="contact Lee">
|
|
<i class="fa-solid fa-envelope-open-text" aria-hidden="true"></i>
|
|
<span class="menu-text">contact</span>
|
|
</a>
|
|
</li>
|
|
|
|
</ul>
|
|
</nav>
|
|
|
|
</header>
|
|
|
|
|
|
<main id="main">
|
|
|
|
|
|
|
|
|
|
|
|
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
|
|
<article>
|
|
<h1 id="screen-reader-optimizations">screen reader optimizations</h1>
|
|
|
|
<div class="post-metadata">
|
|
<p>
|
|
posted on
|
|
<time datetime="2026-02-06">February 6, 2026</time>
|
|
by Lee Cattarin
|
|
</p>
|
|
|
|
|
|
<ul class="post-tags">
|
|
|
|
<li>
|
|
|
|
<a href="/tags/software/">software</a>
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
|
|
<img src="/img/crow.jpg" alt="Image unrelated to post. A crow poses on driftwood against a whitish sky." loading="lazy" decoding="async" width="1000" height="666">
|
|
|
|
|
|
<h2 id="context">context</h2>
|
|
<p>recently, I've been working on a <a href="https://inherentlee.codeberg.page/spoonfairies/" target="_blank" rel="external">website for a project called spoonfairies</a>. On the providers page, we list a series of names along with their pronouns, location, and services offered. Visually, it looks like this:</p>
|
|
<p><img src="/img/spoonfairies-provider.png" alt="A provider listing from spoonfairies. On the top row of text, it shows the provider's name in large purple text, then their pronouns in slightly opaque white and slightly smaller font, then aligned on the right, a map pin emoji and their general location in standard size white text. On the second row of text, it lists a few services the provider offers, comma separated." loading="lazy" decoding="async" width="1000" height="147"></p>
|
|
<h2 id="pronouns">pronouns</h2>
|
|
<p>at first, all three pieces of information in the top row had no extra styling - it was just a line of text with the same color and size throughout. The location bit also didn't exist yet, so we're going to briefly ignore it. Screenreader testing (with NVDA, specifically) informed me that, when reading through a long list of providers, parentheses become <em>very</em> irritating. Imagine hearing the following:</p>
|
|
<blockquote>
|
|
<p>Lorem Ipsum left parentheses she slash her right parentheses web accessiblity webdev. Dolor Sit left parentheses they slash them right parentheses housecleaning. Amet Consectetur left parentheses he slash him right parentheses webdev spreadsheets software.</p>
|
|
</blockquote>
|
|
<p>...ad nauseam. Kinda irritating.</p>
|
|
<h3 id="the-fix">the fix</h3>
|
|
<p>put the pronouns in a span that provides special styling, and use <code>::before</code> and <code>::after</code> to apply parentheses.</p>
|
|
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/providers/lorem-ipsum<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
|
|
Lorem Ipsum
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pronouns<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>she/her<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span></code></pre>
|
|
<pre class="language-css"><code class="language-css"><span class="token selector">.pronouns::before</span> <span class="token punctuation">{</span>
|
|
<span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">"("</span> / <span class="token string">""</span><span class="token punctuation">;</span>
|
|
<span class="token punctuation">}</span>
|
|
<span class="token selector">.pronouns::after</span> <span class="token punctuation">{</span>
|
|
<span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">")"</span> / <span class="token string">""</span><span class="token punctuation">;</span>
|
|
<span class="token punctuation">}</span></code></pre>
|
|
<p><strong>the slash is the magic there.</strong> The string before the slash indicates the visual content, and the string after the slash is the alternative text content. I went happily on my way.</p>
|
|
<p>plus, this is neat - now I can style the pronouns separately. Let's make them the standard text color rather than the link color, and a bit smaller, and a smidge opaque... nice.</p>
|
|
<h2 id="location">location</h2>
|
|
<p>ooh, time to implement locations! I did my same ol' trick.</p>
|
|
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/providers/lorem-ipsum<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
|
|
Lorem Ipsum
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pronouns<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>she/her<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>location<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Tacoma<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span></code></pre>
|
|
<pre class="language-css"><code class="language-css"><span class="token selector">.location::before</span> <span class="token punctuation">{</span>
|
|
<span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">"📍"</span> / <span class="token string">"is based out of"</span><span class="token punctuation">;</span>
|
|
<span class="token punctuation">}</span></code></pre>
|
|
<p>I even added actual alternative text rather than an empty string to provide some context. Pronouns, I figured, could exist without much context, as it's pretty common for them to follow directly after names in introductions, but location isn't as much of a given.</p>
|
|
<p>again, style em up nice, more of a standard text look, right-aligned. Cool.</p>
|
|
<h2 id="a-bigger-problem-than-parentheses">a bigger problem than parentheses</h2>
|
|
<p>...then I did some screen reader testing. Which I should have done directly after the pronouns bit. Turns out, I wasn't thrilled with what the <code><span></code>s did.</p>
|
|
<p>at least with fairly default settings in NVDA, the <code><span></code>s broke up the way the link was read out. Suddenly, I was getting:</p>
|
|
<blockquote>
|
|
<p>visited link Lorem Ipsum visited link she slash her visited link Tacoma</p>
|
|
</blockquote>
|
|
<p>this is all one link, mind you. The <code><a></code> tag isn't broken into three links. But the <code><span></code>s apparently break up the screen reader output anyway (in NVDA, that's a continual caveat).</p>
|
|
<p>ooookay... what next?</p>
|
|
<h3 id="total-overhaul">total overhaul</h3>
|
|
<p>I moved away from my <code>content</code> approach entirely (well, I kept it around as a failsafe, but it's not running the show now). Instead, I switched over to an <code>aria-label</code> for the whole link.</p>
|
|
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/providers/lorem-ipsum<span class="token punctuation">"</span></span>
|
|
<span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Lorem Ipsum she/her is based out of Tacoma<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
|
|
Lorem Ipsum
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pronouns<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>she/her<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>location<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Tacoma<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
|
|
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span></code></pre>
|
|
<p>(technically, all this is templated to hell and back. I would hope that's obvious given I'm talking about <em>lists</em> of these entries.)</p>
|
|
<p>now, after more screen reader testing, it reads out smoothly. The <code>aria-label</code> precludes the actual link text and cleanly says what needs to be said, with nothing breaking up the text and the whole thing easily recognized as one link. <em>And</em> I've got my fancy styling. Sweet.</p>
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<nav aria-label="pagination">
|
|
<ol class="pagination post-pagination">
|
|
|
|
<li class="older">
|
|
<a href="/charlie-the-alpaca-handspun/">
|
|
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> charlie the alpaca handspun
|
|
</a>
|
|
</li>
|
|
|
|
|
|
<li class="newer">
|
|
<a href="/crow/">
|
|
crow <i class="fa-solid fa-hand-point-right" aria-hidden="true"></i>
|
|
</a>
|
|
</li>
|
|
|
|
</ol>
|
|
</nav>
|
|
|
|
|
|
|
|
<hr>
|
|
|
|
|
|
|
|
|
|
<section class="related-posts">
|
|
<h2 id="related-posts">related posts</h2>
|
|
<ol id="postlist">
|
|
|
|
<li class="post">
|
|
<a class="postlink" href="/backend-accessibility/">
|
|
|
|
<img src="/img/camelCase-print.jpg" alt="A carved stamp next to its print. The print reads '#camelCase' in a slightly formal-looking italic font." loading="lazy" decoding="async" width="1000" height="750">
|
|
|
|
<h2 id="backend-accessibility">backend accessibility</h2>
|
|
<ul class="postlist-tags">
|
|
|
|
<li>software</li>
|
|
|
|
</ul>
|
|
</a>
|
|
</li>
|
|
|
|
<li class="post">
|
|
<a class="postlink" href="/gender-in-data-models/">
|
|
|
|
<img src="/img/peony.jpg" alt="Image unrelated to post. A light pink peony in full bloom, close up." loading="lazy" decoding="async" width="1000" height="666">
|
|
|
|
<h2 id="gender-in-data-models">gender in data models</h2>
|
|
<ul class="postlist-tags">
|
|
|
|
<li>gender</li>
|
|
|
|
<li>software</li>
|
|
|
|
<li>highlight</li>
|
|
|
|
</ul>
|
|
</a>
|
|
</li>
|
|
|
|
<li class="post">
|
|
<a class="postlink" href="/domain-and-site-setup/">
|
|
|
|
<img src="/img/crinkly-mushrooms.jpg" alt="Picture unrelated to post. Some crinkly brown-orange mushrooms in vibrant green grass." loading="lazy" decoding="async" width="1000" height="750">
|
|
|
|
<h2 id="domain-and-site-setup">domain and site setup</h2>
|
|
<ul class="postlist-tags">
|
|
|
|
<li>software</li>
|
|
|
|
</ul>
|
|
</a>
|
|
</li>
|
|
|
|
</ol>
|
|
|
|
</section>
|
|
|
|
|
|
</heading-anchors>
|
|
|
|
</main>
|
|
|
|
<footer>
|
|
<ul>
|
|
<li>
|
|
<a href="/colophon/">
|
|
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>
|
|
|
|
|
|
<!-- This page `/screen-reader-optimizations/` was built on 2026-02-20T20:23:43.796Z -->
|
|
<body>
|
|
</body></body></html>
|