Files
leecat.art-site/framer-accessibility/index.html
T

2080 lines
55 KiB
HTML
Raw Normal View History

2026-09-08 17:26:33 +00:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>framer 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="framer accessibility | hello hello">
<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:url" content="https://leecat.art/framer-accessibility/">
<meta property="og:image" content="/img/framer.png">
<meta property="og:image:alt" content="A screenshot of the Framer projects page showing part of the tile for a very rudimentary project titled Accessibility Test. It has a huge title that just reads Title, a basic form, and some pictures of my dog as notable features.">
<meta name="generator" content="Eleventy v3.1.2">
<meta itemprop="ai-content-declaration:version" content="1.0.0">
<meta itemprop="ai-content-declaration:level" content="none">
<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>
2026-09-08 17:40:59 +00:00
2026-09-11 18:57:10 +00:00
2026-09-08 17:26:33 +00:00
<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;
margin-left: auto;
}
.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);
}
#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 {
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;
}
.blur {
filter: blur(.75rem);
clip-path: inset(0 round 1rem);
}
.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: 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;
}
img[tabindex="0"] {
cursor: pointer;
}
img[tabindex="0"]:focus-visible {
outline: .15rem solid var(--color-teal);
}
dialog {
margin: auto;
flex-flow: column;
background: transparent;
border: none;
position: absolute;
}
dialog[open] {
display: flex;
}
dialog::backdrop {
background-color: rgba(from var(--color-bg) r g b / .8);
backdrop-filter: blur(4px);
}
body:has(dialog[open]) {
overflow: hidden;
}
.close-dialog {
background-color: rgba(from var(--color-teal) r g b / .35);
font-size: 1.5rem;
padding: 0 .5rem .15rem;
border-radius: 1rem;
color: var(--color-teal);
box-shadow: .15rem .15rem var(--color-shadow);
border: .08rem solid var(--color-teal);
align-self: flex-end;
justify-self: flex-start;
margin: 0 .1rem;
/* Click animation handling */
position: relative;
top: 1rem;
left: -.15rem;
transition: top .1s ease-in, left .1s ease-in;
}
.close-dialog:focus-visible {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
@media (any-hover: hover) {
.close-dialog:hover {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
}
@media (forced-colors: active) {
.close-dialog:focus-visible {
outline-offset: .08rem;
outline: .08rem solid;
}
@media (any-hover: hover) {
.close-dialog:hover {
outline-offset: .08rem;
outline: .08rem solid;
}
}
}
/* Click animation */
.close-dialog:active {
top: 1.1rem;
left: -.05rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
.modal-img {
max-height: calc(100vh - 2rem);
object-fit: contain;
width: auto;
max-width: 90%;
margin: 0 auto;
}
: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));
}
2026-09-09 20:38:19 +00:00
:root:has(#theme-light:checked) {
color-scheme: light;
}
:root:has(#theme-dark:checked) {
color-scheme: dark;
}
2026-09-08 17:26:33 +00:00
/* 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;
margin: 0 auto;
}
/* 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);
font-size: .8em;
vertical-align: .1em;
}
span.ha-placeholder {
opacity: .55;
}
/* Lists */
::marker {
color: var(--color-pink);
}
ul, ol, dl, li {
margin-left: 1rem;
}
@media (max-width: 650px) {
ul, ol, dl {
margin-left: 0;
}
}
li {
2026-09-09 20:38:19 +00:00
line-height: 1.2;
2026-09-08 17:26:33 +00:00
margin-top: .65rem;
margin-bottom: .65rem;
}
li ul, li ol {
margin: .5rem 0;
2026-09-08 17:34:46 +00:00
margin-left: 1rem;
}
@media (max-width: 650px) {
li ul, li ol {
margin-left: 0;
}
2026-09-08 17:26:33 +00:00
}
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);
margin: 1.25rem 0;
}
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 {
background-color: var(--color-bg);
padding: .75rem 0;
border-bottom: thick solid var(--color-teal);
box-shadow: 0 .25rem .15rem var(--color-shadow);
2026-09-11 18:57:10 +00:00
display: grid;
grid-template-columns: auto;
2026-09-08 17:26:33 +00: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;
2026-09-11 18:57:10 +00:00
margin: -.125rem 10%;
2026-09-08 17:26:33 +00:00
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);
margin-top: 2rem;
}
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);
}
2026-09-09 20:38:19 +00:00
/* color picker */
#theme {
display: flex;
justify-content: center;
margin: .5rem auto 0;
padding: 0 .75rem .5rem;
width: fit-content;
border: .125rem solid var(--color-teal);
border-radius: 2rem;
}
#theme legend {
margin: 0 auto;
color: var(--color-teal);
}
footer label {
cursor: pointer;
border: .125rem solid var(--color-pink);
padding: 0 .25rem;
color: var(--color-pink);
background-color: var(--color-bg); /* this stops the box shadows from showing through */
box-shadow: .15em .15em var(--color-shadow);
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
footer input:checked + label {
color: var(--color-blue);
border-color: var(--color-blue);
}
footer label i {
color: var(--color-teal);
}
footer input:checked + label i {
color: var(--color-pink);
}
@media (any-hover: hover) {
footer label:hover,
footer input:checked + label:hover,
footer label:hover i,
footer input:checked + label:hover i {
color: var(--color-bg);
}
footer label:hover {
background-color: var(--color-pink);
}
footer input:checked + label:hover {
background-color: var(--color-teal);
}
}
footer input:focus-visible + label,
footer input:focus-visible + label i {
color: var(--color-bg);
background-color: var(--color-pink);
border-color: var(--color-pink);
}
footer input:checked:focus-visible + label,
footer input:checked:focus-visible + label i {
color: var(--color-bg);
background-color: var(--color-teal);
border-color: var(--color-teal);
}
footer label:active {
top: .09em;
left: .09em;
box-shadow: .05em .05em var(--color-shadow);
}
label.theme-light {
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
border-right: none;
padding-left: .35rem;
}
label.theme-auto {
}
label.theme-dark {
border-top-right-radius: 1rem;
border-bottom-right-radius: 1rem;
border-left: none;
padding-right: .35rem;
}
footer input {
/* To allow screen reader to still access these. */
/* From https://lyra.horse/blog/2025/08/you-dont-need-js/ */
opacity: 0;
position: absolute;
pointer-events: none;
}
2026-09-08 17:26:33 +00:00
/* 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: center;
gap: .5rem;
}
.webring ul,
.webring li {
margin-left: 0;
}
2026-09-11 18:57:10 +00:00
/* Handle color schemes */
:root:has(#theme-light:checked) .dark {
display: none;
}
:root:has(#theme-dark:checked) .light {
display: none;
}
@media (prefers-color-scheme: light) {
:root:not(:has(#theme-dark:checked)) .dark {
display: none;
}
}
@media (prefers-color-scheme: dark) {
:root:not(:has(#theme-light:checked)) .light {
display: none;
}
}
.searchbox {
margin: 1rem auto 0;
}
pagefind-searchbox {
/* Shadows */
--pf-scroll-shadow: var(--color-shadow) !important;
--pf-shadow-sm: .15rem .15rem 0 var(--color-shadow) !important;
--pf-shadow-md: .25rem .25rem 0 var(--color-shadow) !important;
--pf-shadow-lg: 1rem 1rem 0 var(--color-shadow) !important;
/* Typography */
--pf-font: var(--font-family) !important;
/* Border */
--pf-border-radius: 1.5rem !important;
/* Focus */
--pf-outline-width: .125rem !important;
--pf-outline-offset: 0 !important;
/* Width */
--pf-searchbox-max-width: min(550px, 92vw) !important;
}
.light pagefind-searchbox {
--pf-text: var(--color-dark) !important;
--pf-text-secondary: var(--color-dark-alt) !important;
--pf-text-muted: var(--color-dark-alt) !important;
--pf-background: var(--color-light-alt) !important;
--pf-border: var(--color-pink-dark) !important;
--pf-border-focus: var(--color-pink-dark) !important;
--pf-skeleton: var(--color-light) !important;
--pf-skeleton-shine: var(--color-dark) !important;
--pf-hover: var(--color-light) !important;
--pf-mark: var(--color-teal-dark) !important;
--pf-outline-focus: var(--color-teal-dark) !important;
/* Error */
--pf-error-bg: var(--color-light-alt) !important;
--pf-error-border: var(--color-red-dark) !important;
--pf-error-text: var(--color-red-dark) !important;
--pf-error-text-secondary: var(--color-red-dark) !important;
}
.dark pagefind-searchbox {
--pf-text: var(--color-light) !important;
--pf-text-secondary: var(--color-light-alt) !important;
--pf-text-muted: var(--color-light-alt) !important;
--pf-background: var(--color-dark-alt) !important;
--pf-border: var(--color-pink-light) !important;
--pf-border-focus: var(--color-pink-light) !important;
--pf-skeleton: var(--color-dark) !important;
--pf-skeleton-shine: var(--color-dark) !important;
--pf-hover: var(--color-dark) !important;
--pf-mark: var(--color-teal-light) !important;
--pf-outline-focus: var(--color-teal-light) !important;
/* Error */
--pf-error-bg: var(--color-dark-alt) !important;
--pf-error-border: var(--color-red-light) !important;
--pf-error-text: var(--color-red-light) !important;
--pf-error-text-secondary: var(--color-red-light) !important;
}
pagefind-searchbox input {
border-width: .125rem !important;
}
pagefind-searchbox input:not(:focus-visible) {
box-shadow: var(--pf-shadow-md) !important;
}
2026-09-12 17:51:15 +00:00
@media (max-width: 650px) {
.pf-trigger-shortcut {
display: none !important;
}
}
2026-09-11 18:57:10 +00:00
.pf-searchbox-dropdown,
.pf-searchbox-footer {
border-width: .125rem !important;
}
.pf-error {
margin-top: .5rem !important;
}
2026-09-08 17:40:59 +00:00
/* 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: .9em;
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-09-08 17:26:33 +00:00
@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-09-09 20:38:19 +00:00
2026-09-08 17:26:33 +00:00
<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);
}
}
}
/* no longer used, using aria-labelledby instead - see getAnchorElement
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 aria-labelledby=${heading.id}>${content}</span>`; // CHANGED HERE
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 }
/* don't even bother on mobile */
if (window.innerWidth > 650) {
const dialog = document.querySelector("dialog");
const closeButton = document.querySelector(".close-dialog");
const hero = document.querySelector(".hero");
hero.tabIndex = 0;
hero.addEventListener("click", (e) => dialog.showModal());
hero.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
dialog.showModal();
}
});
closeButton.addEventListener("click", (e) => dialog.close());
closeButton.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
dialog.close();
}
});
2026-09-09 20:38:19 +00:00
}
const THEME = "theme";
const saveTheme = (themeValue) => {
localStorage.setItem(THEME, themeValue);
};
const themeLight = document.getElementById("theme-light");
themeLight.addEventListener("input", (e) => {
saveTheme(themeLight.value);
});
const themeAuto = document.getElementById("theme-auto");
themeAuto.addEventListener("input", (e) => {
saveTheme(themeAuto.auto);
});
const themeDark = document.getElementById("theme-dark");
themeDark.addEventListener("input", (e) => {
saveTheme(themeDark.value);
});
var currentThemeValue = localStorage.getItem(THEME);
switch (currentThemeValue) {
case "light":
themeLight.checked = true;
2026-09-09 21:03:37 +00:00
themeAuto.checked = false;
2026-09-09 20:38:19 +00:00
break;
case "dark":
themeDark.checked = true;
2026-09-09 21:03:37 +00:00
themeAuto.checked = false;
2026-09-09 20:38:19 +00:00
break;
2026-09-09 21:03:37 +00:00
case "auto":
2026-09-09 20:38:19 +00:00
default:
themeAuto.checked = true;
2026-09-08 17:26:33 +00:00
}</script>
2026-09-11 18:57:10 +00:00
<link href="/pagefind/pagefind-component-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-component-ui.js" type="module"></script>
2026-09-08 17:26:33 +00:00
</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="home">
<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>
2026-09-11 18:57:10 +00:00
<div class="dark searchbox" data-pf-theme="dark">
<pagefind-searchbox></pagefind-searchbox>
</div>
<div class="light searchbox">
<pagefind-searchbox></pagefind-searchbox>
</div>
2026-09-08 17:26:33 +00:00
</header>
2026-09-12 17:14:18 +00:00
<main id="main">
2026-09-08 17:26:33 +00:00
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
2026-09-12 17:14:18 +00:00
<article data-pagefind-body="">
2026-09-08 17:26:33 +00:00
<h1 id="framer-accessibility">framer accessibility</h1>
<div class="post-metadata">
<p>
posted on
<time datetime="2026-09-08">September 8, 2026</time>
by Lee Cattarin
</p>
<ul class="post-tags">
<li>
<a href="/tags/software/">software</a>
</li>
</ul>
</div>
<dialog closedby="any" aria-label="image modal" tabindex="-1">
<button class="close-dialog" autofocus="" aria-label="close the image modal">&times;</button>
<img class="modal-img" src="/img/framer.png" alt="A screenshot of the Framer projects page showing part of the tile for a very rudimentary project titled Accessibility Test. It has a huge title that just reads Title, a basic form, and some pictures of my dog as notable features." loading="lazy" decoding="async" width="841" height="532">
</dialog>
<img class="hero" src="/img/framer.png" alt="A screenshot of the Framer projects page showing part of the tile for a very rudimentary project titled Accessibility Test. It has a huge title that just reads Title, a basic form, and some pictures of my dog as notable features." loading="lazy" decoding="async" width="841" height="532">
<p>This is a work in progress post originally written for a colleague beginning to explore <a href="https://framer.com" target="_blank" rel="external">Framer</a>, a visual site designer with CMS. It walks through some major web accessibility topics, first generally and then as they relate to Framer, with links to relevant resources.</p>
<h2 id="accessible-from-the-beginning">Accessible from the beginning</h2>
<p>Accessibility <strong>must</strong> be built from the start of a project. Retrofitting a project to be accessible requires far more effort than designing for accessibility from the start. Building with accessibility in mind prevents time consuming and expensive reworks.</p>
<h2 id="definitions">Definitions</h2>
<ul>
<li><strong>Assistive technology (AT):</strong> &quot;technology used by individuals with (varying levels and kinds of) disabilities in order to perform functions that might otherwise be difficult or impossible. It is an umbrella term for a wide variery of tools, including tools that enable access to information on the Web. ... Keyboards, screen readers, braille displays, voice recognition software, switch controls, and screen magnifiers are examples of high-tech assistive technology. Operating system and browser accessibility display modes (such as dark mode, and Windows High Contrast Mode) are also a form of assistive technology.&quot; (Sara Souiedan, <a href="https://practical-accessibility.today" target="_blank" rel="external">Practical Accessibility</a> - content gated behind paid course)
<ul>
2026-09-12 17:51:15 +00:00
<li>See <a href="https://tetralogical.com/blog/2021/12/24/browsing-with-assistive-technology-videos/" target="_blank" rel="external">Browsing with Assistive Technology</a> for an overview of some common methods/uses of AT.</li>
2026-09-08 17:26:33 +00:00
</ul>
</li>
<li><strong>Screen readers (SR):</strong> A type of AT aimed at Blind/low vision users which reads aloud page content. Notably, SRs do not simply read page content in order; they have complex shortcuts which allow users to summarize links on a page, skip headline-by-headline, move to landmark sections, and more.</li>
<li><strong>Web Content Accessibility Guidelines (WCAG):</strong> &quot;the standard that defines how to make Web content more accessible to people with disabilities. It defines a baseline set of guidelines that digital content must meet in order to provide minimum levels of accessibility.&quot; (Sara Souiedan, Practical Accessibility again)</li>
</ul>
<h2 id="semantic-html">Semantic HTML</h2>
<p>Most HTML elements are semantic, that is, they convey meaning. Labeling something an <code>&lt;h1&gt;</code> doesn't just style it like a header; it also means that assistive tech can understand it to be a header (this also helps with SEO - a lot of things that improve accessibility improve SEO as well). See the Mozilla docs for a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics#semantic_elements" target="_blank" rel="external">list of semantic HTML elements</a>.</p>
<p>Notably, <code>&lt;div&gt;</code>s and <code>&lt;span&gt;</code>s are not semantic and convey no meaning. They are used for styling alone.</p>
<h3 id="semantic-html-in-framer">Semantic HTML in Framer</h3>
<p>Semantic HTML elements can be applied to any Framer element using the &quot;Accessibility&quot; section in the right-hand sidebar. Read about <a href="https://www.framer.com/help/articles/semantic-tags-navigation-footer/" target="_blank" rel="external">using semantic tags for navigation and footers</a> - this applies to far more than the two elements mentioned.</p>
<h2 id="reading-order">Reading order</h2>
<p>The order of elements in an HTML document is referred to as the <strong>reading order</strong>. While visual styling can change the flow of elements in a page, SRs will follow reading order. It's important for your reading order to match the visual flow of elements on the page for two reasons:</p>
<ol>
<li>Those elements are presumably in visual order for a reason! A different reading order may make them confusing or totally unfollowable.</li>
<li>Low vision or partially sighted users who rely on both screen visuals and SRs will suffer if the visual content does not match the audio content.</li>
</ol>
<h3 id="reading-order-in-framer">Reading order in Framer</h3>
<p>Reading order is maintained in Framer in two ways:</p>
<ol>
<li>When layouts are applied effectively via the &quot;Layout&quot; section in the right-hand sidebar, the visual order of the elements guides the reading order.</li>
<li>In the absence of the above, the order of the elements in the left-hand sidebar determines reading order. Notably, reading order seems to go from bottom to top in this case rather than the intuitive top to bottom.</li>
</ol>
<h2 id="keyboard-navigation">Keyboard navigation</h2>
<p>Following from reading order, we reach the concept of keyboard navigation. The ability to navigate the web without a mouse is core to several forms of AT as well as improving quality of life for savvy internet users - and anyone filling out web forms. Using the <code>TAB</code> key to move through a webpage navigates through the reading order, landing on every interactable element it finds. What is an interactable element? There are four kinds:</p>
<ol>
<li><strong>Links</strong> bring a user to a new page (or sometimes a different section of the same page)</li>
<li><strong>Buttons</strong> perform an action without changing pages</li>
<li><strong>Form controls</strong> allow user input</li>
<li>Any element with the <strong><code>tabindex</code></strong> attribute set can be tabbed to. Notably, if tabindex is set, interacting with the element should always do something. Use tabindex <em>extremely</em> sparingly and with intention.</li>
</ol>
<h3 id="keyboard-navigation-in-framer">Keyboard navigation in Framer</h3>
<p>Links, buttons, and form controls are tabbable by default. When tagged as the correct HTML element, they will function as expected.</p>
<p><a href="https://www.framer.com/help/articles/tab-order-customization/" target="_blank" rel="external">Framer supports adding the tabindex attribute</a>. If you <em>must</em> do this for some reason, make sure to set its value to 0 - Framer defaults to 1. 0 will maintain the reading order established by layout and the left-hand sidebar; 1 will place the tabbable element first in the tabbing order.</p>
<h2 id="keyboard-focus">Keyboard focus</h2>
<p>When navigating with a keyboard, there should always be visual indication on the screen of where we have navigated to. This indication is referred to as <strong>focus</strong> or the <strong>focus indicator</strong>. Browsers have default focus stylings, usually an outline around the element, but focus can - and frequently should - be customized with CSS. Default focus outlines may not have very high contrast against the elements in your website, and, unless you are testing with many browsers, you can't easily confirm that all default focus indication works well with your color scheme. Additionally, default focus may clash with your site and impact how elements like your forms look in use.</p>
<h3 id="keyboard-focus-in-framer">Keyboard focus in Framer</h3>
<p>Framer does not currently allow for customizing keyboard focus in the editor. It must be done using <a href="https://www.framer.com/help/articles/how-to-add-custom-code/" target="_blank" rel="external">custom CSS</a>.</p>
<p>In my tests, it was possible to clip out the browser default focus outline with certain padding settings. Review your site by tabbing through the page(s), and ensure that a full outline surrounds all interactable elements. If an outline is missing, consider adjusting the padding around that element.</p>
<h2 id="skip-links">Skip links</h2>
<p>Skip links allow keyboard users to bypass large repeated blocks of content (such as a top nav). Without a skip link, a keyboard user will not stay long on a site - after moving to a new page, they may realize they have to tab through the entire nav again and leave.</p>
<p>Skip links are generally invisible to mouse users, and only appear when tabbing around a page. To see an example, open <a href="https://webaim.org/" target="_blank" rel="external">WebAIM (Web Accessibility in Mind)</a> and hit the <code>TAB</code> key. Notice the red box that appears in the upper left that reads &quot;skip to main content.&quot;</p>
<h3 id="skip-links-in-framer">Skip links in Framer</h3>
<p>Skip links in Framer require custom code, both React and CSS.</p>
<h2 id="text-alternatives">Text alternatives</h2>
<p>Text alternatives - alternative text, captioning, audio descriptions, etc. - are the most commonly thought of form of web accessibility. You likely need no further explanation of what they are and why they matter, but they can be rather daunting to write. I recommend reading <a href="https://adrianroselli.com/2024/05/my-approach-to-alt-text.html" target="_blank" rel="external">Adrian Roselli's approach to writing alt text</a>.</p>
<h3 id="text-alternatives-in-framer">Text alternatives in Framer</h3>
<p>Text alternatives can be easily set by double clicking on the media item in question to open the editing dialog.</p>
<h2 id="color">Color</h2>
<p>There are a couple areas to consider when it comes to color:</p>
<ol>
<li><strong>Light and dark modes</strong> are both accessibility needs, and a site that only offers one limits access of some potential users.</li>
<li><strong>Color contrast</strong> will improve users' abilities to view and read your site. WebAIM covers <a href="https://webaim.org/articles/contrast/" target="_blank" rel="external">color contrast as it relates to WCAG</a>. While you can use <a href="https://webaim.org/resources/contrastchecker/" target="_blank" rel="external">WebAIM's contrast checker</a>, I recommend finding a more fully-featured color design tool that incorporates contrast checking, luminance comparison, color vision simulation, and more. My personal tool of choice is <a href="https://atmos.style/" target="_blank" rel="external">Atmos</a>.</li>
<li>Ensuring that <strong>color alone is not used to communicate meaning</strong> helps users with various color deficiencies or certain AT users access your content and understand what is going on. For example, a form that highlights inputs in red when they are improperly filled in should <em>also</em> provide error messages.</li>
</ol>
<h3 id="color-in-framer">Color in Framer</h3>
<p>Regarding item 1, Framer supports <a href="https://www.framer.com/academy/lessons/light-dark-mode" target="_blank" rel="external">light and dark modes for your color styles</a>. Items 2 &amp; 3 are handled independently of Framer.</p>
<h2 id="responsive-design">Responsive design</h2>
<p>A huge percentage of web browsing comes from mobile devices or tablets - and that's not to mention ultrawide monitors. Our websites <strong>must</strong> take this into account.</p>
<h3 id="responsive-design-in-framer">Responsive design in Framer</h3>
<p>Read about <a href="https://www.framer.com/blog/design-responsive-websites/" target="_blank" rel="external">how to design responsive websites in Framer</a>.</p>
<h2 id="forms">Forms</h2>
<p>Accessible forms can be <em>hard</em>. They bring in challenges relating to labeling, validation, reading order, and color design.</p>
<ol>
<li><strong>Labeling:</strong> all form controls should have associated labels. Note that placeholder text does not constitute a label! Best practice is that labels be visible, but in some cases (for example a search bar which can be widely visually understood via placeholder text and a magnifying glass icon) you may want an invisible label. <a href="https://www.w3.org/WAI/tutorials/forms/labels/#hiding-label-text" target="_blank" rel="external">Invisible labels can be achieved in several ways</a>.</li>
<li><strong>Validation:</strong> thankfully, form control attributes can control validation for many common scenarios such as email, phone number, etc. Its also possible to control whether an input is required.</li>
<li><strong>Reading order:</strong> its important to ensure that tabbing through your form follows the same order as the visual items.</li>
<li><strong>Color:</strong> as discussed earlier, ensure that errors are not communicated solely by color!</li>
</ol>
<h3 id="forms-in-framer">Forms in Framer</h3>
<ol>
<li><strong>Labeling:</strong> follow the pattern that Framer uses in its basic form for labeling. This will nest the form control inside the label.</li>
</ol>
<ul>
<li>If you want an invisible label, you will need to write custom CSS.</li>
</ul>
<ol>
<li><strong>Validation:</strong> In the &quot;Input&quot; section of the right-hand sidebar, there is a dropdown for input type. There is also a toggle for whether the input is required.</li>
</ol>
<ul>
<li>Note that setting an input to required will not add an asterisk or any other similar visual pattern to the label - doing this yourself is highly recommended. It also will not provide visual feedback to the user until clicking the &quot;Submit&quot; button, at which point an error message comes up - this can be a painful pattern for longer forms. Further visual feedback requires custom CSS.</li>
</ul>
<ol>
<li><strong>Reading order:</strong> follow the guidance for reading order discussed earlier.</li>
<li><strong>Color:</strong> since visual feedback for required feeds does not exist by default, this only becomes a concern if you write custom CSS.</li>
</ol>
<h2 id="other-considerations">Other considerations</h2>
<ul>
<li>Links should have <strong>anchor text</strong> that accurately represents what they point to (review this document for examples - note how links encapsulate the content they lead to).
<ul>
<li>Counterexample: a newspaper page with many &quot;continue reading&quot; links. In this case, you would use the title or aria-label attributes to give a better programmatic name to the link that isn't visible to sighted users. Framer doesn't support the title attribute, so use <a href="https://www.framer.com/help/articles/improving-accessibility-with-aria-labels/" target="_blank" rel="external">aria-label</a> (found under &quot;Accessibility&quot; in the right-hand sidebar).</li>
</ul>
</li>
<li>Links should always be <strong>visibly indicated as links</strong> - underlines are traditional, and traditional is understandable by users. If you choose otherwise, ensure you aren't <a href="#color">distinguishing only by color</a>!
<ul>
<li>If your links are underlined, avoid underlining other text elements to avoid confusion.</li>
</ul>
</li>
<li>Some users need <strong>reduced or removed animations</strong>. There is a setting in Framer to <a href="https://www.framer.com/help/articles/reduced-motion-settings/" target="_blank" rel="external">allow reduced motion support</a>. It's turned off by default.</li>
</ul>
</article>
<nav aria-label="pagination">
<ol class="pagination post-pagination">
<li class="older">
<a href="/pink-lime-and-grey-shawl/">
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> pink, lime, and grey shawl
</a>
</li>
2026-09-08 23:06:13 +00:00
<li class="newer">
<a href="/mouse-coin-purse/">
mouse coin purse <i class="fa-solid fa-hand-point-right" aria-hidden="true"></i>
</a>
</li>
2026-09-08 17:26:33 +00:00
</ol>
</nav>
<hr>
<section class="related-posts">
<h2 data-ha-exclude="" id="related-posts">related posts</h2>
<ol id="postlist">
2026-09-10 21:23:21 +00:00
<li class="post">
2026-09-12 17:51:15 +00:00
<a class="postlink" href="/comparing-text-editors/">
<h2 data-ha-exclude="" id="comparing-text-editors">comparing text editors </h2>
2026-09-10 21:23:21 +00:00
<ul class="postlist-tags">
<li>software</li>
</ul>
2026-09-12 17:51:15 +00: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-09-10 21:23:21 +00:00
</a>
</li>
2026-09-11 18:57:10 +00:00
<li class="post">
2026-09-12 17:51:15 +00:00
<a class="postlink" href="/backend-accessibility/">
<h2 data-ha-exclude="" id="backend-accessibility">backend accessibility </h2>
2026-09-11 18:57:10 +00:00
<ul class="postlist-tags">
<li>software</li>
</ul>
2026-09-12 17:51:15 +00:00
<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">
2026-09-11 18:57:10 +00:00
</a>
</li>
<li class="post">
2026-09-12 17:51:15 +00:00
<a class="postlink" href="/my-favorite-git-flag/">
<h2 data-ha-exclude="" id="my-favorite-git-flag">my favorite git flag </h2>
2026-09-11 18:57:10 +00:00
<ul class="postlist-tags">
<li>software</li>
</ul>
2026-09-12 17:51:15 +00:00
<img src="/img/shelf-mushrooms.jpg" alt="Picture unrelated to post. Creamy beige shelf mushrooms on a mossy tree trunk." loading="lazy" decoding="async" width="1000" height="666">
2026-09-11 18:57:10 +00:00
</a>
</li>
2026-09-08 17:26:33 +00:00
</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, copyright 2022-2026">
hello hello from Lee Cattarin, © 2022-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>
2026-09-09 20:38:19 +00:00
<fieldset id="theme">
<legend>color scheme</legend>
<input type="radio" id="theme-light" name="theme" value="light">
<label class="theme-light" for="theme-light">
<i class="fa-regular fa-sun" aria-hidden="true"></i>
light
</label>
2026-09-09 21:03:37 +00:00
<input type="radio" id="theme-auto" name="theme" value="auto" checked="">
2026-09-09 20:38:19 +00:00
<label class="theme-auto" for="theme-auto">
auto
</label>
<input type="radio" id="theme-dark" name="theme" value="dark">
<label class="theme-dark" for="theme-dark">
dark
<i class="fa-regular fa-moon" aria-hidden="true"></i>
</label>
</fieldset>
2026-09-08 17:26:33 +00:00
</footer>
2026-09-12 17:51:15 +00:00
<!-- This page `/framer-accessibility/` was built on 2026-09-12T17:51:00.657Z -->
2026-09-08 17:26:33 +00:00
</body>
</html>