<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.nicchan.me</id>
    <title>Nic Chan</title>
    <updated>2026-02-13T02:17:35.081Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Nic Chan</name>
    </author>
    <link rel="alternate" href="https://www.nicchan.me"/>
    <link rel="self" href="https://www.nicchan.me/feed.xml"/>
    <subtitle>The personal blog of Nic Chan, a front-end web developer.</subtitle>
    <logo>https://www.nicchan.me/pixels/portrait.png</logo>
    <icon>https://www.nicchan.me/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[Wishcessibility]]></title>
        <id>https://www.nicchan.me/blog/wishcessibility/</id>
        <link href="https://www.nicchan.me/blog/wishcessibility/"/>
        <updated>2026-02-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[No recycling was contaminated in the creation of this post.]]></summary>
        <content type="html"><![CDATA[<p>I was listening to the excellent (now completed) <a href="https://wastenotwhynot.com/">Waste Not Why Not podcast</a> when I first heard the term ‘wishcycling’. If you’re not familiar with the term, wishcycling is basically what happens at the end of any house party comprised of adults in their thirties. In a sincere attempt to be helpful, the attendees will gather all the trash and recycling, so as to not impose on the host too much. However, one person will inevitably misunderstand what constitutes valid recycling and optimistically chuck the dirty pizza box in the recycling pile. The whole batch of recycling is then contaminated, and ends up going to landfill, causing the thing that people wished to avoid in the first place.</p>
<p>Accessibility is really similar to recycling in certain ways. Most people agree that they are both net positives for the world, so many folks want to try their best at it, even if it requires them to take on a bunch of work they might not necessarily be prepared for. This often leads to a problem that I will call ‘wishcessibility.‘</p>
<section><h2 id="what-is-wishcessibility">What is wishcessibility?</h2><p>As I see it, wishcessibility isn’t a single specific bad practice, it’s anything that inadvertently hurts accessibility when you’re trying to improve it.</p><p>Wishcessibility is when the unpaid, open-source maintainer follows the ARIA Authoring Practices Guide without testing the patterns, thinking that they <a href="https://hidde.blog/ideal-a11y-guidance/">represent best accessibility practices.</a></p><p>Wishcessibility is when the junior developer learns about keyboard-only users for the first time and adds tabindex=“0” to every heading and text node on the page, without anyone in the organization preventing this work from being pushed to production.</p><p>Wishcessibility is trusting the accessibility advice of an LLM without checking to see if that is how it works in real life.</p><figure><img alt="A conversation with an LLM where I ask it 'how does aria-controls help accessibility' and it responds that it plays an important role in accessibility, full text is included below." loading="lazy" decoding="async" sizes="(min-width: 1854px) 1854px, 100vw" style="object-fit:cover;max-width:1854px;max-height:1266px;aspect-ratio:1.4644549763033174;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_437,c_lfill,f_auto/v1770625841/aria-controls-llm 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_512,c_lfill,f_auto/v1770625841/aria-controls-llm 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_565,c_lfill,f_auto/v1770625841/aria-controls-llm 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_656,c_lfill,f_auto/v1770625841/aria-controls-llm 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_737,c_lfill,f_auto/v1770625841/aria-controls-llm 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_874,c_lfill,f_auto/v1770625841/aria-controls-llm 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1139,c_lfill,f_auto/v1770625841/aria-controls-llm 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1854,h_1266,c_lfill,f_auto/v1770625841/aria-controls-llm 1854w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1311,c_lfill,f_auto/v1770625841/aria-controls-llm 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1398,c_lfill,f_auto/v1770625841/aria-controls-llm 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1748,c_lfill,f_auto/v1770625841/aria-controls-llm 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2185,c_lfill,f_auto/v1770625841/aria-controls-llm 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3708,h_2532,c_lfill,f_auto/v1770625841/aria-controls-llm 3708w" src="https://res.cloudinary.com/nicchan/image/upload/w_1854,h_1266,c_lfill,f_auto/v1770625841/aria-controls-llm"><details><p><summary>Full LLM Output</summary>
Me: How does aria-controls help accessibility?</p><p>LLM: The <code>aria-controls</code> attribute is part of the WAI-ARIA (Accessible Rich Internet Applications) specification, and it plays an important role in accessibility by helping assistive technologies understand relationships between elements.</p><p>How <code>aria-controls</code> Helps Accessibility</p><ul>
<li><strong>Defines relationships</strong>: It tells assistive technologies (like screen readers) which element(s) are controlled by a given control (e.g., a button, tab, or combobox).</li>
<li><strong>Improves navigation</strong>: Screen readers can announce that activating a control will affect another part of the page, helping users anticipate changes.</li>
<li><strong>Supports dynamic content</strong>: In modern web apps, content often updates without page reloads. <code>aria-controls</code> makes these updates more transparent to users relying on assistive tech.</li>
<li><strong>Enhances context</strong>: Users understand not just what a control does, but <em>where</em> its effect will be applied.</li>
</ul></details><img alt="A screenshot from a11ysupport.io that shows aria-controls is poorly supported across all screen readers, with only JAWS allowing the user to jump to the controlled element." loading="lazy" decoding="async" sizes="(min-width: 2304px) 2304px, 100vw" style="object-fit:cover;max-width:2304px;max-height:765px;aspect-ratio:3.011764705882353;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_213,c_lfill,f_auto/v1770625840/aria-controls-support-table 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_249,c_lfill,f_auto/v1770625840/aria-controls-support-table 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_275,c_lfill,f_auto/v1770625840/aria-controls-support-table 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_319,c_lfill,f_auto/v1770625840/aria-controls-support-table 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_359,c_lfill,f_auto/v1770625840/aria-controls-support-table 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_425,c_lfill,f_auto/v1770625840/aria-controls-support-table 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_554,c_lfill,f_auto/v1770625840/aria-controls-support-table 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_638,c_lfill,f_auto/v1770625840/aria-controls-support-table 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_680,c_lfill,f_auto/v1770625840/aria-controls-support-table 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2304,h_765,c_lfill,f_auto/v1770625840/aria-controls-support-table 2304w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_850,c_lfill,f_auto/v1770625840/aria-controls-support-table 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1063,c_lfill,f_auto/v1770625840/aria-controls-support-table 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_1275,c_lfill,f_auto/v1770625840/aria-controls-support-table 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_1488,c_lfill,f_auto/v1770625840/aria-controls-support-table 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_4608,h_1530,c_lfill,f_auto/v1770625840/aria-controls-support-table 4608w" src="https://res.cloudinary.com/nicchan/image/upload/w_2304,h_765,c_lfill,f_auto/v1770625840/aria-controls-support-table"><figcaption><p>The LLM drastically overstated the benefits of <code>aria-controls</code>, which has poor screen
reader support.</p></figcaption></figure><p>Wishcessibility might even be when someone installs that accessibility overlay, because they’ve seen it on a dozen other similar sites and they’ve fallen for the <a href="https://overlayfactsheet.com/en/">flood of marketing lies</a>.</p><p>The common thread here is that wishcessibility, like wishcycling, is always performed by people who want to do the right thing. When a giant corporation decides to whittle their accessibility team down to a single individual with the job title of ‘Resident Sisyphus’, that’s not wishcessibility, that’s just regular ol’ corporate greed. The corporation may or may not be aware that this results in ableism, but the result is the same.</p><p>While I would normally be somewhat wary of placing too much emphasis on intent over impact, I genuinely believe it is a useful distinction here because it mirrors the career trajectory of many accessibility practitioners I know. Most of them (including me) started with a sincere desire to make things accessible, and some of us were lucky enough to be allowed to fail, over and over again, until the net good of our work became greater than our failures. Having good intent doesn’t cancel out a negative impact, but I believe cultivating that good intent will result in greater accessibility, turning wishcessibility into actual accessibility.</p><p>But as common as that narrative is in my circles, people transcending wishcessibility is quite rare, as inaccessible code and design dominates our industry. It turns out that combatting it is a lot harder than defining it.</p></section>
<section><h2 id="how-do-we-combat-wishcessibility">How do we combat wishcessibility?</h2><p>Much of the day to day (and money) in the accessibility industry is driven by a compliance mindset, and this is the inevitable result of the law driving most corporate accessibility efforts. The accessibility practitioners’ standard refrain of ‘it depends’ is not generally acceptable in court, and ultimately, I understand the reasoning behind having measurable, standardized goals, even if it often devolves into box-ticking exercises.</p><blockquote>
<p>“When a measure becomes a target, it ceases to be a good measure.”</p>
</blockquote><p>However, as Sherri Byrne-Haber notes in her excellent recent piece ‘<a href="https://buttondown.com/access-ability/archive/dont-fall-into-the-accessibility-gradescore-trap/">Don’t Fall Into the “Accessibility Grade/Score” Trap</a>’, an inherent part of creating standards involves flattening nuance into something reproducible in different contexts. This leads to a proliferation of one-size-fits-all guidance and advice, usually in the form of marketing pieces for different service providers.</p><p>Well-meaning developers who want to do the right thing but lack expertise lap up surface-level accessibility content, and the cycle of inaccessibility continues, unfettered by nuance. Without expertise and experience in the practice of accessibility, developers just try to do the best they can with what they’re given. Without organizational support, they don’t have the opportunity to deepen their knowledge, and they can only do so much before the pressures to ship faster inevitably cause harm. I know this, because I’ve been there.</p><p>The reason why it’s so hard to get organizational support for accessibility is that the fundamental core of the field is dealing with the inherent messiness of human beings. It’s a rite of passage in the field to receive user feedback that completely conflicts with standard accessibility practice. Disabled people are humans, and being human means having some unexplainable quirks, like the person I once met who only uses cursive fonts on their phone. Though receiving this kind of feedback doesn’t make for straightforward, actionable product feedback, much of how we practice accessibility today is born of <a href="https://www.nicchan.me/blog/what-tumblr-taught-me-about-accessibility/">people figuring out how to do accessibility for themselves</a>, regardless of what standards say. We can’t forget that the standards are derived from people’s lived experiences, and not the other way around.</p><p>One sad reality of our current tech hype cycle means that if you asked your boss for $200 to do some testing with disabled users, they would probably say no, but if you asked them for the same $200 to have access to an LLM so you could generate some user personas, they would probably say yes. There’s something safe and comforting about the answer-shaped results of an LLM, real human experiences distilled down to generalizable averages.</p><p>But this is all a farce, a <strong>wish</strong>, if you will. It’s a lie we tell ourselves to hide the fact that perfect accessibility is actually impossible. The only way to get anywhere close is through systemic support and buy-in so that people can do the gritty, in-the-trenches work of grappling with our shared humanity.</p><p>Much like the person at the party tries to impress their friends by attempting to recycle, we aim for perfect tooling scores, closed issues and well-written reports. The other stuff, the actual user experience is relegated to afterthought. I’m not saying we should throw out the baby with the bathwater and get rid of standards like WCAG. They represent an important baseline (and <a href="https://yatil.net/blog/meeting-wcag-is-an-achievement">an achievement!</a>), a necessary starting point before the even harder work begins.</p><p>Though I, a perpetual optimist, continue to diligently sort out my waste from my recyclables, I know it’s but a drop in the ocean of what we have to do to fix the harm done to our environment. Similarly, I have no illusions about what this blog post can achieve — I know publishing this won’t suddenly make organizations dedicate what it needs to achieve an accessible internet. I just hope that by publishing this, it gives us some language to talk about why the work of accessibility needs us to think beyond the pixels, the code, the standards and the law, about the actual humans we are solving problems for.</p><p>(Thank you <a href="https://janmaarten.com/">Jan Maarten</a> for giving me feedback on this piece!)</p></section>]]></content>
        <published>2026-02-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[2025 Recap]]></title>
        <id>https://www.nicchan.me/blog/2025-recap/</id>
        <link href="https://www.nicchan.me/blog/2025-recap/"/>
        <updated>2026-01-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[am i doing this gratitude thing right?]]></summary>
        <content type="html"><![CDATA[<p>I’ve never been one to write these kinds of posts, but I’ve really enjoyed reading these! Hearing that my buddies had small moments of joy and success makes me feel warm and fuzzy inside, despite the overall mood of 2025.</p>
<p>For me, the overall theme of this year was survival. I knew with several months out of the year gone to theme development, I wasn’t going to break my personal record for income, but that’s fine by me. I know how badly others in our industry have been affected by the current economic climate, so being able to stay abreast of all my bills is good enough for me. There was a lot of suffering all around, much of it from systems that didn’t have to be that way, and that kind of suffering is uniquely painful. I am grateful to be shielded from most of that this year, but it still weighs heavy on my heart.</p>
<p>It’s now time for a very abrupt change in tone, so onto the recap!</p>
<section><h2 id="web-development">Web development</h2><section><h3 id="shopify-theme">Shopify Theme</h3><p>We got the <a href="https://www.nicchan.me/blog/we-launched-our-first-shopify-theme/">Shopify theme</a> out! I won’t ramble on about it since I already covered it, but needless to say, it was a tremendous effort. With 2025 behind us, I’m feeling a little mixed about the Shopify ecosystem and the direction the leadership is taking, but I’m tremendously proud of what Marianne and I accomplished.</p><p>Finishing the theme and then immediately jumping into client work felt like finishing a marathon and hopping onto a treadmill (who am I kidding, this is a very unqualified use of this metaphor), and in some ways I’m still recovering from that time period. My personal tendency is to overwork myself to the point of exhaustion rather than procrastinate, and that’s something I need to actively keep fighting in the future.</p></section><section><h3 id="client-work">Client work</h3><p>I got to work with some very fun and cool clients, both old and new. I’ve always considered myself a developer’s developer, in that I like working with teams where they look at the code as part of the end product, not just the website itself. Getting to geek out over the technical details together is something I enjoy, and want to do more of next year! Shoutout to <a href="https://set.studio/">Set Studio</a>, <a href="https://cloudfour.com/">CloudFour</a>, <a href="https://tetralogical.com/">Tetralogical</a> and Justin Veiga. All of them are amazing, and I’m honored that they placed their trust in me this year.</p></section><section><h3 id="going-viral">Going viral</h3><p>I went viral a few times this year. I thought the traffic bump from hitting several industry newsletters was already sizeable. Even reaching the fifth page on Hacker News usually results in quite a bump compared to my daily visitors. Reaching the #1 spot on Hacker News had emails coming in at a rate of one per minute, and I got two years worth of page views in a few hours, which left me hyperventilating under my desk.</p><p>Overall, this wasn’t a bad thing, despite how it felt in the moment. I received many lovely and kind comments/emails (and bug reports!) and they still trickle in till this day.</p><p>However, this experience taught me I’m pretty happy with my current audience size as is. I have enough of a readership that there is a non-zero chance that people I respect may have heard of me, but I don’t have to deal with any of the consequences of celebrity on a regular basis. In this metric-driven world where everyone is trying to do whatever it takes to make the number go up, I think this was a valuable lesson to learn early. If you’re reading this, I’m grateful you are here!</p></section><section><h3 id="grid-aware-websites">Grid-aware websites</h3><p>My time on the <a href="https://www.thegreenwebfoundation.org/">Green Web Foundation</a>’s Grid-Aware Websites advisory group came to an end. I really enjoyed getting to <a href="https://nicchan.me/blog/exploring-grid-aware-websites/">think about sustainability</a> as it relates to our industry. While I still think there’s a way to go before grid aware websites are become widely adopted, it was exciting to bring awareness to it and I look forward to learning more from the green tech world.</p></section><section><h3 id="conferences-travel-and-speaking">Conferences, travel and speaking</h3><p>I made it to my first industry conference since 2019, the wonderful Smashing Conference in Freiburg! Both the Smashing team and conference attendees were so friendly and interesting, I had so many conversations about people’s passions! It’s genuinely nice to talk about things other than work sometime. It also was intriguing to me how many folks were working on accessibility — a heartening sign that the European Accessibility Act is having some impact. Coming from a place where most web professionals don’t even know what accessibility is, it was very refreshing.</p><p>Overall, it was wonderful to see so many friends both old and new, and as a bonus, I got to tack on a few fun days in Paris with Marianne to the start of the trip. I haven’t been to Europe since my teenage years, and it took my breath away. Some day I’d love to be back for a painting trip, and to hit up the many highlights I missed! I didn’t venture into the Louvre because I knew it would take me at least two days to get to, but the Musée de l’Orangerie was a standout and I can’t stop thinking about the experience of seeing Monet’s work up close.</p><p>I finished off the year by speaking at a few events, something I also haven’t done in quite a few years. Thanks to the wonderful team at <a href="https://inclusivedesign24.org/2025/">ID24</a> (especially Patrick Lauke and Eric Bailey) and to Sia Karamalegos at the <a href="https://11tymeetup.dev/">11ty Meetup</a> for making it a smooth process, even though I was stressed out of my mind for two straight months. I always underestimate how long it takes to prepare for speaking events — it takes me like a full week to prepare a talk of any length. I want to clean up the transcripts and some point and post them here, please hold me to it! The response to the talks was overwhelmingly positive, and it made me appreciate the web community more.</p></section><section><h3 id="the-future">The future?</h3><p>One of the things I learned about myself this year is that it’s important that I enjoy the <strong>process</strong> of doing my job. Like many web folk, I didn’t plan to become web developer (I studied art), but I have come to enjoy the process of building a mental model of how things will work, as well as the process of reasoning about problems when they inevitably come up.</p><p>I hate what the current AI hype is doing to the world, and even if none of the bad things about it existed, I don’t personally enjoy the process of begging and cajoling the computer to spit out something acceptable. It feels much more like gambling than problem solving to my brain. I’m not sure how (or when) exactly the impact of this realization will manifest; I want to continue building for a web I love, but maybe that’s a future that’s no longer possible. We’ll see.</p><p>In my case, I don’t have any human dependents, debt or extenuating factors that might pressure me into making more financially responsible decisions, and so I’m privileged enough to try to hold out for work that is gratifying in some way. Pleasures of a DINK household, I guess?</p></section></section>
<section><h2 id="personal--creative">Personal &amp; Creative</h2><section><h3 id="cats">Cats</h3><p>I adopted a third cat! I picked her up on a complete whim due to her striking resemblance to my previous cat, and I’m happy to say that I don’t regret it at all. Miette (named by the folks on Mastodon) is settling in well and has somehow managed to improve the relationship between the resident cats, while discovering new ways to be an adorable menace. I want to produce some silly art to help people differentiate my three (kinda) black cats, stay tuned!</p><figure><img alt="Two fluffy cats, one all black and one tuxedo cat, curled up on a bed together in a way that so that their heads and toes are touching." loading="lazy" decoding="async" sizes="(min-width: 2048px) 2048px, 100vw" style="object-fit:cover;max-width:2048px;max-height:1152px;aspect-ratio:1.7777777777777777;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_360,c_lfill,f_auto/v1767235465/P_20251231_155452 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_422,c_lfill,f_auto/v1767235465/P_20251231_155452 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_466,c_lfill,f_auto/v1767235465/P_20251231_155452 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_540,c_lfill,f_auto/v1767235465/P_20251231_155452 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_608,c_lfill,f_auto/v1767235465/P_20251231_155452 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_720,c_lfill,f_auto/v1767235465/P_20251231_155452 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_938,c_lfill,f_auto/v1767235465/P_20251231_155452 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1080,c_lfill,f_auto/v1767235465/P_20251231_155452 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1152,c_lfill,f_auto/v1767235465/P_20251231_155452 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1152,c_lfill,f_auto/v1767235465/P_20251231_155452 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1440,c_lfill,f_auto/v1767235465/P_20251231_155452 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1800,c_lfill,f_auto/v1767235465/P_20251231_155452 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2160,c_lfill,f_auto/v1767235465/P_20251231_155452 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4096,h_2304,c_lfill,f_auto/v1767235465/P_20251231_155452 4096w" src="https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1152,c_lfill,f_auto/v1767235465/P_20251231_155452"><figcaption>I love them so much.</figcaption></figure></section><section><h3 id="touching-grass">Touching Grass</h3><p>Thankfully, I managed to get my health back on track this year. For the past few months, I’ve been successfully managing my work-related chronic pain condition through regular exercise. Until now, it was something I had to keep under control with a regular combination of physiotherapy, massage and painkillers, so I’m happy to have made progress in this area. Not being in terrible pain is a great motivation to go to the gym, as it turns out.</p><p>I also managed to participate in a hike organized by a local farm recently. I love getting to venture out to other parts of Hong Kong that I don’t see in my daily life, and I want to overcome my plant-killer tendencies, so I’m thinking about joining the weekly farming cohort next year! If not, I still want to participate in more one-off events. Growing things scratches the same itch in my brain as building things.</p></section><section><h3 id="gaming">Gaming</h3><p>I played a lot more video games year, thanks to not struggling as much with the chronic pain thing. Many of these games were co-op games with far away friends — I’m so happy we got to rekindle our friendships with some good ol’ fashioned screaming and yelling. My favorite co-op game of the year was V Rising, which feels like a video game <strong>made</strong> for me. I loved the combat, the crafting and getting to design and build a gothic castle. I’m thinking about starting a third run, solo this time, just to enjoy the world and environments a bit more, along with the increased challenge of playing on Brutal mode.</p><p>My top solo game of the year was Clair Obscur: Expedition 33. I’m a lifelong fan of JRPGs (and also art, duh) so this game also felt uniquely special. JRPG stories often feel quite overplayed and tropey, but this one was really brought to life by stunning motion-capture and voice acting performances. The fantasy environments were creative and beautifully designed in a way that felt really intentional, and the soundtrack is something that I appreciate more and more with every re-listen. This undoubtedly makes my top 10 games of all time list, and I have to actively try to not ramble on about it forever, please, just play it!</p><p>Honorable mentions go to Hades II (whose main flaw is coming after Hades I, a <strong>perfect</strong> game), Ember Knights, Necesse, Death’s Door and Fantasy Life i, all of which were enjoyable and occupied significant chunks of my time this year.</p></section><section><h3 id="art">Art</h3><p>Despite the lack of new material on my Instagram, I did draw pretty regularly! I went to life drawing quite a bit, and it turns out that being forced to censor an errant nipple is enough of a blocker to not post the image at all. I didn’t paint much, often choosing quicker medium like markers and colored pencils, which made for a fun combination. I’m not too upset at this development, but I do have quite a few images bouncing around in my head that I want to realize in some way or another. I’m struggling a lot more with getting larger creative projects done. At some point in the process, I start to doubt my own ability to realize the vision and so I end up not trying at all. I think that’s why I’ve been gravitating towards art practices like plein-air and life drawing. You show up and try, and that removes the problem of translating the vision you’ve built up in your head into reality.</p><figure><img alt="An assortment of nude human figures in markers and color pencils composited into one canvas. The approach is generally loose and sketchy, the colors non-naturalistic." loading="lazy" decoding="async" sizes="(min-width: 2436px) 2436px, 100vw" style="object-fit:cover;max-width:2436px;max-height:3000px;aspect-ratio:0.812;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_788,c_lfill,f_auto/v1767235481/2025-composite 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_924,c_lfill,f_auto/v1767235481/2025-composite 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_1020,c_lfill,f_auto/v1767235481/2025-composite 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_1182,c_lfill,f_auto/v1767235481/2025-composite 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1330,c_lfill,f_auto/v1767235481/2025-composite 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1576,c_lfill,f_auto/v1767235481/2025-composite 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_2054,c_lfill,f_auto/v1767235481/2025-composite 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_2365,c_lfill,f_auto/v1767235481/2025-composite 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_2522,c_lfill,f_auto/v1767235481/2025-composite 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2436,h_3000,c_lfill,f_auto/v1767235481/2025-composite 2436w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_3153,c_lfill,f_auto/v1767235481/2025-composite 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_3941,c_lfill,f_auto/v1767235481/2025-composite 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_4729,c_lfill,f_auto/v1767235481/2025-composite 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_5517,c_lfill,f_auto/v1767235481/2025-composite 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_4872,h_6000,c_lfill,f_auto/v1767235481/2025-composite 4872w" src="https://res.cloudinary.com/nicchan/image/upload/w_2436,h_3000,c_lfill,f_auto/v1767235481/2025-composite"><figcaption>5-20 minute figures from this year.</figcaption></figure><p>One of my few goals for 2025 was to get my work into some kind of art show. That didn’t happen this year! My regular life drawing place might have a group exhibition next year, so maybe 2026 is the year?</p></section></section>
<section><h2 id="closing-thoughts">Closing thoughts</h2><p>Wow, that’s quite a bit more than I intended to write! 2025 felt like a whirlwind as I was living it, and looking back, that’s definitely reflected in the amount of stuff I did. I’ve never been one for future goal setting (I hate disappointing my future self), but I do think I can scribble down a few brief hopes and dreams for the next year. Hoping for better things feels like a trap at this point, so I will close this post with a set of general tiny wishes.</p><section><h3 id="in-2026-i-would-like-more">In 2026, I would like more…</h3><ul>
<li>Reading</li>
<li>Blogging, especially about non tech things</li>
<li>Farming</li>
<li>Sleeping</li>
<li>Art</li>
<li>Momos</li>
</ul></section><section><h3 id="and-less">and less…</h3><ul>
<li>Burnout</li>
<li>Slop</li>
<li>Doomscrolling</li>
<li>Agonizing over things I can’t control</li>
</ul></section></section>]]></content>
        <published>2026-01-04T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[The f*** off contact page]]></title>
        <id>https://www.nicchan.me/blog/the-f-off-contact-page/</id>
        <link href="https://www.nicchan.me/blog/the-f-off-contact-page/"/>
        <updated>2025-12-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to get people to NOT contact you]]></summary>
        <content type="html"><![CDATA[<aside class="post-callout"><p><p>Edit: Woah, this post broke containment! If you’re new here, hi! You can
find some settings to customize your reading experience in the bottom right.</p></p><p><p>This post contains swear words. I’ve censored the first one, but read on at
your peril!</p></p></aside>
<p>Many years ago, I had a client that sold a service. They weren’t a design agency, but for the sake of anonymity, we’ll just call them a design agency. Let us say that their core offering was a full-service design package, but they also made a substantial part of their income from doing smaller tasks related to their primary offering. These kind of services included smaller tasks like one-off campaigns or newsletter designs; tasks that their customers may very well be able to do on their own, but the prospect of freeing up some time by by offloading it to an expert was a tempting offer for many of their customers, and made up a significant chunk of their revenue.</p>
<p>We were hired to do a complete redesign of their site from the ground up. The process went smoothly at first, all the wireframes were approved without issue, but when it came to the design phase, we began to hit walls. For example, they would stumble across sites that they liked and wanted to depart from the agreed-upon wireframes in order to implement a similar design.</p>
<p>The problem was, they were thinking about their inspiration sites from an aesthetic point of view, not from a user experience perspective. Their decisions were coming from a place of ‘we like the balance of imagery and text in this page’ and not ‘we think this design will achieve the intended goal of the page.’ Now, you know me, I love a good <a href="https://www.smashingmagazine.com/2025/04/gild-just-one-lily/">singular gilded lily</a>, but the client had unwittingly stumbled across a trap, they had fallen in love with what I call a “Fuck off contact page.”</p>
<section><h2 id="what-the-fuck-is-a-fuck-off-contact-page">What the fuck is a ‘fuck off contact page?’</h2><p>A “fuck off contact page” is what a company throws together when they actually don’t want anyone to contact them at all. They are usually found on the websites of million or billion dollar companies, likely Software-as-a-service (SaaS) companies that are trying to reduce the amount of money they spend on support by carefully hiding the real support channels behind login walls. These companies tend to offer multiple tiers of support, with enterprise customers having a customer success manager who they can call on this ancient device we call phones, whereas the lower-paying customers may have to wrangle various in-app ticket mechanisms. If you solve your own problem by reading the knowledge base, then this is a win for the company. They don’t want to hear from you, they want you to fuck off.</p><figure><img alt="Two mobile wireframes. On the left, the wireframe has a large heading that says Contact, and a contact form with two fields, 'Name' and 'How can we help you?' below it. On the right, the mockup has a large heading that says Contact, and three icons with text underneath. In order, they are 'Check out our knowledge base', 'Visit us in person' and 'Reach out to our sales team.'" loading="lazy" decoding="async" sizes="(min-width: 752px) 752px, 100vw" style="object-fit:cover;max-width:752px;max-height:718px;aspect-ratio:1.0473537604456824;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_611,c_lfill,f_auto/v1765177043/contact 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_716,c_lfill,f_auto/v1765177043/contact 750w,
https://res.cloudinary.com/nicchan/image/upload/w_752,h_718,c_lfill,f_auto/v1765177043/contact 752w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_791,c_lfill,f_auto/v1765177043/contact 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_917,c_lfill,f_auto/v1765177043/contact 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1031,c_lfill,f_auto/v1765177043/contact 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1222,c_lfill,f_auto/v1765177043/contact 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1504,h_1436,c_lfill,f_auto/v1765177043/contact 1504w" src="https://res.cloudinary.com/nicchan/image/upload/w_752,h_718,c_lfill,f_auto/v1765177043/contact"><figcaption><p>These are recreated versions of the wireframes that we did for the site, the
original contact form version of the page is on the left, and the ‘fuck off
contact page’ is on the right. In actuality, the ‘fuck off contact page’ was
even more ‘fuck off’ due to the whitespace and a large hero image. This
meant that ‘talk to the sales team’, the only option that would put you in
touch with a human anytime soon, was at the very bottom of the page, long
after some people would stop scrolling.</p></figcaption></figure><p>In other words, this is entirely inappropriate for the kind of service-based agency that our client was. The billion dollar SaaS company wants to reduce the number of incoming inquiries, and is hoping to weed out anyone who is not determined to contact them by giving them unsatisfying options. The service company wants to show how helpful they are and cultivate leads. These are fundamentally opposing goals.</p><p>Let me explain further. I’m not sure about you, but as a user, when I see a button that says ‘talk to our sales team’, I treat the entire region of the page with the same trepidation as nuclear waste. The page is now a no-go zone, and I try to exit as quickly as possible, knowing that whatever my original query was, I’m going to have to solve it unassisted. Seeing as this is a company who makes money off of convincing people to let them handle the easy stuff, adding friction to this key part of their sales funnel just doesn’t feel like a winning strategy.</p></section>
<section><h2 id="how-the-fuck-did-you-convince-them-to-change-their-minds">How the fuck did you convince them to change their minds?</h2><p>Try as we might, we couldn’t. In all honesty, we probably could have done more in order to talk them out of it, but the project had gone in such a way where we were focused on trying to talk the client out of changing other things that would drastically increase design or development time beyond the initial scope. In other words, we were too busy putting out other fires. This re-designed contact page, as certain as we were of how bad of an idea it was, wasn’t a fire, so we let it through.</p><p>The project finished on time, everyone got paid, and the client was happy with the end result, but I still felt very disappointed in the whole thing. While I personally believe in the value of good design, I also believe there are a lot of smoke-and-mirrors in the industry, and I hated the thought that I might have inadvertently contributed to it. Even if the client is happy, it didn’t meet my internal bar for a quality product worth sticking my name on, and I feel like I’ve let down both the client and the end-users.</p></section>
<section><h2 id="how-the-fuck-do-i-avoid-being-in-a-position-where-im-asked-to-implement-a-fuck-off-contact-page">How the fuck do I avoid being in a position where I’m asked to implement a ‘fuck off contact page’?</h2><p>I think our problems started from before we even began to touch a single design tool. As a favor to one of the folks involved, we had discounted our rates for this client, and I think that set us off on the wrong foot. Instead of seeing us as people who brought valuable knowledge and expertise to the project, they saw us as the hands that would execute their vision.</p><p>Especially for those not familiar with the process of design, it can be tempting to see things like discovery and wireframing as obstacles to be cleared before you get to the fun part, designing the visual identity. Unfortunately, many designers are also guilty of this!</p><p>As service providers, I believe we need to do a better job on educating clients on the design process and why each step is so important. This is radical idea in some circles, but knowing why you’re building something is a necessary part of doing a good job at it! That’s why we do things like determining the architecture before we start thinking about the brand. Flow charts and diagrams are not as fun as interactive prototypes, but they’re much more important to get right.</p><p>Also, the discounted pricing probably didn’t help — instead of signaling that we were doing a favor out of respect for them, it just signaled that we were easily exploitable. There was a lack of trust throughout the process, on both sides. While I really want to believe that I can have the kind of relationships with clients where constructive disagreement is welcomed and valued, how I get there is still something I’m figuring out, even many years later.</p><p>I think that’s part of the reason why I blog. By blogging, I’m putting a body of work out there that communicates my values and ethos. While much of the details of my client work has to remain private, these posts can be public, and hopefully they can help me find people who resonate with what I have to offer. Or you know, just be bold enough to communicate ‘Fuck off’ to those who don’t!</p><p>(Feel free to <a href="https://www.nicchan.me/contact">reach out</a> if you’re interested in working with folks who care, maybe a little too much, about doing right by your users.)</p></section>
<section><h2 id="links-and-resources">Links and Resources</h2><p>I hit publish on this as I listened to <a href="https://www.youtube.com/watch?v=a7sh-5UYnJc">Andy Bell’s recent talk at Beyond Tellerand</a>. It has SO many gems of wisdom about the core skills required to deliver successful client projects. It’s also why I love working with <a href="https://set.studio/">Set.Studio</a>, check ‘em out.</p></section>]]></content>
        <published>2025-12-08T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Newsletters that regularly hit my inbox these days]]></title>
        <id>https://www.nicchan.me/blog/newsletters-that-regularly-hit-my-inbox/</id>
        <link href="https://www.nicchan.me/blog/newsletters-that-regularly-hit-my-inbox/"/>
        <updated>2025-11-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Also I got featured in People & Blogs!]]></summary>
        <content type="html"><![CDATA[<p>I got tagged in this by my buddy <a href="https://janmaarten.com/newsletters-that-regularly-hit-my-inbox-these-days/">Jan Maarten</a>, who got tagged by my other buddy <a href="https://ericwbailey.website/published/newsletters-that-regularly-hit-my-inbox-these-days/">Eric Bailey</a>. I’m not gonna lie, both of these lists have very strong overlap with my own lists. I’ll try my best not to be too repetitive, but it will be hard as they are both stellar lists that I wholeheartedly co-sign. Jan also has some really, good practical advice on how to receive newsletters in a way that don’t add to my 2000+ unread inbox emails, it’s very much worth taking a look.</p>
<p>When I was thinking of who I should include in this post, I got into an argument with my own brain while trying to create a meaningful distinction between newsletters and blogs. After several weeks of mulling over this question, I think these are the main points of differentiation:</p>
<ul>
<li>Newsletters typically stick to a single theme or format, personal blogs are free of these restrictions</li>
<li>Newsletters often publish on a set schedule</li>
<li>Newsletters might have more than one person behind it</li>
</ul>
<p>There’s a lot of wiggle room in the definitions, but was important for me to answer this question because I follow a LOT of personal blogs that I absolutely adore and could not fit into this list due to time constraints. Fencing in the scope of this post let me feel a little bit better about this whole process and that I wasn’t leaving anyone out, but I’m pretty sure I will still forget a few people, eek!</p>
<p>I won’t be linking to any Substack newsletters. If you want to reap the network benefits of your <a href="https://sightlessscribbles.com/posts/20250425/">Nazi bar platform</a>, you should also bear the stigma.</p>
<p>Onto the newsletters!</p>
<section><h2 id="web-development">Web Development</h2><section><h3 id="a11yweekly"><a href="https://a11yweekly.com/">a11yweekly</a></h3><p>I try to limit the number of ‘weekly digest’ type things I subscribe to to keep the reading load manageable, but I think David A. Kennedy does a really good job of curating the links. I like that there is a ‘New to a11y’ link of the week, which makes it a great read for people who are newer to the field as well.</p></section><section><h3 id="the-index"><a href="https://piccalil.li/the-index/">The Index</a></h3><p>Short, snappy, to-the-point, what’s not to love? The Index helps to mitigate my compulsive need to backread on social platforms because I know the team at Piccalilli has got it covered. Still can’t spell ‘Piccalilli’ without copy and pasting it though, sorry Andy!</p></section><section><h3 id="webdev"><a href="https://web.dev/blog">web.dev</a></h3><p>Specifically, the Web Platform Monthly Updates, tagged ‘New to the web platform’. I think it’s a shame they don’t give a way to separate this content out from the rest , because it is genuinely useful to know what has or hasn’t landed on the web.  Monthly is a frequency that works for me for this kind of level of technical detail, weekly pulls me away from my routine too much as I’m very prone to being nerd-sniped.</p></section></section>
<section><h2 id="tech-industry">Tech Industry</h2><section><h3 id="open-web-advocacy-blog"><a href="https://open-web-advocacy.org/blog/">Open Web Advocacy Blog</a></h3><p>OWA is a much needed voice for the battle of the open web. It’s great to get a sense of what the broader trends in legislation are like, along with updates on where the OWA is focusing their advocacy.</p></section><section><h3 id="citation-needed-by-molly-white"><a href="https://www.citationneeded.news/">Citation Needed by Molly White</a></h3><p>I fully believe if every reporter was even just 5% more like Molly White, the world would be a better place. Molly doesn’t take what company spokespeople say for granted, and is willing to put in the legwork to do the research required to report things truthfully. Crypto may be past it’s heyday, but it’s still fucking up democracy everywhere, so her work is still needed.</p></section><section><h3 id="404-media"><a href="https://www.404media.co/">404 Media</a></h3><p>I throw cash at 404 Media for their independent reporting and I hope more folks join me in doing so! Their newsletter summarizes all their weekly reporting as well as providing updates. If you’re a freak like me and you read all the articles in full, perhaps you won’t find this as useful, but it’s a great way to stay on top of some under-reported subjects in the tech industry.</p></section></section>
<section><h2 id="miscellaneous">Miscellaneous</h2><section><h3 id="gurney-journey"><a href="https://gurneyjourney.blogspot.com/">Gurney Journey</a></h3><p>As far as art educators go, James Gurney is just the best. I’ve been reading his posts since I was a teenager, so he wins the unique title of being ‘the person I’ve followed on the internet for the longest.’ You WILL become a better artist by following him, no doubt about that. Unfortunately he’s been cutting a lot of his posts short and putting them on Substack — he’s given so much to the art community that I don’t want to begrudge him the opportunity to monetize, but I wish he had chosen any other platform!</p></section><section><h3 id="sc244"><a href="https://buttondown.com/ericwbailey">SC2.4.4</a></h3><p>I’ve already said I’m not a huge fan of the link roundup format, but I will absolutely make an exception for Eric’s newsletter SC2.4.4. This is pretty much the only newsletter where I will consistently click through to every link, and that’s really saying something given that the links span a variety of topics and fields!</p></section><section><h3 id="people--blogs"><a href="https://peopleandblogs.com/">People &amp; Blogs</a></h3><p>Manu is a fantastic blogger and you should check his blog out, but I especially love his newsletter where he interviews a different person each week. A good 50% of my RSS feed is made up of these folks, check them out! I was lucky enough to be recently <a href="https://manuelmoreale.com/interview/nic-chan">featured</a>, and I swear I’m not including it for this reason, I just took two whole months to publish this post.</p></section></section>
<section><h2 id="what-newsletters-are-you-reading">What newsletters are you reading?</h2><p>Tell me what you’re reading! I always feel bad about tagging people but if you make a post, I’ll link to it here!</p></section>]]></content>
        <published>2025-11-25T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Exploring grid-aware websites]]></title>
        <id>https://www.nicchan.me/blog/exploring-grid-aware-websites/</id>
        <link href="https://www.nicchan.me/blog/exploring-grid-aware-websites/"/>
        <updated>2025-08-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Could adapting websites to a user's energy grid help make them more sustainable?]]></summary>
        <content type="html"><![CDATA[<p>Over the past year, I’ve had the incredible privilege of getting to participate in the Grid-Aware Websites (affectionately abbreviated to GAW) <a href="https://www.thegreenwebfoundation.org/news/introducing-our-grid-aware-websites-project/#technical-advisory-group">advisory group</a>. The Green Web Foundation team have thoroughly explained what a grid-aware website is in this detailed case study of the <a href="https://branch.climateaction.tech/issues/issue-9/designing-a-grid-aware-branch/">Branch Magazine redesign</a>, but if I had to put it in my own words, a grid-aware website responds to the cleanliness or dirtiness of a user’s electricity grid. In simpler terms, whether it is currently using more renewable energy sources or fossil fuels.</p>
<p>To do so in the most performant way possible, this currently involves getting a user’s rough location and using the <a href="https://portal.electricitymaps.com/developer-hub/api/getting-started#geolocation">Electricity Maps API</a> to check the current status of the grid, and then returning that information to a serverless function on the edge so that the website can be modified, all before it reaches the user’s browser.</p>
<p>To explore this concept, I’ve thrown together a small demo of what this might look like in two of my favorite web tools that I believe are conducive to this effort, and written up a few of my personal thoughts.</p>
<section><h2 id="why-implement-grid-awareness">Why implement grid-awareness?</h2><p>Many folks may notice that the in brief approach outlined above, there’s a non-trivial amount of code required to implement grid-awareness. Since less code = better performance = greener websites, isn’t this a bit counterintuitive, if not self-defeating? I hear you, and it’s something we’ve thought about a lot as a group, and the Green Web Foundation have addressed this question excellently in their <a href="https://www.thegreenwebfoundation.org/tools/grid-aware-websites/">FAQ</a>. Essentially, this is an experimental approach to see if this is a viable way to reduce the carbon emissions of websites.</p><p>I would love nothing more than for every site and application to be the best version that it could possible be, but we’re definitely not living in that world at the moment. The collective push by performance, sustainability and accessibility advocates have not been able to make the industry shift meaningfully, despite strong real world incentives to make sites more usable for more people.</p><p>When every company is asking ‘How can we do more?’, I think it is a powerful mindset shift to think ‘How can we do more <strong>with less</strong>?’ This question is typically framed in terms of having less budget or monetary resources, but when asked in terms of a performance budget or a user interface design, it forces you to think about what parts of the user experience is really necessary.</p><p>A big part of the Green Web Foundation’s philosophy with this project is raising awareness about the inherent connection between digital and physical worlds. To a designer using the latest MacBook with a Gigabit internet connection, performance concerns may often feel completely abstract and divorced from reality. Tools that remind us that yes, the choices we make do have an actual impact, can be an important part of driving change.</p><p>We can’t ignore the impact that digital systems have on our environment, especially at this critical moment when electricity usage is greater than ever. Sure, the energy saved from efforts like this may be a drop in the bucket compared to what AI is using, but I don’t think we’ve ever solved any societal problems by <strong>not</strong> trying to think about or address them.</p><p>Though my preferred approach is to build progressively enhanced, lean sites in order to make them sustainable, many brands prefer to express a commitment to sustainability in more overt ways. The visibility and positioning of something like a grid-aware toggle can be a powerful motivator for certain types of companies.</p><p>For example, I once had a client that sold sustainably made household items, and they wanted their website to reflect their commitment to sustainability. I suggested the usual high-impact things like reducing the size of media and using a static site generator. They didn’t mind the statically-generated approach as it helped them save on hosting costs, but they didn’t want to give up their use of background video, a decision that forced every user to download an additional 6MB of video. Instead, they suggested implementing a dark mode toggle, which I was more than happy to do to improve accessibility, but deep down, I knew that this was still a a net loss. If grid-aware websites had existed back then, maybe we could have reached some kind middle ground.</p><p>Ultimately, the end goal of the grid-aware websites project is to not build a single library, but to embed this kind of thinking into the greater web ecosystem. Much like how we have media queries for things like preferring reduced motion or using forced colors mode, what if we could have a media query to prefer a simpler, more environmentally friendly experience? The future is uncharted territory, and this is an open invitation to invite the web dreamers, gardeners, and explorers to join in.</p></section>
<section><h2 id="the-breakdown">The breakdown</h2><section><h3 id="step-1-determine-what-grid-awareness-looks-like-for-you">Step 1: Determine what grid-awareness looks like for you</h3><p>Now that we’ve discussed <strong>why</strong> one might want to consider implementing grid-awareness, let’s go into <strong>how</strong> one would implement it. In this post I will be following <a href="https://developers.thegreenwebfoundation.org/grid-aware-websites/tutorials/grid-aware-tutorial-cloudflare-workers/">this tutorial</a> by the Green Web Foundation pretty closely, which involves deploying the project to Cloudflare and spinning up a worker from their template.</p><p>Since I’ve been living in <a href="https://nicchan.me/blog/we-launched-our-first-shopify-theme/">e-commerce land</a> lately, I decided to throw together a proof-of-concept using a product display page (PDP) as a practical example that could relate to a wider audience. Although it is widely known that a performant product display page is important for improving conversions, many merchants opt for style over substance, resulting in page that is slow to load. I added some components that are fairly common across e-commerce sites, including:</p><ul>
<li>a scrolling marquee</li>
<li>an image slideshow</li>
<li>an image lightbox</li>
<li>tabs for product information</li>
<li>a modal notification upon adding an item to the cart</li>
</ul><p>All these components require JavaScript, but fall back to alternatives that still function without JavaScript. For example the scrolling marquee becomes a static text banner, the lightbox slideshow is just a horizontal scrolling region with links to larger images, the tabs fall back to a list of headings and paragraphs, and the product form is just a regular HTML form.</p><p>For this particular example, I thought that a good way to implement grid-awareness would be to create a distinction between JavaScript that is core to the e-commerce experience, and JavaScript that is just for presentation. I decided that of the JavaScript features listed above, only the modal form was integral to the purchasing experience, so I would need to find a way to disable the other features when the grid was ‘dirty’.</p><p>The grid-aware worker has three modes to represent three levels of carbon intensity — ‘low’, ‘moderate’ and ‘high’. To keep this simple, we’re just going implement this as a binary on and off state. In ‘low’ mode, you get the site with all the bells and whistles, and in ‘moderate’ and ‘high’ mode, you will just get the core experience.</p></section><section><h3 id="step-2-determine-how-youre-going-to-make-it-happen">Step 2: Determine how you’re going to make it happen</h3><p>Following the tutorial provided by the Green Web Foundation, I deployed the site to Cloudflare, then installed the <a href="https://github.com/thegreenwebfoundation/gaw-plugin-cloudflare-workers">specific grid-aware worker</a> they made (there’s also plans to build out a <a href="https://github.com/thegreenwebfoundation/gaw-plugin-netlify-edge">Netlify</a> equivalent). Out of the box, the plugin provides a drop-in connection to the Electricity Maps API once you plug in your key, which is very helpful. This worker will get the request, get the user’s rough location, and then determine the current state of the grid.</p><img alt="The Green Web Foundation's grid-aware statusbar component features a location marker with the current Location (Hong Kong), text that says 'Your local grid: Around average emissions', and a switch toggle to enable the grid-aware auto mode." loading="lazy" decoding="async" sizes="(min-width: 1524px) 1524px, 100vw" style="object-fit:cover;max-width:1524px;max-height:115px;aspect-ratio:13.252173913043478;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_48,c_lfill,f_auto/v1755821377/toolbar-cropped 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_57,c_lfill,f_auto/v1755821377/toolbar-cropped 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_62,c_lfill,f_auto/v1755821377/toolbar-cropped 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_72,c_lfill,f_auto/v1755821377/toolbar-cropped 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_81,c_lfill,f_auto/v1755821377/toolbar-cropped 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_97,c_lfill,f_auto/v1755821377/toolbar-cropped 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1524,h_115,c_lfill,f_auto/v1755821377/toolbar-cropped 1524w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_126,c_lfill,f_auto/v1755821377/toolbar-cropped 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_145,c_lfill,f_auto/v1755821377/toolbar-cropped 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_155,c_lfill,f_auto/v1755821377/toolbar-cropped 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_193,c_lfill,f_auto/v1755821377/toolbar-cropped 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3048,h_230,c_lfill,f_auto/v1755821377/toolbar-cropped 3048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1524,h_115,c_lfill,f_auto/v1755821377/toolbar-cropped"><p>It can also optionally inject the <a href="https://www.thegreenwebfoundation.org/news/a-web-component-for-grid-aware-websites/">grid-aware status bar</a> web component, which will show some data about the user’s current grid and allow the user to manually toggle between the modes. The status bar is optional, you can also build your own custom solution to give the user control over their grid-aware experience.</p><p>The website design changes in response to the grid are unique to each website, so this is an implementation step for us. On the Cloudflare version of this worker, this involves using the <a href="https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/">HTMLRewriter API</a> to transform the markup before it’s sent to the user. Even though the underlying approach would be the same for both 11ty and Astro, due to the different bundling strategies, we have to implement them slightly differently. Let’s take a closer look.</p><section><h4 id="11ty">11ty</h4><p>Ah, 11ty, my old friend. I haven’t picked it up in a while, but I’m excited to play around with the new WebC stuff, which lets you write single file components. The WebC architecture in 11ty is a really interesting idea, and the same technique that can be used for setting up critical CSS and JS can be used to set up grid-awareness.</p><p>One thing I particularly like about 11ty + WebC is that it lets you put scripts and other assets into designated buckets, while still allowing the author to write them in the main component file. I’ve included an example below.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="html"><code><span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&lt;!--This goes in your component file --&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">script</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> webc:bucket</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;bucketName&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	/* Your script stuff goes here! */</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">script</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&lt;!--This goes in your layout or page file --&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">script</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> @raw</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;getBundle(&#39;js&#39;, &#39;bucketName&#39;)&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">script</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"></span></code></pre><p>This is really handy! We can now separate the JS that is optional by putting it in it’s own separate bucket, so we can easily stick a <code>data-gaw-remove</code> attribute on it it for our Cloudflare worker to strip out. An approach like this is an excellent authoring experience, as we aren’t limited to putting the data attribute on script tags, we can put it on markup, style tags, etc. Our resulting HTML Rewriter API function is super simple as a result, we just remove all of these elements in one fell swoop.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">  new</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> HTMLRewriter</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">().</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">on</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">on</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&#39;[data-gaw-remove]&#39;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, {</span></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#B8BAFF">    element</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> (</span><span style="color:#4075BA;font-style:italic;--shiki-dark:#B993FF;--shiki-dark-font-style:italic">element</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) </span><span style="color:#A75293;--shiki-dark:#CB96CC">=&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">      element</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">remove</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">();</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">    },</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">  });</span></span>
<span class="line"></span></code></pre></section><section><h4 id="astro">Astro</h4><p>Astro has a little more ‘magic’ in it than 11ty, and it made it a little harder to figure out a solid grid-aware approach. Ideally, we could use the same approach as 11ty and just take away anything with the <code>data-gaw-remove</code> attribute on it, but Astro has this thing where if you add any attribute to a script tag, <a href="https://docs.astro.build/en/guides/client-side-scripts/#script-processing">Astro will not process it</a>. It’s typically used as an escape-hatch for things like analytics scripts, so they aren’t included in a bundle, but it poses a problem for us as we still want to process our scripts, we just want to add something extra on top.</p><p>To split the code up into different buckets, we’d probably need to hook into the underlying Vite/Rollup configuration. That felt beyond the scope of this small demo, so I came up with a quick-and-dirty solution. I noticed the bundled scripts for each Astro component kept the name of the component in the script <code>src</code>. I decided to use a prefix-based approach, and used the HTMLRewriter to strip out scripts that started with “GA-”. In terms of code, this is almost as simple as the 11ty version, but quite a bit less flexible — I can’t see people relying on opinionated naming as a way to implement this in real life scenarios.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">new</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> HTMLRewriter</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">().</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">on</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;script&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, {</span></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#B8BAFF">	element</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> (</span><span style="color:#4075BA;font-style:italic;--shiki-dark:#B993FF;--shiki-dark-font-style:italic">element</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) </span><span style="color:#A75293;--shiki-dark:#CB96CC">=&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">		if</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> (</span><span style="color:#4075BA;--shiki-dark:#B993FF">element</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">getAttribute</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;src&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">)?.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">includes</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;GA-&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">)) {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			element</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">remove</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">();</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		}</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	},</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">});</span></span>
<span class="line"></span></code></pre></section></section><section><h3 id="step-3-finetune-and-deploy">Step 3: Finetune and deploy</h3><p>There’s still a little work we need to do to get this up and running! We can reduce the number of external requests to the Electricity Maps API by implementing some caching. We can <a href="https://github.com/thegreenwebfoundation/gaw-plugin-cloudflare-workers/tree/main?tab=readme-ov-file#additional-functionality">look at the documentation</a> for the additional steps of setting up Workers KV, a form of data storage that will let us cache data from the API, the actual computed page, or both! The plugin default is to cache the data for one hour, so users from the same region won’t need to make additional requests. Once we’re done configuring that, we can deploy the worker with <code>npx wrangler deploy</code> and start processing our site!</p><div class="post-callout"><h4 class="post-callout__title">Disclaimer</h4><p class="post-callout__text"><p>Please note, to make the distinction between grid-aware modes clearer and to
reduce the amount of code required for the demo to function, I’ve opted not
to respect accessibility features such as <code>prefers-reduced-motion</code> in the
following demonstrations to avoid accessibility-related behavior being
mistaken for grid-aware behavior. I’ve also reached for third-party
libraries so that I could get this post out in a reasonable amount of time</p></p></div><p>Below is a comparison of what the site looks like when the grid is ‘dirty’
vs ‘clean’</p>  <p>Visit the live <a href="https://grid-aware-ecommerce-11ty.nicchan.me/">11ty demo</a> and the <a href="https://grid-aware-ecommerce-astro.nicchan.me/">Astro demo</a> to see it respond to your local energy grid.</p><p>I’ve put all the code into public repos (<a href="https://github.com/nchan0154/grid-aware-ecommerce-11ty">11ty</a>, <a href="https://github.com/nchan0154/grid-aware-ecommerce-astro">Astro</a>), with the main project at the top level and the grid-aware worker in a designated folder at <code>grid-aware-worker</code>. Take a peek!</p></section></section>
<section><h2 id="is-this-another-thing-i-have-to-care-about">Is this another thing I have to care about?</h2><p>I know what you’re thinking. Grid-awareness might seem like just one more item in the long checklist of things you should probably do, but don’t have the time or budget to do.</p><p>Here’s the thing, I think grid-awareness fits really neatly into all those other things you might already be doing to make a better website. If you’ve already got a website that has features to reduce data usage or utilizes progressive enhancement/graceful degradation, you already have the bones for a grid-aware website. Grid-awareness is in it’s infancy, and it isn’t exactly a specific practice in the way progressive enhancement is, it’s more of mindset.</p><p>Grid-awareness on an large social media application with video data might look completely different to grid-awareness on the Branch magazine website, and that’s totally fine. For example, the Branch magazine website doesn’t load web fonts and heavily compresses images when the grid is ‘dirty’. That approach is completely different to the one I’ve done here, and that’s okay! One of the main points I want people to take away from this is that <strong>you</strong> get to decide how to implement this.</p><p>It doesn’t have to be a completely new thing, either. Many applications already have something like a data saver mode, or a lower power mode. Grid-awareness could be an extension of these settings, and utilize the same core logic, only switching up the conditions when this logic is triggered.</p></section>
<section><h2 id="challenges">Challenges</h2><p>The first major blocker for the grid-aware websites project is the cost of the Electricity Maps API. The Carbon-Aware pricing plan required to make this work is currently 99 Euros a month (a recent development that occurred after the project started), a hefty price to cough up for a smaller organization. It’s hard to justify the expense when the entire architecture required to run the site may be cheaper, or even free. If you feel strongly about this, feel free to weigh in on this <a href="https://github.com/thegreenwebfoundation/grid-aware-websites/issues/21">Github Issue</a>. If there is a strong demand for a free plan, Electricity Maps may consider implementing it.</p><p>This specific proprietary API isn’t the only option. The <a href="https://mastodon.social/@burger_jaap/114669791492401229">EU has passed laws requiring member states to make this data available</a>, and it may be possible that there will be greater options for this information with the greater shift to greener energy.</p><p>The one thing that might be the biggest blocker for implementing grid-awareness at scale is that it involves all levels of the stack. Unlike areas such as accessibility or performance, where even one lone advocate can make a big difference, the method to implement grid-awareness requires a knowledge of all the moving pieces required to make it happen. The person who knows how to design a multi-layered, grid-aware experience is probably not the same person coding that front-end experience, and they’re probably not the same person with control over the building and deployment of the site.</p><p>Smaller projects like this demo where one person is in charge of all aspects can more easily implement grid-awareness because the complexity is significantly reduced, but at the same time, these kinds of sites are already most likely to be the least problematic in terms of carbon emissions. The same kind of paradox occurs with the tooling choice — tools like 11ty and Astro that make it easier to split your JavaScript up are much more likely to produce performant outputs already, making it uncertain if implementing this is worth the tradeoff. Once you pick up a bigger web framework, you’re more likely to benefit from implementing grid-awareness, but the barrier to do so will be much higher.</p><p>The grid-aware advisory group is well aware of this adoption barrier and has representatives from open-source CMS communities, who are actively working on grid-awareness libraries for CMSes like <a href="https://branch.climateaction.tech/issues/issue-9/adapting-wordpress-to-grid-aware-web-experiences/">Wordpress</a> (<a href="https://github.com/nahuai/grid-aware-wp">Github Link</a>), Drupal, Umbraco and Wagtail on the server-side. If grid-awareness was a solved problem at the deployment and ops layer, then it would be easier for designers and front-end developers to implement without worrying about this logic.</p></section>
<section><h2 id="conclusion">Conclusion</h2><p>That’s all I have on grid-aware websites for now! It’s been hard to summarize a years worth of tinkering and thinking into a single blog post, and I really hope I’ve done the subject justice. If you are at all interested in getting involved, the <a href="https://climateaction.tech/">ClimateAction.tech Slack</a> is a wonderful group where much of this conversation happens. Or, you can try to implement the code yourself, feel free to reach out if you want to give this a shot on your own site.</p></section>
<section><h2 id="related-reading">Related reading</h2><ul>
<li><a href="https://developers.thegreenwebfoundation.org/grid-aware-websites/tutorials/grid-aware-tutorial-cloudflare-workers/">Green Web Foundation’s developer tutorial</a></li>
<li>Fershad’s <a href="https://fershad.com/writing/making-this-website-grid-aware/">case study for implementing grid-awareness on his own site</a> has all the technical background you need behind the plugin</li>
<li>Fershad also has a <a href="https://fershad.com/writing/visual-changes-grid-aware-website/">writeup of the corresponding visual changes on his site</a></li>
<li><a href="https://fershad.com/notes/progressive-enhancement-graceful-degradation/">Progressive enhancement and graceful degradation in the context of grid-aware websites</a></li>
<li>I’ve linked the <a href="https://branch.climateaction.tech/issues/issue-9/designing-a-grid-aware-branch/">Branch Magazine case study</a> before, but I’m going to do it again because it’s my blog and I’m the boss around here</li>
</ul></section>]]></content>
        <published>2025-08-26T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Read the latest issue of Branch Magazine]]></title>
        <id>https://www.nicchan.me/blog/read-the-latest-issue-of-branch-magazine/</id>
        <link href="https://www.nicchan.me/blog/read-the-latest-issue-of-branch-magazine/"/>
        <updated>2025-07-24T14:31:00.000Z</updated>
        <summary type="html"><![CDATA[Bridging the gap between web development and sustainability.]]></summary>
        <content type="html"><![CDATA[<p>I always had a strong interest in environmental and sustainability causes. At age twelve, I made an unusual decision for a child of that age and decided to boycott Hong Kong Disneyland. This was disappointing news to my father, who had worked on the project and understandably wanted to to take his children to Disneyland to show off his hard work. I had boycotted it because the land reclamation efforts for Disneyland had destroyed much of the habitat for Hong Kong’s most adorable endangered species, the pink dolphin. Do you know how hard it is for a twelve year old to boycott Disneyland? It’s pretty dang hard.</p>
<p>Still, while I <strong>cared</strong>, and have always cared, I was never really able to translate that care for the environment into my professional work. I tried my absolute best to build fast, performant sites that are usable by everyone and while that definitely has some impact on sustainability, I wouldn’t say that I focused on it or developed any sort of reputation as a sustainability expert.</p>
<p>Needless to say, I was pretty shocked when Fershad reached out to me to consider applying for the <a href="https://www.thegreenwebfoundation.org/news/introducing-our-grid-aware-websites-project/">Grid-aware websites advisory group</a>, a group of technical experts to help advise on the development of a toolkit that would allow a website to respond to the ‘cleanliness’ of a user’s energy grid.</p>
<p>As I read through the proposal, key phrases jumped out at me. “Oh, they want to gather folks from open-source CMS sustainability teams, that’s definitely not me. Also, they want people who have already worked on public sustainability initiatives, that’s not me either.” Still, the idea sounded really interesting, and I managed to suppress my imposter syndrome long enough for me to fill out the form and hit apply.</p>
<p>I’m so, so glad I did. Against all odds, I was accepted, and getting to geek out with folks over sustainability and how it relates to the front-end every now and then has definitely been a highlight of a crappy year. There’s just something about occasionally being surrounded by people who give an actual shit about the environment; it feels like a much-needed panacea to living in a world where it seems like people are happy to boil an ocean if it saves them time personally.</p>
<p>I know that the work we do here is a small drop in the bucket compared to way AI is rapaciously gobbling up the internet, but it has done wonders for my mental health and my desire to go on and keep fighting, and that feels more important now more than ever.</p>
<section><h2 id="the-green-web-foundation-rocks">The Green Web Foundation rocks</h2><p>The folks over at the Green Web Foundation are some of the loveliest people I’ve had the opportunity to meet. Chris Adams is so incredibly articulate that even having my words summarized by him after a group discussion feels like a transformative experience. Hannah Smith has such a joyous presence that even though the meetings usually occur after a long day of work in my timezone, I instantly feel perk up whenever I’m in one. Fershad Irani, AKA Fish, is so cool and smart that you think he would be incredibly unapproachable, but he’s actually incredibly friendly and chill. All that’s to say, if you ever get the chance to work with or support the Green Web Foundation, don’t pass up the opportunity! People working at smaller, mission-driven organizations don’t get enough thanks or recognition compared to companies with larger marketing budgets, especially a time when the wider culture is doing its best to trample all over their work. I just wanted to say a big thank you to the Green Web Foundation for all their hard work!s</p><p>This leads me to the title of this post, which is to read the latest issue of Branch magazine. This issue is called ‘Attunement – Designing in an Era of Constraints’, and has a lot of wonderful writing about what folks have been up to in the grid-aware advisory group. As I’ve mentioned, I’ve always found it difficult to connect the things I cared about with my work in web development, and if you relate at all to this sentiment, you might find this issue an especially good one to check out.</p><a class="button--ui" href="https://www.nicchan.me/services"><span class="button--ui__content"><p>Read the latest issue of Branch Magazine!</p></span></a></section>
<section><h2 id="favorite-articles">Favorite articles</h2><ul>
<li><a href="https://branch.climateaction.tech/issues/issue-9/a-digital-almanac-attuning-our-web-habits-to-the-natural-world/">A Digital Almanac: Attuning Our Web Habits to the Natural World</a> by Lucy Sloss. Quiet and contemplative, this piece contextualizes sustainability within larger trends in technology in a way that makes me feel hopeful.</li>
<li><a href="https://branch.climateaction.tech/issues/issue-9/designing-a-grid-aware-branch/">Designing a Grid-Aware Branch</a> by Hannah Smith, Fershad Irani, and Tom Jarrett. You all know I <strong>love</strong> a good case study. This one is no exception, I especially love all the little details and specifics, there are a tons of valuable practices here that make me reexamine how I build websites.</li>
<li><a href="https://branch.climateaction.tech/issues/issue-9/ai-climate-and-the-global-majority-a-just-transition-towards-cop30-and-the-peoples-summit/">AI, Climate, and the Global Majority: A Just Transition Toward COP30 and the People’s Summit</a> by Lori Regattieri. A thought-provoking look at AI and the energy crisis from the perspective of climate justice and technological sovereignty. If this has to happen, it needs to happen with a consideration for the many folks who would be most affected by climate disaster.</li>
<li><a href="https://branch.climateaction.tech/issues/issue-9/finding-harmony-between-business-and-sustainability-with-grid-aware-computing/">Finding Harmony between Business and Sustainability with Grid-Aware Computing</a> by Michael J. Oghia, James Hobbs (Contributor) and Andy Eva-Dale (Contributor). As an accessibility person, communicating why you should implement something to corporate stakeholders is something I’m simultaneously pretty familiar with and not very good at. Who would have known that ‘you should give a shit about other people!’ is not a particularly effective argument in the boardroom. I appreciate the deep dive into how to communicate the importance of giving-a-shit into corporate speak.</li>
</ul></section>
<section><h2 id="authors-note">Author’s note</h2><p>Hey Nic? You’ve forgot to explain what the heck a Grid-aware website actually is.</p><p>I know, and it’s coming! Due to some changes in the availability of the Electricity Maps API, I’ve had to hit pause on a demo, and I’d really like to have something that people can click around. In the mean time, the aforementioned <a href="branch.climateaction.tech/issues/issue-9/designing-a-grid-aware-branch/">Branch Magazine redesign article</a> has a really good explainer.</p></section>]]></content>
        <published>2025-07-24T14:31:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building my new website: A modern approach to font fallbacks with font property adjustments]]></title>
        <id>https://www.nicchan.me/blog/building-my-new-website-part-6-font-fallbacks/</id>
        <link href="https://www.nicchan.me/blog/building-my-new-website-part-6-font-fallbacks/"/>
        <updated>2025-07-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Also, how personal is too personal for a personal site?]]></summary>
        <content type="html"><![CDATA[<p>This is a post in a series about building my new website, check out <a href="https://www.nicchan.me/blog/building-my-new-website-part-5/">the previous post in this series.</a></p>
<section><h2 id="a-note-from-the-present-day">A note from the present day</h2><p>Hi, it’s present-day Nic here. I feel bad that this draft has been sitting in my posts for the better part of a year! I had pretty much drafted the complete text back then, but taking three screenshots proved to be an insurmountable task for an entire 8 months, whoops! But we’re nearly at the end of this journey of building this site, thank you for still reading these and all your kind words!</p><p>I also found some time to add drag and drop (desktop only) + better window layering without compromising the no-JS experience. A big thanks to everyone who nudged me to do so, I probably would have never gotten around to it otherwise. That’s one of the beautiful things about a progressive-enhancement driven approach. Coming up with a core, workable experience and adding flourishes later means that the older behavior can still exist as fallback behavior. I initially used CSS to manage layered windows with as little complexity as possible: any window with a focused element in it just had a higher z-index. I’ve now added some JavaScript on top to make the stacking feel more natural, but the old approach still totally works to ensure all content is readable even without JS!</p><p>Back to the past…</p></section>
<section><h2 id="fonts">Fonts</h2><p>It’s at this point I start doing some performance testing. One of the key issues was that I had a huge amount of Cumulative Layout Shift as the fonts loaded. The font I had chosen, W95FA, is pretty narrow compared to other fonts, and I wanted to see if I could minimize the amount of jank as the fonts came in. The layout of my site meant that containers resizing would feel especially choppy. I also knew that I would want to add a toggle between pixel and anti-aliased fonts, as it was a setting I’ve seen in video games to improve accessibility for those who struggled with reading pixel fonts. Switching between the two would need to feel pretty seamless, with as little layout shift as I could manage.</p><p>At some point, I had stumbled upon the website <a href="https://modernfontstacks.com/">Modern Font Stacks</a>, which provides a list of font fallbacks by style to cover every OS. I really liked the Humanist font stack, but the question remained on how to make sure the transition was seamless.</p><img alt="The font fallback tool has a preview where you can see your webfont (in teal) with the font fallback underneath it in yellow, allowing for tweaking to get the alignment as close as possible. Below the font previews is CSS code with adjusted font properties." loading="lazy" decoding="async" sizes="(min-width: 1842px) 1842px, 100vw" style="object-fit:cover;max-width:1842px;max-height:1544px;aspect-ratio:1.1930051813471503;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_536,c_lfill,f_auto/v1753141522/font-fallback 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_629,c_lfill,f_auto/v1753141522/font-fallback 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_694,c_lfill,f_auto/v1753141522/font-fallback 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_805,c_lfill,f_auto/v1753141522/font-fallback 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_905,c_lfill,f_auto/v1753141522/font-fallback 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1073,c_lfill,f_auto/v1753141522/font-fallback 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1398,c_lfill,f_auto/v1753141522/font-fallback 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1842,h_1544,c_lfill,f_auto/v1753141522/font-fallback 1842w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1609,c_lfill,f_auto/v1753141522/font-fallback 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1717,c_lfill,f_auto/v1753141522/font-fallback 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_2146,c_lfill,f_auto/v1753141522/font-fallback 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2682,c_lfill,f_auto/v1753141522/font-fallback 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3684,h_3088,c_lfill,f_auto/v1753141522/font-fallback 3684w" src="https://res.cloudinary.com/nicchan/image/upload/w_1842,h_1544,c_lfill,f_auto/v1753141522/font-fallback"><p>Luckily, I discovered this helpful little <a href="https://screenspan.net/fallback">Font Fallback tool</a>, which lets you upload a font and then visually adjust the new <code>size-adjust</code>, <code>ascent-override</code>, <code>descent-override</code> and <code>line-gap-override</code> properties with sliders in order to create a seamless fallback. This is an absolutely wonderful tool, my only gripe is that it doesn’t have a whole bunch of fallback fonts, so I had to edit the source code so I could make it work with my chosen stack. So yes, I acquired every single one of these fallback fonts, modified the source code of this website to allow me to use it as a fallback, and played around with the little sliders until it was just right for each fallback. I think this kind of touch is the kind of thing that adds a lot of polish to a website, but is often overlooked.</p></section>
<section><h2 id="artwork">Artwork</h2><img alt="The artwork view has a bunch of digital and traditional paintings arranged as individual windows in a operating system style interface." loading="lazy" decoding="async" sizes="(min-width: 3414px) 3414px, 100vw" style="object-fit:cover;max-width:3414px;max-height:2061px;aspect-ratio:1.6564774381368268;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_386,c_lfill,f_auto/v1753141523/artwork 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_453,c_lfill,f_auto/v1753141523/artwork 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_500,c_lfill,f_auto/v1753141523/artwork 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_580,c_lfill,f_auto/v1753141523/artwork 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_652,c_lfill,f_auto/v1753141523/artwork 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_773,c_lfill,f_auto/v1753141523/artwork 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1007,c_lfill,f_auto/v1753141523/artwork 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1159,c_lfill,f_auto/v1753141523/artwork 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1236,c_lfill,f_auto/v1753141523/artwork 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1545,c_lfill,f_auto/v1753141523/artwork 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1932,c_lfill,f_auto/v1753141523/artwork 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3414,h_2061,c_lfill,f_auto/v1753141523/artwork 3414w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2318,c_lfill,f_auto/v1753141523/artwork 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_2705,c_lfill,f_auto/v1753141523/artwork 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_5120,h_3091,c_lfill,f_auto/v1753141523/artwork 5120w,
https://res.cloudinary.com/nicchan/image/upload/w_6016,h_3632,c_lfill,f_auto/v1753141523/artwork 6016w,
https://res.cloudinary.com/nicchan/image/upload/w_6828,h_4122,c_lfill,f_auto/v1753141523/artwork 6828w" src="https://res.cloudinary.com/nicchan/image/upload/w_3414,h_2061,c_lfill,f_auto/v1753141523/artwork"><p>I’ve always been torn about having an artwork section on my site. It’s not at all related to what I do professionally, and I always wondered if I would be judged for having a section dedicated to it. Through the magic of the indie web, I stumbled <a href="https://joshcrain.io/">Josh Crain</a>, a fellow web developer’s website, and I really enjoyed the artwork section. His work is full of light, and I really resonated with his artistic journey. I’m so glad I found his site, because it helped me decide that I didn’t particularly care any more if anyone thought negatively about it, and I implemented an artwork section for my site! This isn’t a super popular page on the site, but those who like it seem to <strong>really</strong> love it, so I’ll chalk this up as a success.</p><p>Selecting the pieces was the most difficult part. I knew I wouldn’t be able to feature too many works on this page with the layout that I had in mind, so I had to be more choosey than I’d like. In the end, I chose a mix of pieces from the mediums I’ve been enjoying in the past few years (digital, but only the <a href="https://www.heavypaint.com/">HEAVYPAINT</a> program) and gouache, with a mix of plein air pieces in there.</p><p>Development was straightforward as this was one of the very first sections that I designed when coming up with the initial idea for my site. As soon as I saw my first View Transitions demo, I knew I wanted to utilize it to power this section. When a window in this section is maximized, the window itself is cross-faded and the caption fades in, but a separate view transition on the artwork makes it scale up smoothly. I really can’t believe what you can get with CSS (and a sprinkle of JS to manage state and fallbacks!)</p></section>
<section><h2 id="work">Work</h2><img alt="Some before and afte rimages in the work section arranged on an asymmetric grid, with a decorative flourish and an image caption." loading="lazy" decoding="async" sizes="(min-width: 3423px) 3423px, 100vw" style="object-fit:cover;max-width:3423px;max-height:2577px;aspect-ratio:1.3282887077997672;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_482,c_lfill,f_auto/v1753141523/work 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_565,c_lfill,f_auto/v1753141523/work 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_623,c_lfill,f_auto/v1753141523/work 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_723,c_lfill,f_auto/v1753141523/work 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_813,c_lfill,f_auto/v1753141523/work 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_964,c_lfill,f_auto/v1753141523/work 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1256,c_lfill,f_auto/v1753141523/work 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1445,c_lfill,f_auto/v1753141523/work 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1542,c_lfill,f_auto/v1753141523/work 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1927,c_lfill,f_auto/v1753141523/work 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2409,c_lfill,f_auto/v1753141523/work 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3423,h_2577,c_lfill,f_auto/v1753141523/work 3423w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2891,c_lfill,f_auto/v1753141523/work 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_3373,c_lfill,f_auto/v1753141523/work 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_5120,h_3855,c_lfill,f_auto/v1753141523/work 5120w,
https://res.cloudinary.com/nicchan/image/upload/w_6016,h_4529,c_lfill,f_auto/v1753141523/work 6016w,
https://res.cloudinary.com/nicchan/image/upload/w_6846,h_5154,c_lfill,f_auto/v1753141523/work 6846w" src="https://res.cloudinary.com/nicchan/image/upload/w_3423,h_2577,c_lfill,f_auto/v1753141523/work"><p>I was really dreading the work section. Because I had places where I was showing desktop screenshots, mobile screenshots, sometimes in comparison with each other, I knew I had to come up with layouts that would work for 1-2 images of any aspect ratio and an optional caption, which is a lot more challenging than a designer-lite like me initially thought. I really did not want to be in a position where I would have to write new styles every time I added a case study, so I decided to embrace asymmetry and came up with a limit of three image layouts that I could use as needed. This part of the site seemed like the perfect time to try out container queries, as we only care about sizing within the avaialable window space, not the whole viewport!</p><p>I also decided to keep the chunky background line I had in the previous version of my site as a design element, with the addition of pixel half tones in order to fit in better with this version. I’ve always wanted to do fun, CSS Grid overlapping layouts with different textures, but never had the chance to on client work. I try to treat my portfolio like a garden: you don’t need permission to grow things in your own garden, so you might as well plant what makes you happy. I’m not entirely sure if this metaphor is appropriate, I’ve never grown up in a place where I could have a garden and I am well known in my friend group as a plant-murderer, so please do not construe this as actual gardening advice.</p></section>]]></content>
        <published>2025-07-22T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Please, can we have ARIA Notify]]></title>
        <id>https://www.nicchan.me/blog/please-can-we-have-aria-notify/</id>
        <link href="https://www.nicchan.me/blog/please-can-we-have-aria-notify/"/>
        <updated>2025-07-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A hate letter to live regions.]]></summary>
        <content type="html"><![CDATA[<p>I’m delighted to see that there has been <a href="https://blogs.windows.com/msedgedev/2025/05/05/creating-a-more-accessible-web-with-aria-notify/">more chatter about implementing the ARIA Notify API</a>. A small part of me, the part with an inflated sense of ego, is hoping that my frenzied complaints with fellow accessibility specialists has found its way into the right ears and that my griping has moved the needle. The larger, more rational part of me realizes this is unlikely, and that is more likely that other developers are experiencing the same issues I am. Either way, I’m just glad it’s being considered.</p>
<p>This post is not going into all the details of implementing a live region, as I believe <a href="https://www.sarasoueidan.com/blog/accessible-notifications-with-aria-live-regions-part-2/">Sara Soueidan has written the seminal post on the subject</a> already. If you click through to the post, you will find an extremely detailed breakdown on how to implement a live region in order to work consistently in as many assistive technology combinations as possible. Still, Sara’s final takeaway is worth noting. She writes:</p>
<blockquote>
<p>[!NOTE]
Avoid live regions if you can.
I know this isn’t the advice you’d expect at the end of a whole chapter about live regions. But hear me out.</p>
<p>Live regions are inconsistent and unpreditcable. It’s easy for their implementations to go wrong. There’s a lot of manual work involved to get them working properly.</p>
<p>Furthermore, the design of live regions is intended to give maximum flexibility to screen readers to implement an experience that is best for their users.</p>
<p>What this means is that ARIA live region properties are only <strong>strong suggestions</strong> as to how you want live region announcements to be made, but the value of these properties (and by extension: the behavior of live regions) <strong>may be overridden by browsers, assistive technologies, or by the user.</strong> This along with current bugs and implementation gaps means that you can’t guarantee that a live region will always work the way you designed it to. This is one of the reasons why you should try to rely on live regions as little as possible, and use alternative and more robust approaches whenever you can.</p>
</blockquote>
<p>This pretty much sums it up. Unfortunately, it is my experience that an application with any level of interactivity greater than a static page may need to use live regions, but live regions are a pretty awful tool for the case they were meant to handle.</p>
<p>When I worked on <a href="https://www.nicchan.me/work/audiom/">Audiom</a>, live regions were a necessity for what we were trying to accomplish, there was no way we could minimize our usage of them. To give a brief description, Audiom is an accessible map viewer that allows for users to navigate the map by utilizing the keyboard. As a user presses an arrow key, they take one ‘step’ forward in the map, and a sound cue or text announcement is played stating the users current position, much like a video game. We wanted announcements to happen without moving focus, because a map is not particularly useful if you have to keep tabbing back to it for every step you take.</p>
<p>This post will just cover why I’m hoping and praying with every fiber of my being that we will get the ARIA Notify API across all browsers at some point in the future.</p>
<section><h2 id="it-doesnt-work-when-hidden">It doesn’t work when ‘hidden’</h2><p>One of the cardinal rules of live regions is that they work best when the element already exists on the page. Another rule is that they don’t work if the part of the accessibility tree they are in is hidden from the accessibility tree, ie. when <code>display: none</code>, the <code>hidden</code> attribute, or any other method of hiding text via CSS is applied. This means they need to be present in the DOM, and the content within them is accessible to screen reader users.</p><p>This has a lot of potential to confuse users. For example, let’s take a standard e-commerce announcement, when a user adds an item to cart, you could have a live region announcement that says ‘(item name) has been added to the cart.’ (We’re just not going to get into whether or not this is the best technique to communicate this particular example message, or we’ll never get through the article. I gave <a href="https://www.nicchan.me/blog/talk-developing-an-accessible-add-to-cart-flow/">a whole talk on this</a> at one point.)</p><p>That means, the screen reader user has the potential to accidentally stumble across the message as it is just inserted in the DOM. How confusing would it be to accidentally find a message like this as you’re browsing the page? Wouldn’t you think you’ve just added a second item to your cart?</p><p>As a result, accessibility experts typically recommend you 1. stick the live region somewhere where a user is unlikely to encounter it, such as the bottom of a page and 2. wait a period of time, not too short and not too long of a time, before clearing out the live region so that screen reader users don’t accidentally stumble across it again. A mysterious timeout number that no one can really nail down, definitely not a code smell, right?</p><p>The fact that live regions work most consistently when the element already exists on the page is in opposition to how developers usually would implement such status updates. If you need live regions, you’re probably working on a somewhat complex application, possibly managed by a JavaScript framework. You probably don’t have all possible status messages already rendered on the page, but hidden to users. You’re likely creating these status message elements on the fly, and adding them to the DOM whenever needed. This means that the way we show status messages to sighted users and the way that live regions work are fundamentally in conflict with each other.</p><p>Even if you listen to popular advice to avoid live regions wherever possible, if it does turn out that live regions are the most correct way to implement your desired UI, you may need to set up two different interfaces, one for sighted users and one for non-sighted users. And I think we all know who gets the short end of the stick when the deadline looms and the budget gets cut.</p></section>
<section><h2 id="fun-with-modal-dialogs">Fun with modal dialogs</h2><p>Requiring live regions to not be hidden is not the only trap. As I mentioned before, live regions need to be accessible within the current accessibility tree, which means that content that is outside of a dialog or made inert through other means is ignored. In simpler terms, if you need announcements within a modal dialog, you need a new live region within that dialog. Managing this overhead is yet another piece of kindling for the bonfire of how terrible live regions are to work with.</p></section>
<section><h2 id="developers-are-lazy">Developers are lazy</h2><p>If you’re reaching for the live regions, you probably have good intentions. Because there isn’t really a purpose for live regions other than for accessibility, if you are trying to use them it probably means you want to do the right thing. While live regions are not quite up there with <a href="https://sarahmhigley.com/writing/activedescendant/">aria-activedescendant</a> in terms of ‘how much does implementing this feel like navigating a minefield’, the truth is that every developer who reaches for live regions ends up <a href="https://tetralogical.com/blog/2024/05/01/why-are-my-live-regions-not-working/">needing to implement them in a very particular way</a></p><p>It all feels like a lot of unneeded effort. When the underlying primitives are difficult, this has a knock-on effect where everything that depends on that primitive gets more complicated to implement correctly. It always sucks when an interface is not accessible, but when it is not accessible despite everyone involved wanting it to be accessible, that sucks even harder. This leads me to my next point…</p></section>
<section><h2 id="the-inconsistency-is-built-in-the-spec">The inconsistency is built in the spec</h2><p>When I was working on Audiom, something happened that clearly highlighted to me why live regions are flawed and never going to solve the problems we need it to solve.</p><p>Navigation worked well in NVDA, but JAWS users faced enormous lag when holding down an arrow key (ie. traversing the map very quickly and queuing up many messages). When we filed an issue, a JAWS developer informed us of what it said in the spec:</p><blockquote>
<p>When regions are specified as assertive, assistive technologies will immediately notify the user, and <strong>could</strong> potentially clear the speech queue of previous updates.</p>
</blockquote><p>In case you missed it, the key word here is ‘could’. This means that two different assistive technologies could have completely different behaviors, and it would be fine according to the specification. In our use case, being able to clear out the queue of messages is essential for making it work on all screen readers, and this is something not supported consistently by live regions.</p><p>I know some folks are probably thinking, couldn’t you just debounce this? We did try but with an application like this, there wasn’t really an acceptable ‘timeout’ that didn’t make the experience worse for users of other screen readers. If an audio track for a movie or a video game was a couple hundred milliseconds off, it would be quite jarring. There are just some use cases that require more control over timing.</p><p>The <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Accessibility/AriaNotify/explainer.md#interrupt">ARIA Notify explainer</a> mentions an interrupt property that isn’t implemented in this initial spec, but it would be fantastic to implement in the future. It mentions other real-world use cases as well, such as interrupting a stream of chat messages when a user sets their status to ‘Do not disturb.’</p><p>People who are involved in specifications know that generally speaking, changing existing things is pretty difficult due to the backward-compatible nature of the web, but creating new, better things is slightly easier, and I really believe that ARIA Notify has the potential to be that better thing.</p></section>
<section><h2 id="potential-for-improved-functionality-from-assistive-technologies">Potential for improved functionality from assistive technologies</h2><p>The way screen readers interact with live regions is okay, at best. One of the reasons why accessibility experts often recommend against live regions is that they are really fallible, even when you do everything correctly. In the ARIA Notify explainer, it says that:</p><blockquote>
<p>The available priority controls (<code>assertive</code> vs. <code>polite</code>) are not well specified and are up to the interpretation of screen readers. In one instance, an author wanted to make a live region announcement immediately following a user action to supplement it with related context. However, the <code>polite</code> setting was too polite; a subsequent focus change would always mute the announcement. The <code>assertive</code> setting was too assertive and caused subsequent (important) focus change context to be lost while the assertive announcement was made.</p>
</blockquote><p>In my experience, I’ve found this exact scenario to be very common. Users interacting with an application as normal can accidentally skip over the live region announcement just by continuing to use the website, without recourse.</p><p>For normal text that is in the DOM, a screen reader has the ability to drill down into the text, playing it again if needed or traversing it letter by letter. Live regions don’t have this, so at Audiom, we added a keyboard shortcut to announce the most recently announced message, and we also built a speech log component if users needed to revisit certain text in more detail.</p><p>I haven’t seen any discussion around this, but I think it would be amazing if screen reader vendors could implement similar features into the screen readers themselves. If a user is interrupted for any reason, being able to replay the last few announcements with a shortcut key would really help the usability of complex applications. A potential new API creates an opportunity to really think about user experience enhancements like this without breaking anything.</p></section>
<section><h2 id="the-internationalization-issue">The internationalization issue</h2><p>I would be remiss if I didn’t mention a potential flaw I see with the ARIA Notify API, which is essentially the same problem with ‘aria-label’ and translations. Of the techniques to give a control an accessible name, aria-label is usually ranked near the bottom, with a key reason being that it doesn’t play well with <a href="https://adrianroselli.com/2019/11/aria-label-does-not-translate.html">automatic translation tools</a>.</p><p>In my testing, I found that live regions did translate with things like the in-built Google Translate in Chrome. Even if it was a slightly janky experience where the announcement would be read first in the original language before announcing the translated one, hearing something in the correct language is certainly better than it being ignored.</p><p>I’m not a translation tool developer, but from my outside perspective, I do think it would be much easier to watch the DOM for HTML attribute changes (which aria-label would fall under), rather than JavaScript text announcements, so if these tools aren’t catching aria-label, this doesn’t bode well for any ARIA Notify announcements.</p><p>I would personally really hate to see something like ARIA Notify be implemented only for it to be advised against due to the lack of translation support, so I hope we can think through this a bit more and avoid creating something that technically exists but is practically useless, such as <a href="https://a11ysupport.io/tech/aria/aria-controls_attribute">aria-controls</a>, or something that is useful but strongly recommended against, like aria-label.</p></section>
<section><h2 id="closing-thoughts">Closing thoughts</h2><p>I hope this article has communicated why I think we need something better than live regions for screen reader announcements. I know things like ARIA Notify are not as cool and fun as potential layout features such as CSS Masonry or View Transitions, but I genuinely believe that having different tools for screen reader announcement would make it easier to create accessible applications.</p><p>I don’t see ARIA Notify as replacing live regions, they still have their place. I would still recommend using a live region for text that is visible on the screen that you just need to update, such as updating the visible search results count when new results are fetched. ARIA Notify would just add a little more control where it is desperately needed. We have many ways to hide an element in HTML and CSS, each with different, valid use cases, so why can’t we have multiple ways to announce text to assistive technologies?</p><p>(Thank you EJ Mason for pushing me to publish!)</p></section>]]></content>
        <published>2025-07-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Was my IAAP Certification worth it?]]></title>
        <id>https://www.nicchan.me/blog/was-my-iaap-certification-worth-it/</id>
        <link href="https://www.nicchan.me/blog/was-my-iaap-certification-worth-it/"/>
        <updated>2025-07-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Plus a bunch of easily detectable errors, you're welcome!]]></summary>
        <content type="html"><![CDATA[<p>A reader of the blog asked me if my International Association of Accessibility Professionals (IAAP) Web Accessibility Specialist (WAS) certification was worth it. A ton of thoughts about this have been circling my head recently for reasons I will get into later, so I thought it was high time that I got around to writing this post. I took the exam way back in November 2020, so my details might be a little fuzzy. I will try my best to recall them as faithfully as I can.</p>
<section><h2 id="the-exam">The exam</h2><p>I signed up for the November 2020 exam around the time I ended a full-time engagement with a long-term client. Although I tried my best to implement accessibility in the day to day work, I really wanted to be able to do more work that was specifically focused on accessibility, and getting certified felt like the best way to get my foot in the door. Of the available credentials, the IAAP WAS certification seemed like the one that aligned most closely with my goals as a web developer who also does accessibility (as opposed to a designer, UX researcher or tester).</p><p>When I signed up for the exam, I was still doing full-time work. I made paper flash cards, as I’ve always found the physical act of writing to be essential for actually remembering things. I also signed up for the paid course at <a href="https://dequeuniversity.com/">Deque University</a>, which I found to be quite helpful (not sponsored). Once I ended the full-time contract, I had a couple of weeks where I only had part-time work, so I basically studied as much as I could for those two weeks outside of my working hours. I was lucky enough to pass the test on the first try, which was great for me because I don’t know if I would have been able to find the time to study on top of a full-time job.</p><p>The questions I struggled with the most were questions about keyboard shortcuts for different screen readers, and questions about different laws around the world. This may be a little odd, but when I use the keyboard, I don’t consciously think about what keys I’m pressing, I basically remember what finger motion I do to activate the key. I also have a fancy ergonomic keyboard so I remap a bunch of default shortcuts to be more comfortable for me, so remembering combinations that feel arbitrary is genuinely pretty difficult.</p><p>I’m more well-versed in the legal aspects now, especially now that I’ve done more accessibility work, but I do feel that it is slightly western-centric in that I’m sure there are plenty of other accessibility experts who do not live in the regions that the test tends to ask about. Some people have also levied the criticism that such a focus on rote memorization for these kinds of facts is ableist, which I think is important to keep in mind.</p></section>
<section><h2 id="the-impact-it-had-on-my-career">The impact it had on my career</h2><p>I do think it helped, marginally. In my experience, people in the accessibility field do not find IAAP certifications to be a particularly useful as a benchmark for how ‘good’ someone is at accessibility. Almost none of the folks I respect the most in the field have a certification. However, it is somewhat of a useful tool for appealing to the ‘box-checking’ folks who might be in charge of an organization’s accessibility processes. I’ve been on teams where I was the only person with a IAAP certification, and the fact I had one was seen as an asset for those seeking work in government or education, because they could point to my certificate as additional validation.</p><p>As a self-taught developer, I’ve often had bouts of imposter syndrome. It can be difficult to feel secure in your accessibility knowledge when you know that mistakes can contribute to the further oppression of marginalized groups. This is something I genuinely struggle with, even while trying to remind myself that I am still human and that I will mess up sometimes. Having some sort of validation in the form of a very expensive piece of paper did feel like a shield against the insecurities in the early part of my career.</p><p>However, despite this, I can’t say I’ve ever gotten any accessibility work by virtue of just being listed on their site. They used to have a public facing page that just listed everyone by name, but now they’ve switched to a <a href="https://www.accessibilityassociation.org/certification-portal/s/certified-professional-directory">verification form</a> type of interface. They’ve also now listed my country as China which is not particularly useful for me as I do not speak Mandarin well enough to do accessibility work with it and it may deter my actual audience (basically anyone who speaks English) from looking me up.</p><p>If I try to think about all the inbound leads I’ve had related to accessibility, most of them were via word-of-mouth. Of those who didn’t reach out as the result of a direct referral, one mentioned a blog post that I wrote, and two have mentioned watching my recorded talks on accessibility. This definitely isn’t a statistically significant number of results, but going forward I’m likely to invest more time and effort in those areas instead.</p></section>
<section><h2 id="the-caec-process">The CAEC process</h2><p>We’ve reached the spicy part of the post! I recently came across <a href="https://www.linkedin.com/feed/update/urn:li:activity:7338112202932936705/">this post by Rian Rietveld.</a> For those in the accessibility field, Rian should need no introduction. She is a pillar in the industry and her contributions have made the web a better place. The post is well-worth the read, but to summarize, Rian was frustrated that her paid, public work for various governments and on the Wordpress Accessibility Team did not count for her Continuing Accessibility Education Credit (CAEC) requirements.</p><p>What are CAECs? I’m glad you asked! The IAAP certificates are valid for 3 years, and require a certain number of hours of additional engagement in the accessibility field outside of paid work. The exact number of hours varies by certificate, the WAS certificate I took requires 35 CAECs, which is around 12 CAECs a year.</p><p>To understand Rian’s post, we need to look a bit more at the specific CAEC requirements. I’ve rounded them up into the summary below:</p><ol>
<li>Professional Development CAECs are counted in 15-minute increments rounded up to the nearest number. Accepted formats:
<ul>
<li>Attending live/archived webinars or workshops</li>
<li>Attending and completing online course(s)</li>
<li>Attending accessibility presentations at industry conference(s)/summit(s)</li>
<li>Attending course(s) from educational institutions- Note capped at 15 CAECs per course per year.</li>
</ul>
</li>
<li>Sharing Your Accessibility Knowledge CAECs breakdown
<ol>
<li>One hour of structured teaching content delivery = 3 CAECs (two hours to prepare for one hour of structured delivery)</li>
<li>Featured speaker or speaking engagement = 2 CAECs per hour of speaking</li>
<li>One Year’s Active Volunteer Committee Work within the Accessibility Profession= 5 CAECs per committee</li>
<li>Publication of blog series (more than five) regarding accessibility = 5 CAECs per year</li>
<li>Publication of professional or scholarly article = 5 CAECs per article</li>
<li>Publication of a book regarding accessibility = 6 CAECs</li>
<li>Organizing accessibility Meetups = 1 CAEC per event as the organizer</li>
<li>Organizing local accessibility conferences = 5 CAECs per event. Must be listed as a lead position on the conference organizing committee.</li>
</ol>
</li>
<li>IAAP Volunteering CAEC breakdown
<ol>
<li>One Year’s Active IAAP or G3ict Committee Work = 5 CAECs per committee</li>
<li>One hour of IAAP volunteer service (outside of committee work) = 1 CAEC</li>
<li>Please note: Volunteer engagement is capped at 15 CAECs per year.</li>
</ol>
</li>
</ol><p>To sum it up, the IAAP’s wants you to earn your CAECs through the Professional Development category. This is the only category where each hour of your time is granted one whole CAEC, except for the option where you can sign up to volunteer for the IAAP itself, which also grants you one CAEC per hour you spend.</p><p>Rian’s feedback, that her paid work for open standards and public distribution should count for CAECs, and I fully agree. She met with an IAAP representative and it seems they will make steps forward in this regard. It’s much needed, and I really hope they listen to her feedback.</p><p>Besides this, I still have a few other complaints.</p><p>For the Professional Development category, basically the only realistic category in which people can rack up points, has a heavy location bias. When I was doing research for this post, I found one of the IAAP folks saying that they managed to achieve their year’s credits by attending one lunchtime webinar a month, implying that it was that easy. Do you know what time that lunchtime webinar is at for me? It’s 1am! I am barely holding back my rage here.</p><p>I live in a place where there aren’t really any local or far-but-timezone-appropriate events I can attend during normal working hours. I’m mostly limited to online, live-streamed events that I can watch later. And I’m so grateful that there are at least a few free options to choose from. If <a href="https://inclusivedesign24.org/2025/">Inclusive Design 24</a> and <a href="https://www.deque.com/axe-con/">axecon</a> didn’t exist, I probably wouldn’t have a way to get enough CAECs without shelling out for IAAP’s own paid webinar series.</p><p>As grateful as I am that free resources exist, I can’t count the number of times I’ve sat through a thinly-veiled product advertisement or an Accessibility 101 talk for a CAEC. I can’t help but wonder if there are better uses of my time. To put it frankly, I don’t really know how much time I have left on this earth, but I do know that I want to spend as little of it as possible in yet another webinar that I don’t care about.</p><p>Which brings us to our second point, the Sharing Your Knowledge CAEC requirements are so strict that they are functionally useless. I’ve never written a book, but I’m sure it takes more than 5 hours. If you’ve organized an accessibility conference but aren’t the number one lead organizer, you’re out of luck.</p><p>If you are paid for any of this work, this means it is invalid for CAECs. As a freelancer, I have no company behind me that will sponsor my presence at any events. If I choose to speak at an event, I may receive a stipend that would cover 1/10th of the hourly rate I would charge if I were doing development work, and somehow, this invalidates the CAEC.  I get it, people who are sponsored by their organizations shouldn’t get a free pass at a certificate renewal, but this seems like an unnecessarily strict boundary, as Rian pointed out.</p><p>Additionally, the other work that is deemed appropriate for the CAEC is just… completely out of touch with what web accessibility experts do. Forgive me for tooting my horn for a tiny bit, but I have:</p><ul>
<li>Written blog posts that are referenced by multiple governments around the world</li>
<li>Written a posts for publications that reached hundreds of thousands of page views, for which I received a nominal stipend equivalent to one or two hours of my normal rate</li>
<li>Conducted free (publicly documented) accessibility testing for a variety of products and organizations, leading to more accessible products used by millions</li>
<li>Contributed to the A11y Project, including a substantial rewrite adding dark mode to the site</li>
</ul><p>Honestly, this pales in comparison to the amount of work other folks I know do. I’m only really a ‘few times a year’ blogger at best, many folks I know publish on a regular schedule. Not one of these accomplishments would count for a single CAEC. The requirement that blog posts be in a series of five is honestly baffling to me. I honestly believe that some of the most important pieces of accessibility writing ever are in the form of single blog posts. There are many, many blogs by accessibility experts who I consider required reading for entering the field, and none of this would count for any ‘Sharing your knowledge’ CAECs! I can’t even tell you the last time I read an academic accessibility paper as part of my day-to-day work, but I read accessibility blog posts every single day.</p><p>The failure to include open-source work also feels like a glaring omission. The WAS certification is described as a certification in technical digital proficiency, it’s just weird to me that doing the very same work for free is somehow not included. There are some people I know who refuse to get the certification because it would take away time from their ability to do work with actual impact, and I respect the hell out of that position.</p><p>My conclusion about all of this is that the IAAP does a very good job of promoting accessibility as a money-making business, but not as a field. Why else would you even consider any form of co-operation with <a href="https://karlgroves.com/why-is-iaap-giving-special-treatment-to-overlays/">overlay companies</a>? The Professional Development CAECs are all in easily monetizeable forms, and lead to partnerships with those offering academic courses or paid conferences, even if they don’t relate to how most of us actually stay on top of the field.</p></section>
<section><h2 id="adventures-on-the-iaap-site">Adventures on the IAAP site</h2><p>The final thing that inspired me to write this post is the absolutely horrible time I had trying to submit my CAECs. I’m not exactly sure when, but at some point, the IAAP moved over to a new portal system to submit the credits.</p><p>Upon navigating to the portal, I was greeted with this login form.</p><img alt="A login form, featuring a username and password field, with placeholders as labels within the inputs. The input fields have a white background and a light gray border against a light blue background." loading="lazy" decoding="async" sizes="(min-width: 1170px) 1170px, 100vw" style="object-fit:cover;max-width:1170px;max-height:1011px;aspect-ratio:1.1572700296735905;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_553,c_lfill,f_auto/v1751877943/iaap 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_648,c_lfill,f_auto/v1751877943/iaap 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_715,c_lfill,f_auto/v1751877943/iaap 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_830,c_lfill,f_auto/v1751877943/iaap 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_933,c_lfill,f_auto/v1751877943/iaap 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1170,h_1011,c_lfill,f_auto/v1751877943/iaap 1170w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1106,c_lfill,f_auto/v1751877943/iaap 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1441,c_lfill,f_auto/v1751877943/iaap 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1659,c_lfill,f_auto/v1751877943/iaap 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1770,c_lfill,f_auto/v1751877943/iaap 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2340,h_2022,c_lfill,f_auto/v1751877943/iaap 2340w" src="https://res.cloudinary.com/nicchan/image/upload/w_1170,h_1011,c_lfill,f_auto/v1751877943/iaap"><p>You might think those grey boxes represent inputs, right? Ignoring the fact they use the problematic ‘placeholder text as label’ technique, there is already a Non Text Contrast violation in the fact that the input border or background color don’t contrast enough against the page background.</p><img alt="Closeup on the login field. There is a focus outline around a small section of the supposed field area, around 1/3 of what appeared to be the total clickable area." loading="lazy" decoding="async" sizes="(min-width: 765px) 765px, 100vw" style="object-fit:cover;max-width:765px;max-height:128px;aspect-ratio:5.9765625;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_107,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_125,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 750w,
https://res.cloudinary.com/nicchan/image/upload/w_765,h_128,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 765w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_139,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_161,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_181,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_214,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1530,h_256,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813 1530w" src="https://res.cloudinary.com/nicchan/image/upload/w_765,h_128,c_lfill,f_auto/v1751877941/Pasted_image_20250704185813"><p>The reality is actually much worse! This white rectangle isn’t an input, the actual
input area is a tiny rectangle within the boundaries of the white box, featuring
a focus outline that does not meet contrast requirements! People I know who wouldn’t
dare call themselves accessibility experts out of respect for the field wouldn’t
even make this mistake!</p><p>Once you’re in, the process to submit the CAECs is such poor UX that I may have burst a blood vessel while trying to fill the form in. There is a rich text field to list out what activities you participated in. Since some activities are officially preapproved for CAECs, if it were me I would have made some kind of user interface where you could select a provider, then check off a list of checkboxes instead of having to type it up and calculate the total number of hours yourself. It would probably speed up the process on the validation end too. But this next step is the part that really got to me.</p><p>The previous page informs you that there will be a place to upload the associated files to prove you’ve participated in whatever activities. Why they aren’t on the same page, I’m unsure. When landing on the next page, this is what I see (I’ve blanked out some information here.)</p><img alt="The IAAP certification portal. There is a bar with the current certification information, and a big collapsible region with three disclosures for 'Certification, Status &#38; Dates, CAEC Tracking', taking up the majority of the screen space. Underneath this is a clickable element that says 'Files.'" loading="lazy" decoding="async" sizes="(min-width: 2577px) 2577px, 100vw" style="object-fit:cover;max-width:2577px;max-height:1490px;aspect-ratio:1.729530201342282;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_370,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_434,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_479,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_555,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_624,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_740,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_964,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1110,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1184,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1480,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_2577,h_1490,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 2577w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1850,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2220,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_2590,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_5120,h_2960,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 5120w,
https://res.cloudinary.com/nicchan/image/upload/w_5154,h_2980,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652 5154w" src="https://res.cloudinary.com/nicchan/image/upload/w_2577,h_1490,c_lfill,f_auto/v1751877942/Pasted_image_20250704191652"><p>“Aha, the Files button, that must be it!” I thought. But upon clicking it, I realized my mistake, this was actually not the button that allows you to upload files, which is in a section much farther down the fold. This is in fact, a link, and upon clicking the entirety of your forms progress is lost, an experience so frustrating that it caused me to write this 2500 word blog post on a Friday night.</p><figure><div class="video" style="aspect-ratio: 2034 / 278"><video controls=""><source type="video/mp4" src="https://res.cloudinary.com/nicchan/video/upload/v1751878682/chrome_2xectfp5fO.mp4"></video></div><figcaption><p>In researching this post, I also failed to click a link because clicking on
it caused it to move out of the way. The link lacks and underline and
doesn’t have adequate contrast with the surrounding text, and neither does
the focus outline, by the way. I suspect it ran away from me because it knew
how badly I was going to eviscerate it when I managed to finally inspect it.</p></figcaption></figure><figure><img alt="A disclosure widget with a list of links in it. The currently focused link has a yellow orange focus outline that is not really visible against the white background, and the currently focused text is dark blue with a dark gray background, completely illegible." loading="lazy" decoding="async" sizes="(min-width: 1319px) 1319px, 100vw" style="object-fit:cover;max-width:1319px;max-height:877px;aspect-ratio:1.5039908779931586;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_426,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_499,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_551,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_638,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_718,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_851,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1319,h_877,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 1319w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1109,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1277,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1362,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1702,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_2638,h_1754,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607 2638w" src="https://res.cloudinary.com/nicchan/image/upload/w_1319,h_877,c_lfill,f_auto/v1751877942/Pasted_image_20250704192607"><figcaption><p>This is a real focus style on their site, in a dropdown which is coded as an
ARIA menu when it should probably just be a disclosure widget with links.</p></figcaption></figure><p>I genuinely don’t like to nitpick people unnecessarily, I think it can hurt well-intentioned folks trying to learn. But if you are judging us on our accessibility, I do believe that gives us the right to judge you back. I find it a bit embarrassing that the organization that certifies accessibility experts has such obvious accessibility errors that can be detected with even a superficial glance.</p><p>The accessibility field already struggles with the ‘divide’ between experts and disabled people. With a test that has an ableist focus on rote memorization, a re-certification process that adds additional burden onto a group that may already struggle with having enough spoons to do unpaid work, and an inaccessible website, it sometimes feels like the IAAP just widens the gap between practitioners and the disabled people they’re meant to serve.</p><p>To come back to the original question, I’m still on the fence about renewing my certification. I don’t do a bunch of enterprise or government work so it’s been less useful than I would have hoped. I would rather work with people who already vaguely know who I am and what I bring to the table, but the fact that I would have to take the exam again to regain the certification is definitely holding me back a bit. There is probably enough relevant, on-demand content that I was going to watch anyway to squeeze out another renewal, but I would never judge an accessibility professional for choosing to forgo the certification.</p></section>
<section><h2 id="related-reading">Related reading</h2><ul>
<li>The IAAP re-certification process is but one pain point that <a href="https://lea.codes/posts/2025-04-25-abandoning-the-accessibility-consultant-path/">Lea Rosema</a> has with the field</li>
<li>I linked this earlier, but <a href="https://karlgroves.com/why-is-iaap-giving-special-treatment-to-overlays/">Karl Groves</a> point needs to be shouted from the rooftops.</li>
<li><a href="https://adrianroselli.com/2024/01/under-supported-and-underpaid.html">Adrian Roselli</a> briefly mentions why the IAAP certs are tainted in his eyes</li>
<li><a href="https://www.linkedin.com/posts/erineperkins_im-letting-my-cpacc-certification-lapse-activity-7345826465994752001-Rplp/">Erin E. Perkins</a> made a post about CAECs on LinkedIn, with some official responses in the replies. (Thanks Rian for bringing this to my attention!)</li>
</ul></section>]]></content>
        <published>2025-07-07T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[You're not a front-end developer until you've...]]></title>
        <id>https://www.nicchan.me/blog/youre-not-a-front-end-developer-until-youve/</id>
        <link href="https://www.nicchan.me/blog/youre-not-a-front-end-developer-until-youve/"/>
        <updated>2025-06-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Published at least one shitpost!]]></summary>
        <content type="html"><![CDATA[<p>Ive just passed the 10 year mark of my career in web development! A lot of things have happened in that time, so I thought I would celebrate this milestone with this list, which is mostly comprised of little anecdotes/observations that I put on various social media platforms over the years. I’ve checked off most of these myself, but some of them are referencing general trends I noticed other devs do. Please don’t take this list too seriously, I’m not trying to revoke anyone’s web developer creds, this is all tongue-in-cheek!</p>
<div class="rss-only"><p><p>Updated on June 11 to add an RSS version of the list just in case your feed
strips out web components.</p></p><p><p>RSS feed readers may wish to click through to the source for maximum
enjoyment. The website version of this post has a little score tracker that
will display a message based on your score, but hey, I’m not your boss!</p></p><ul><li><p>Spent three hours trying to figure out why your site had a horizontal
scrollbar.</p></li><li><p>Been asked to double the size of the logo (I get double points because I
was also asked to make it neon green).</p></li><li>Been asked to move something two pixels to the right.</li><li>Had an API for a Meta product just break on you, suddenly.</li><li>Broke the website in production.</li><li><p>Coded a circular bug, where a feature would break in either Firefox,
Chrome or Safari. Choose wisely.</p></li><li><p>Tried to explain a coding problem to someone else and solved it
mid-sentence.</p></li><li><p>Conducted a search for an issue and the search results contained your own
blog post.</p></li><li><p>Swore it couldn’t possibly be caching, and then it turned out to be
caching (submitted by <a href="https://joshcollinsworth.com/">Josh Collinsworth</a>)</p></li><li><p>Refreshed prod while working on local and wondered why your changes
weren’t showing (submitted by <a href="https://joshcollinsworth.com/">Josh
Collinsworth</a>)</p></li><li><p>Cried over DNS settings. Or at least threw a very large internal temper
tantrum.</p></li><li>Had really strong opinions about either dark/light mode.</li><li>Bought a domain for a side project.</li><li><p>Let the same domain lapse one year later after you did nothing with it.</p></li><li><p>Gotten 90% of the way through the side project only to abandon it now that
you’ve finished the fun part.</p></li><li><p>Built a project that is so niche that it is basically only relevant to
you.</p></li><li><p>Built a <a href="https://blakewatson.com/journal/the-joys-of-home-cooked-apps/">home-cooked
app</a> for
your family because you didn’t want to pay for the alternative. It ain’t
pretty but it works!</p></li><li><p>Came up with a startup idea, bonus points for each vowel omitted from the
startup name.</p></li><li>Felt the eternal call to redesign your website.</li><li>Written something in a new framework just to try it out.</li><li><p>Re-written that same thing in a different framework next year because you
didn’t like the first framework.</p></li><li><p>Used at least one web technology that no one uses any more. (Meteor.js
anyone?)</p></li><li>Listen to a web development podcast for fun.</li><li><p>Posted something on social media that attracted at least ten reply guys</p></li><li><p>Tried to convince stakeholders to adopt progressive enhancement because
you were too lazy to build a thing</p></li><li><p>Filed a bug at a browser vendor in hopes they would fix the bug for you
before the site launched</p></li><li><p>Argued with a designer to change the design so the source order would be
the same on desktop and mobile</p></li><li><p>Looked like a wizard in front of your non-tech friends by deleting a
paywall</p></li><li><p>Hacked some broken client-side validation so you could submit a form</p></li><li>Fixed a website’s broken styles with your own stylesheet</li><li>Evangelized the joys of RSS to a non-tech friend</li><li><p>Fantasized about quitting your job and moving somewhere to enjoy the quiet
life, possibly taking up carpentry or farming</p></li></ul></div>
 
<p>Thanks to the Shoptalk d-d-d-discord for giving this a trial run. I do have some availability in my schedule so <a href="https://www.nicchan.me/contact/">reach out</a> if you’re interested in anything I have to offer.</p>]]></content>
        <published>2025-06-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[We launched our first Shopify theme]]></title>
        <id>https://www.nicchan.me/blog/we-launched-our-first-shopify-theme/</id>
        <link href="https://www.nicchan.me/blog/we-launched-our-first-shopify-theme/"/>
        <updated>2025-06-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[This theme is the reason why I've disappeared from the internet for the past half a year.]]></summary>
        <content type="html"><![CDATA[<figure><img alt="Five different looks for the same Shopify theme for different industries. From left to right, a bright pastel beauty store, a black and white minimalist fashion store, an elegant purple and cream jewellry store, an earthy and organic ceramics store, and a bright primary color, brutalist-inspired furniture store." loading="lazy" decoding="async" sizes="(min-width: 1200px) 1200px, 100vw" style="object-fit:cover;max-width:1200px;max-height:630px;aspect-ratio:1.9047619047619047;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_336,c_lfill,f_auto/v1747196547/og-image-banner 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_394,c_lfill,f_auto/v1747196547/og-image-banner 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_435,c_lfill,f_auto/v1747196547/og-image-banner 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_504,c_lfill,f_auto/v1747196547/og-image-banner 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_567,c_lfill,f_auto/v1747196547/og-image-banner 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1200,h_630,c_lfill,f_auto/v1747196547/og-image-banner 1200w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_672,c_lfill,f_auto/v1747196547/og-image-banner 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_876,c_lfill,f_auto/v1747196547/og-image-banner 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1008,c_lfill,f_auto/v1747196547/og-image-banner 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1075,c_lfill,f_auto/v1747196547/og-image-banner 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2400,h_1260,c_lfill,f_auto/v1747196547/og-image-banner 2400w" src="https://res.cloudinary.com/nicchan/image/upload/w_1200,h_630,c_lfill,f_auto/v1747196547/og-image-banner"><figcaption><p>The Primavera theme has five different preset looks that you can customize
as much or as little as you need. All these looks can be configured just by
tweaking settings, without touching a single line of code.</p></figcaption></figure>
<p>A few weeks ago, we quietly launched Primavera, our first Shopify theme! The idea of building a Shopify theme had been floating around my head for a long time now, but the timing never seemed right. It wasn’t until I re-connected with my former colleague <a href="https://www.marianneciara.com/">Marianne</a> that I felt that this idea really had potential.</p>
<p>Marianne is not only a talented developer, she also has other skills that make her an incredible co-founder. While I am much more happy tinkering in code all day, Marianne enjoys marketing, has nearly a decade of experience in providing customer support at luxury brands, has great design chops and also speaks French and Spanish, two key languages that are part of the theme store filters. Talk about luck! Together, we co-founded <a href="https://ultramarinastudios.com/">Ultramarina</a>, and this theme is hopefully just the first of many ventures together.</p>
<img alt="The Ultramarina site, a simple minimalist website with Ultramarine blue accents with some marketing copy and a video of the theme on an iPad." loading="lazy" decoding="async" sizes="(min-width: 1366px) 1366px, 100vw" style="object-fit:cover;max-width:1366px;max-height:1351px;aspect-ratio:1.0111028867505552;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_633,c_lfill,f_auto/v1747194106/ultramarina 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_742,c_lfill,f_auto/v1747194106/ultramarina 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_819,c_lfill,f_auto/v1747194106/ultramarina 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_949,c_lfill,f_auto/v1747194106/ultramarina 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1068,c_lfill,f_auto/v1747194106/ultramarina 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1266,c_lfill,f_auto/v1747194106/ultramarina 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1366,h_1351,c_lfill,f_auto/v1747194106/ultramarina 1366w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1650,c_lfill,f_auto/v1747194106/ultramarina 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1899,c_lfill,f_auto/v1747194106/ultramarina 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_2026,c_lfill,f_auto/v1747194106/ultramarina 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_2532,c_lfill,f_auto/v1747194106/ultramarina 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_2732,h_2702,c_lfill,f_auto/v1747194106/ultramarina 2732w" src="https://res.cloudinary.com/nicchan/image/upload/w_1366,h_1351,c_lfill,f_auto/v1747194106/ultramarina">
<p>Though our journey is just beginning, I thought it would be interesting to share
some thoughts and reflections about the process of getting a theme to launch. I’ve
benefited a great deal from many other folks sharing their experiences openly and
candidly, and I’d like to pay it forward to the community. The following post is
either going to read as a technical post-mortem of sorts (is it a post-mortem if
it’s still alive?), a summary of my developer philosophy, or a loose sales pitch
depending on your tolerance, so don’t say that you haven’t been warned! ;)</p>
<section><h2 id="goals">Goals</h2><p>Given Marianne’s previous background, it seemed only logical for our theme to target the high-end market. There aren’t many all-women teams in the Shopify theme space, and we felt that we could bring not only our professional experience, but our personal experience as the target audience for the kinds of brands we’d be hoping to reach.</p><figure><img alt="The Distinction preset. A luxury fashion store featuring a split hero banner section with a shopabble product and a product collection below. The prices are very, very expensive." loading="lazy" decoding="async" sizes="(min-width: 2000px) 2000px, 100vw" style="object-fit:cover;max-width:2000px;max-height:2496px;aspect-ratio:0.8012820512820513;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_799,c_lfill,f_auto/v1747194361/distinction-desktop 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_936,c_lfill,f_auto/v1747194361/distinction-desktop 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_1033,c_lfill,f_auto/v1747194361/distinction-desktop 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_1198,c_lfill,f_auto/v1747194361/distinction-desktop 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1348,c_lfill,f_auto/v1747194361/distinction-desktop 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1597,c_lfill,f_auto/v1747194361/distinction-desktop 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_2082,c_lfill,f_auto/v1747194361/distinction-desktop 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_2396,c_lfill,f_auto/v1747194361/distinction-desktop 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2000,h_2496,c_lfill,f_auto/v1747194361/distinction-desktop 2000w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_2556,c_lfill,f_auto/v1747194361/distinction-desktop 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_3195,c_lfill,f_auto/v1747194361/distinction-desktop 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_3994,c_lfill,f_auto/v1747194361/distinction-desktop 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_4792,c_lfill,f_auto/v1747194361/distinction-desktop 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4000,h_4992,c_lfill,f_auto/v1747194361/distinction-desktop 4000w" src="https://res.cloudinary.com/nicchan/image/upload/w_2000,h_2496,c_lfill,f_auto/v1747194361/distinction-desktop"><figcaption><p>Okay, maybe the target price point is a little out of reach personally, but
I’ve always loved fashion!</p></figcaption></figure><p>While we definitely wanted to make our theme easy to use for the average merchant, we also had a secondary core audience in mind, agencies and other developers. Over the years, we’ve spent a lot of time customizing premium themes for various merchants, and we have a strong idea of what makes a good foundation for that kind of work. Essentially, we wanted to build the theme for the past versions of ourselves.</p><p>As we are both perfectionists, our core philosophy was “we will spend the time obsessing over the details so other people don’t have to.” One example of this is is the infinite loading option on the collection pages. Although we definitely have mixed feelings about infinite loading, we know that regardless of how we felt, people would want this feature anyway (more on this later), and if it was going to be done, it had to be done well. We looked really carefully at dozens of different implementations and all their pitfalls, and our solution manages to:</p><ol>
<li>Preserve back button state</li>
<li>Utilize real <code>&lt;a&gt;</code> tags that are swapped with the more semantic <code>&lt;button&gt;</code> element to provide search engines with paginated URLs</li>
<li>Supports progressive enhancement in case JavaScript fails to load</li>
<li>Communicate state and manage focus for non-mouse users</li>
</ol><p>We took this level of care with every single part of the theme. We even had a huge multi-hour fight about what to do when someone typed a decimal place in a price input from any given locale, that’s how deep we examined every component. Building a solid foundation was extremely important to us throughout the process of building the theme, as we have a lot planned for the future. From day one, we brought advanced features such as right-to-left support and cart upsells, but there is still plenty more features that we want to add.</p><p>In contrast to the standard SaaS product and similar themes, we feel that the end product of our theme isn’t just the end-result that the user experiences, it’s also the code that underpins it. Developers and agencies should be able to extend the theme easily, they should be able to configure a few design tokens or change a few settings to get all of our powerful functionality with a unique look and feel.</p><p>Yes, it should be easy to get up-and-running, but it should also be easy to take that design and run with it. I’ve been in that place where I have to just change one font size throughout the store, and the lack of a design system or component based architecture means that what should be a 5 minute task becomes a 3 hour task. Not fun, and certainly not something we wanted to deliver to our fellow developers.</p></section>
<section><h2 id="tech">Tech</h2><p>Because we want our codebase to be something that people can easily extend, we had to think really carefully about the tech stack and dependency tree. We settled on an architecture where we concatenated our core CSS and JavaScript into one big file, but we left it un-minified and used no other tooling. For the perf geeks out there, don’t worry! Shopify minifies the code before it serves it, but the code that developers see and access in the editor is unminified.</p><p>That meant that all the CSS is just vanilla CSS with no frameworks, and all the JavaScript bits are Web Components/HTML Custom Elements. Before this project, I had dabbled in Web Components for one-offs, but never for an entire project of this scale. To steal the lexicon of a finance bro for a second, I’m now incredibly bullish on Web Components. Their extensibility is unmatched, if someone wants to take most of your behavior and add their own behavior onto it, they can simply extend the Custom Element and override or enhance the necessary methods.</p><p>Web Components don’t ever need to be upgraded, and they’re a part of browsers so they have to support them until the end of time. Because the Shopify theme store involves a one-time payment for lifetime support (much to the chagrin of theme developers), anything to minimize our support burden would be important.</p><p>In terms of third party dependencies, we only have <strong>two</strong> JavaScript libraries in the theme. The first is What Input, which tracks the current input method. We use this to iron out inconsistencies with how browsers handle <code>:focus-visible</code>. One major thing that came up is that Safari showed <code>:focus-visible</code> upon programmatically moving focus. Because so much of the code relies on the Shopify Section Rendering API (which is basically Shopify’s take on HTMX, it helps you swap out bits of HTML as interactions happen), we have to move focus to the newly rendered HTML quite a bit, and it was often confusing for a focus ring to suddenly appear after a fetch request that is supposed to be hidden to them. However, we’ve still kept the ability to disable it easily if you want to use the native <code>:focus-visible</code> detection.</p><p>The second third-party dependency we use is the instant.page library (which can be disabled, of course). This helps to speed up requests by prefetching the next page when it detects user intent to navigate. In the future, I hope the speculation rules API will replace the need to include this at all, and we can get rid of it entirely.</p><p>To dive into Shopify specifics for a bit, I’m really proud to say Primavera is one of the first themes to utilize the new Theme Blocks architecture. The previous architecture before this is generally called Online Store 2.0, and it comprised of sections (think, a hero section, a list of products, etc.), and a single layer of blocks, which could be more granular components, like a button, but also larger ones like a slideshow slide.</p><p>This single layer limitation was extremely limiting. Take our slideshow example, having the Slideshow be the section and a Slide be the block meant that there wasn’t flexibility for determining what was in the slide without a bunch of duplication. This meant that there were a ton of arbitrary limitations on design, like a slideshow slide would pretty much consist of a subheading, a heading, text and a button, with no possibility to change the order.</p><img alt="The Shopify customizer UI, featuring a series of nested blocks to build up a heading, some text and some disclosure elements below." loading="lazy" decoding="async" sizes="(min-width: 2751px) 2751px, 100vw" style="object-fit:cover;max-width:2751px;max-height:1453px;aspect-ratio:1.893324156916724;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_338,c_lfill,f_auto/v1747201320/blocks-everywhere 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_396,c_lfill,f_auto/v1747201320/blocks-everywhere 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_437,c_lfill,f_auto/v1747201320/blocks-everywhere 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_507,c_lfill,f_auto/v1747201320/blocks-everywhere 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_570,c_lfill,f_auto/v1747201320/blocks-everywhere 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_676,c_lfill,f_auto/v1747201320/blocks-everywhere 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_881,c_lfill,f_auto/v1747201320/blocks-everywhere 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1014,c_lfill,f_auto/v1747201320/blocks-everywhere 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1082,c_lfill,f_auto/v1747201320/blocks-everywhere 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1352,c_lfill,f_auto/v1747201320/blocks-everywhere 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_2751,h_1453,c_lfill,f_auto/v1747201320/blocks-everywhere 2751w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1690,c_lfill,f_auto/v1747201320/blocks-everywhere 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2028,c_lfill,f_auto/v1747201320/blocks-everywhere 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_2366,c_lfill,f_auto/v1747201320/blocks-everywhere 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_5120,h_2704,c_lfill,f_auto/v1747201320/blocks-everywhere 5120w,
https://res.cloudinary.com/nicchan/image/upload/w_5502,h_2906,c_lfill,f_auto/v1747201320/blocks-everywhere 5502w" src="https://res.cloudinary.com/nicchan/image/upload/w_2751,h_1453,c_lfill,f_auto/v1747201320/blocks-everywhere"><p>With theme blocks, you can nest blocks up to 8 levels deep, which means that you could compose blocks out of other blocks. This unlocks tremendous possibilities for design, and it means a better surface for other developers to extend the theme. If you wanted to add a block unique to your store, you could simply add it as a block, and then you could use it in any section or block that you wanted, whether it be a hero section, a card, in tabs, an expandable element, etc.</p><p>There are still a few kinks to work out with Theme Blocks architecture. We’ve had issues with third-party apps not supporting it yet, there’s limitations on variable scoping, and there’s no way to set a limit for how many times a theme block appears. However, I truly do believe that Theme Blocks are the way forward for the Shopify ecosystem, and I’m excited to be at the forefront of those using it. (Edit: We’ve just published a deep dive into our use of <a href="https://ultramarinastudios.com/blog/why-primavera-went-all-in-on-shopify-theme-blocks/">theme blocks</a> that might interest you if you like this post!)</p><figure><img alt="Lighthouse test scores, 100s across the board in performance, accessibility, best practices, and SEO." loading="lazy" decoding="async" sizes="(min-width: 1803px) 1803px, 100vw" style="object-fit:cover;max-width:1803px;max-height:428px;aspect-ratio:4.212616822429907;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_152,c_lfill,f_auto/v1747203464/lighthouse 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_178,c_lfill,f_auto/v1747203464/lighthouse 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_197,c_lfill,f_auto/v1747203464/lighthouse 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_228,c_lfill,f_auto/v1747203464/lighthouse 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_256,c_lfill,f_auto/v1747203464/lighthouse 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_304,c_lfill,f_auto/v1747203464/lighthouse 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_396,c_lfill,f_auto/v1747203464/lighthouse 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1803,h_428,c_lfill,f_auto/v1747203464/lighthouse 1803w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_456,c_lfill,f_auto/v1747203464/lighthouse 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_486,c_lfill,f_auto/v1747203464/lighthouse 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_608,c_lfill,f_auto/v1747203464/lighthouse 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_760,c_lfill,f_auto/v1747203464/lighthouse 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3606,h_856,c_lfill,f_auto/v1747203464/lighthouse 3606w" src="https://res.cloudinary.com/nicchan/image/upload/w_1803,h_428,c_lfill,f_auto/v1747203464/lighthouse"><figcaption><p>If the wind blows exactly right and you spin around in a circle three times,
we can get perfect Lighthouse scores.</p></figcaption></figure><p>The end result of all these technical decisions is hopefully, a codebase that is very much human-readable, but easy to extend. Those who’ve worked with Shopify before know how elusive that perfect 100 lighthouse score is, and we’ve managed to build something that can achieve it (with careful and well-considered use of media, of course!) We really hope to provide a good foundation that will scale as merchants do.</p></section>
<section><h2 id="accessibility">Accessibility</h2><p>Readers of the Blog will probably know that I will not shut up about the accessibility of Shopify themes. <a href="https://cerovac.com/a11y/2025/04/webaim-million-results-show-that-shopping-sites-have-worst-accessibility/">E-commerce as a whole is an incredibly inaccessible part of the internet</a>, and I felt that my unique background as an accessibility consultant and a developer could bring about greater impact if I tried to ‘shift left’ in the process.</p><figure><img alt="Two panel kombucha girl meme. On the left is a blonde woman making a digusted face, the caption says 'Remediate a single inaccessible store. On the right is the same blonde woman looking curious and intrigued, the caption says 'Make many store accessible by building an accessible theme.'" loading="lazy" decoding="async" sizes="(min-width: 540px) 540px, 100vw" style="object-fit:cover;max-width:540px;max-height:462px;aspect-ratio:1.1688311688311688;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_540,h_462,c_lfill,f_auto/v1747194309/meme2 540w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_548,c_lfill,f_auto/v1747194309/meme2 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_642,c_lfill,f_auto/v1747194309/meme2 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_708,c_lfill,f_auto/v1747194309/meme2 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_821,c_lfill,f_auto/v1747194309/meme2 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_924,c_lfill,f_auto/v1747194309/meme2 1080w" src="https://res.cloudinary.com/nicchan/image/upload/w_540,h_462,c_lfill,f_auto/v1747194309/meme2"><figcaption>Not gonna lie, this was basically my thought process.</figcaption></figure><p>As a result, it was always a huge priority to make things accessible, and building the theme has been a delicate balance of trying to manage merchant expectations with creating an accessible user experience.</p><p>For example, if it were completely up to me, I would personally not include a marquee text region in my storefront. I know the problems they pose to users, and I find them quite distracting as I read. However, our thinking was, if merchants were going to use marquee text, we had a duty to build the absolute best marquee we possibly could. This approach was later validated shortly after launch. We had intentionally chose to not allow a marquee in a particular section, but we saw the merchant had customized the code to add the marquee where they wanted it using using the deprecated marquee tag. Needless to say, this wasn’t a very accessible implementation.</p><p>Our marquee component has a pause button, respects the user preference for reduced motion, only enables focus for the first unique instance of a link, and only announces the first instance of the text. While there are certainly things that I think can be improved, a huge amount of effort was put in to make sure that disabled users would not be blocked by it. Marquees are not great for accessibility, but one that has considered accessibility is certainly better than one without.</p><p>The way themes are sold poses an interesting difference to my regular accessibility consulting work. In consulting, you are in direct dialogue with the merchant, you can talk to them about why a feature they want is inaccessible, and sometimes you can even convince them to adopt an alternative approach. With themes however, merchants just browse from a giant list. They will often expect a certain feature or design, without knowing anything at all about the impact on accessibility, and if the theme doesn’t have that feature, they will just move onto the next option.</p><img alt="Theme options for Primavera, featuring the ability to choose between floating labels or labels always on top of form fields, as well as underline text fields vs. text fields that have solid borders all around." loading="lazy" decoding="async" sizes="(min-width: 3061px) 3061px, 100vw" style="object-fit:cover;max-width:3061px;max-height:1432px;aspect-ratio:2.1375698324022347;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_299,c_lfill,f_auto/v1747204869/settings 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_351,c_lfill,f_auto/v1747204869/settings 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_387,c_lfill,f_auto/v1747204869/settings 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_449,c_lfill,f_auto/v1747204869/settings 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_505,c_lfill,f_auto/v1747204869/settings 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_599,c_lfill,f_auto/v1747204869/settings 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_780,c_lfill,f_auto/v1747204869/settings 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_898,c_lfill,f_auto/v1747204869/settings 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_958,c_lfill,f_auto/v1747204869/settings 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1198,c_lfill,f_auto/v1747204869/settings 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3061,h_1432,c_lfill,f_auto/v1747204869/settings 3061w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1497,c_lfill,f_auto/v1747204869/settings 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_1796,c_lfill,f_auto/v1747204869/settings 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4480,h_2096,c_lfill,f_auto/v1747204869/settings 4480w,
https://res.cloudinary.com/nicchan/image/upload/w_5120,h_2395,c_lfill,f_auto/v1747204869/settings 5120w,
https://res.cloudinary.com/nicchan/image/upload/w_6016,h_2814,c_lfill,f_auto/v1747204869/settings 6016w,
https://res.cloudinary.com/nicchan/image/upload/w_6122,h_2864,c_lfill,f_auto/v1747204869/settings 6122w" src="https://res.cloudinary.com/nicchan/image/upload/w_3061,h_1432,c_lfill,f_auto/v1747204869/settings"><p>Knowing this, one other technique we’ve used is an ‘escape hatch’ approach. We have features in the theme that technically comply with WCAG but do not represent best practices in accessibility, and we’ve tried to ensure there’s always a more accessible backup alternative for people to choose. We have my <a href="https://heydonworks.com/article/listen-to-me-not-google/">arch nemesis, text inputs indicated only by a single line</a> in the theme, but we also have the option to switch to regular box inputs just by toggling a setting. We hope this will make it easier for merchants and the teams working with them to enable better UX choices without requiring a major rewrite.</p><p>In terms of future goals, while I’ve tested as much as I could on my own with the assistive technologies I have available to me, I really want to engage more disabled users for testing. We also want to get a third-party VPAT in order to better document where we are at and where we can improve.</p><p>A good accessibility consultant knows that perfect WCAG compliance is an incredibly lofty, even unrealistic goal, and that perfect, bug-free software will never exist. The best we can do is to promise to treat accessibility issues as seriously as we treat any other issue. I’ve been the person e-mailing software vendors them asking them to fix their software, only to be de-prioritized and ignored, and I do not want to be that person for someone else.</p></section>
<section><h2 id="timing">Timing</h2><p>I’m generally pretty decent at estimating (10 years of timesheets will do that to you), but I did drastically underestimate this! Marianne and I had regularly turned out full theme builds from scratch in 3 weeks at the agency we worked at. However, I had failed to realize that building one single design is a much, much smaller task than building a system that can encompass many designs.</p><figure><img alt="Six slightly different looks for the product page, two variations on a thumbnail layout, 3 on a slideshow layout, and one desktop scroll layout where images are stacked vertically." loading="lazy" decoding="async" sizes="(min-width: 1732px) 1732px, 100vw" style="object-fit:cover;max-width:1732px;max-height:1483px;aspect-ratio:1.1679028995279839;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_548,c_lfill,f_auto/v1747208543/pdp-looks 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_642,c_lfill,f_auto/v1747208543/pdp-looks 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_709,c_lfill,f_auto/v1747208543/pdp-looks 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_822,c_lfill,f_auto/v1747208543/pdp-looks 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_925,c_lfill,f_auto/v1747208543/pdp-looks 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1096,c_lfill,f_auto/v1747208543/pdp-looks 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1428,c_lfill,f_auto/v1747208543/pdp-looks 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1732,h_1483,c_lfill,f_auto/v1747208543/pdp-looks 1732w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1644,c_lfill,f_auto/v1747208543/pdp-looks 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1754,c_lfill,f_auto/v1747208543/pdp-looks 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_2192,c_lfill,f_auto/v1747208543/pdp-looks 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2740,c_lfill,f_auto/v1747208543/pdp-looks 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3464,h_2966,c_lfill,f_auto/v1747208543/pdp-looks 3464w" src="https://res.cloudinary.com/nicchan/image/upload/w_1732,h_1483,c_lfill,f_auto/v1747208543/pdp-looks"><figcaption><p>We have six different looks available for the product images. Yes, I hate my
past self, why do you ask?</p></figcaption></figure><p>A lot of work went into building out many different configurations. In a single site design, you might be like, ‘ok, this product page will have 3 product images, all the same aspect ratio, how can we best present this’? But if you don’t know what your merchants are with, they could upload anywhere between one and a hundred images, some video, of mixed aspect ratio, etc. Thinking through these different variations took up a lot more time than anticipated.</p><p>We had hoped to submit our theme to the store in 2.5 months, but it ended up taking about 4 months to get the code done. The path was very non-linear, Theme Blocks architecture was in beta when we started, and it launched after we were about 95% done, at the end of January. We ended up rewriting our entire codebase to use Theme Blocks architecture after the first submission, a process that shockingly only took <strong>6 days</strong> thanks to the modularity of our code.</p><img alt="Desktop and mobile screenshots of the product page on the Gloss demo store, featuring a lipstick and bright pastel colors." loading="lazy" decoding="async" sizes="(min-width: 2000px) 2000px, 100vw" style="object-fit:cover;max-width:2000px;max-height:1400px;aspect-ratio:1.4285714285714286;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_448,c_lfill,f_auto/v1747207438/primavera-theme-3_1 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_525,c_lfill,f_auto/v1747207438/primavera-theme-3_1 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_580,c_lfill,f_auto/v1747207438/primavera-theme-3_1 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_672,c_lfill,f_auto/v1747207438/primavera-theme-3_1 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_756,c_lfill,f_auto/v1747207438/primavera-theme-3_1 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_896,c_lfill,f_auto/v1747207438/primavera-theme-3_1 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1168,c_lfill,f_auto/v1747207438/primavera-theme-3_1 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1344,c_lfill,f_auto/v1747207438/primavera-theme-3_1 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2000,h_1400,c_lfill,f_auto/v1747207438/primavera-theme-3_1 2000w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1434,c_lfill,f_auto/v1747207438/primavera-theme-3_1 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1792,c_lfill,f_auto/v1747207438/primavera-theme-3_1 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2240,c_lfill,f_auto/v1747207438/primavera-theme-3_1 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2688,c_lfill,f_auto/v1747207438/primavera-theme-3_1 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4000,h_2800,c_lfill,f_auto/v1747207438/primavera-theme-3_1 4000w" src="https://res.cloudinary.com/nicchan/image/upload/w_2000,h_1400,c_lfill,f_auto/v1747207438/primavera-theme-3_1"><p>I had also underestimated how much time it would take to get the non-code tasks done too. Styling and getting images for the demonstration stores took about a month and a half, and the fact we were trying to get our business up and running during the key dates of Christmas Holidays and Lunar New Year meant that the paperwork took longer than expected as well. We launched with a complete set of documentation for every one of our 30+ sections, and writing all of it up took up the better part of a month.</p><p>The submission process was totally unknown to us. All themes must be reviewed by Shopify, and the checklist of requirements is over 10,000 words long. We had heard it could take anywhere from weeks to months to hear back from the Shopify review team, and we also knew that it was possible that you could be sent back to the drawing board if your theme wasn’t uniquely different from others in the store. We were very grateful to get through the process in just under a month with only minimal back and forth, we knew the process was not smooth for most.</p><p>The overall process from start to finish took around 6 months on my end. Marianne had worked on some of the design and UX in her downtime from client work for over a year prior. During the six months of me being full-time on this project, there were many weeks where we worked 7 days a week, I took a total of two days off for the winter holidays. It was a completely unsustainable pace, and even a month after launch we’re still trying to recover from the experience. I took on a few small client projects during this time, but the lack of usual income meant that this was a significant investment of time and money.</p><p>I’d be remiss if I didn’t mention something about the current world situation. I am fully aware that the conditions for collaboration would have likely not happened in better economic times as Marianne and I would have both been busy with client work. However, the state of the tech industry is pretty dire, and the world economy seems to be on the brink of recession, with uncertainties about global trade looming over many businesses. You’d be hard-pressed to find a worse time to start something dependent on the success of mostly English-speaking merchants. Still, we have yet to come across a mechanism for time travel, so hunkering down and trying our best is the only thing we’ve got right now.</p></section>
<section><h2 id="advice">Advice</h2><p>When I told people I was working on a Shopify theme, after clarifying that it was indeed not a Spotify theme, I would often be met with responses like “It’s such a good idea to work on a passive income stream!” As much as I’d love to lie on a beach somewhere doing nothing but check my bank account, I have to emphasize that the ideal end-state for us is to reach a place where we can work on this full-time. Because of the requirements of the marketplace, there’s not really a way to make this a form of passive income.</p><p>Shopify themes are not like selling other templates, for two main reasons: 1. the ecosystem is always changing. Shopify requires the theme be updated to support new features, and if you don’t meet the deadline, your theme will be delisted. You definitely cannot hit publish and then forget about it. 2. The price of a theme includes lifetime support. For several hundred US dollars, you need to offer lifetime support for your theme as long as it remains published. I’ve heard of theme partners receiving hundreds of support queries from the same person, for less than the price of a single month of an enterprise license. Potential theme vendors should keep both these things in mind before committing to the journey.</p></section>
<section><h2 id="hows-it-going">How’s it going?</h2><p>We’ve been published in the theme store for about a month now. Sales were strong at first as we were featured in the much coveted homepage spot for new themes. As anticipated, once we lost that privileged position, we now need to split our focus between marketing and developing the product simultaneously. We’re working on building an audience and reputation that can sustain our business for the long term, regardless of whether or not we’re on the first page of the theme store. We know it’s definitely an uphill battle ahead of us, but after so many years of agency work, I’m excited to try living in product-land for a little while.</p></section>
<section><h2 id="shameless-plug">Shameless plug</h2><img alt="The Artistry preset, an earthy, muted preset featuring handmade ceramics." loading="lazy" decoding="async" sizes="(min-width: 2000px) 2000px, 100vw" style="object-fit:cover;max-width:2000px;max-height:2496px;aspect-ratio:0.8012820512820513;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_799,c_lfill,f_auto/v1747209109/artistry-desktop 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_936,c_lfill,f_auto/v1747209109/artistry-desktop 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_1033,c_lfill,f_auto/v1747209109/artistry-desktop 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_1198,c_lfill,f_auto/v1747209109/artistry-desktop 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1348,c_lfill,f_auto/v1747209109/artistry-desktop 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1597,c_lfill,f_auto/v1747209109/artistry-desktop 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_2082,c_lfill,f_auto/v1747209109/artistry-desktop 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_2396,c_lfill,f_auto/v1747209109/artistry-desktop 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2000,h_2496,c_lfill,f_auto/v1747209109/artistry-desktop 2000w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_2556,c_lfill,f_auto/v1747209109/artistry-desktop 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_3195,c_lfill,f_auto/v1747209109/artistry-desktop 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_3994,c_lfill,f_auto/v1747209109/artistry-desktop 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_4792,c_lfill,f_auto/v1747209109/artistry-desktop 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4000,h_4992,c_lfill,f_auto/v1747209109/artistry-desktop 4000w" src="https://res.cloudinary.com/nicchan/image/upload/w_2000,h_2496,c_lfill,f_auto/v1747209109/artistry-desktop"><p>If you’re interested in theme, you can visit <a href="https://ultramarinastudios.com/">our studio site</a> or the <a href="https://themes.shopify.com/themes/primavera/styles/artistry">Primavera theme listing</a> directly to check it out! I’ve linked to Artistry, our most accessible preset out of the box, but I think you’ll be impressed with how easy it is to get entirely different looks by just tweaking the global settings.</p><p>Feel free to reach out to me at <a href="mailto:nic@ultramarinastudios.com">nic@ultramarinastudios.com</a> to let me know what you think. I promise I have far too much anxiety to upsell you if you just want to geek out over the details! If you’re an agency or developer looking to see if Primavera is the right fit for you, drop me a message and we’d be happy to send you more information.</p></section>]]></content>
        <published>2025-05-14T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Coming out of my cage and I've been doing just fine]]></title>
        <id>https://www.nicchan.me/blog/coming-out-of-my-cage-and-ive-been-doing-just-fine/</id>
        <link href="https://www.nicchan.me/blog/coming-out-of-my-cage-and-ive-been-doing-just-fine/"/>
        <updated>2025-04-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Tag, you're it]]></summary>
        <content type="html"><![CDATA[<p>It’s 2025 and I’m titling blog posts with song lyrics as if I were thirteen again. I’m not even really a fan of The Killers but it felt like it summed up the last few months for me. I also figured, if it’s even half as good for breaking a blogging hiatus as it is for warming up the crowd at karaoke, it was a fitting title as any. Hello readers, it sure has been a while!</p>
<p>I have committed a huge marketing sin, which is releasing a well-received redesign and then dropping off the face of the earth. I completely failed to take advantage of any of the momentum the increased traffic brought. The truth is, during these past few months I have been working on getting a product out. When confronted with the reality of a complete lack of income for half a year, I can’t decide if I either have a personality flaw where it is extremely difficult for me to let go and take a break, or I am operating completely rationally under the rules of capitalism. Either way, the past half a year had many days where I was working seven days a week, and it really took its toll on me.</p>
<p>Now that the product is out the door, I can focus some much-needed attention on other things, including the blog! I might post more about the project in the future, but we shall see. I have a strong fear of sounding spammy or overly promotional, I feel I owe it to my readers.</p>
<p>Unfortunately, during the past few months, I also managed to lose all of my draft blog posts, so it’s time to start completely fresh. I’ve been tagged by my lovely mutual and wonderful designer <a href="https://janmaarten.com/">Jan Marten</a> to complete this little post template, and I figured it would be an easy re-entry into blogging. Check out his writing for thoughtful pieces on accessibility and privacy.</p>
<section><h2 id="why-did-you-start-blogging-in-the-first-place">Why did you start blogging in the first place?</h2><p>I’ve been freelancing for almost a decade now, and honestly, publishing a blog was an attempt at marketing. I don’t tend to write content that is particularly ‘clicky’, so I can’t say that I’ve done a very good job at it, but I have made some really wonderful connections through this blog, so I can’t say it’s been a total failure! I have had just one inbound lead reach out to me and mention the blog, but they’ve been a great partner over the years that shares my values,.</p></section>
<section><h2 id="what-platform-are-you-using-to-manage-your-blog-and-why-did-you-choose-it">What platform are you using to manage your blog and why did you choose it?</h2><p>I’m a huge fan of having your own personal site! My website is currently an Astro site, and every post is a .mdx file (as opposed to vanilla Markdown), just because I have an Image component. I’m still feeling like that’s a risky bet, we’ll see what happens by the time the next redesign comes around! You can read more about the decisions that went into this in <a href="https://www.nicchan.me/blog/building-my-new-website-part-1/">this series on redesigning my website</a>, which I still fully intend to finish, heh.</p></section>
<section><h2 id="have-you-blogged-on-other-platforms-before">Have you blogged on other platforms before?</h2><p>I would not blog if it were on a platform I didn’t control, it’s never really interested me. My desire to not be perceived is only occasionally overpowered by my passion for a topic. [insert two  wolves inside you meme]</p></section>
<section><h2 id="how-do-you-write-your-posts-for-example-in-a-local-editing-tool-or-in-a-paneldashboard-thats-part-of-your-blog">How do you write your posts? For example, in a local editing tool, or in a panel/dashboard that’s part of your blog?</h2><p>I really loved the tool Writerie by <a href="https://justmarkup.com/">Michael Scharnagl</a>! Now that it’s gone, I am still looking for the best place to keep all my posts. My main criteria is that it allows me to export to Markdown. I’m open to suggestions, let me know if you really love your blogging flow.</p></section>
<section><h2 id="when-do-you-feel-most-inspired-to-write">When do you feel most inspired to write?</h2><p>My interest in blogging is fairly constant, but easily affected by inertia. This is probably slightly toxic behavior, but I usually only manage to get a post out quickly when I’m feeling riled up. Otherwise, I just let it sit for a while until I can give it a re-read with a fresh perspective.</p></section>
<section><h2 id="do-you-publish-immediately-after-writing-or-do-you-let-it-simmer-a-bit-as-a-draft">Do you publish immediately after writing, or do you let it simmer a bit as a draft?</h2><p>If you haven’t noticed, I’m very much a let it cook kind of girlie! The one thing you can count on is that I will always be late to a trend, so if I blog on a topic, I have to really make sure that something is worth reading, even after a hundred other people have written about it.</p><p>People who manage to publish quality posts on a regular schedule amaze me. I think I need to learn to lower the bar a little. Bloggers I regularly enjoy often let a typo or two slip through the cracks, but it’s not something that affects my perception of them. It’s probably worth letting some imperfection come through if I can publish more regularly than twice a year.</p></section>
<section><h2 id="what-are-you-generally-interested-in-writing-about">What are you generally interested in writing about?</h2><p>Not to sound like a rebel, but the things you won’t find on corporate blogs. This isn’t to disparage those who publish posts on more popular subjects, it’s just that for me personally, I only really have the impulse to write when I feel that there is something missing from the discourse about a topic and I feel like I can bring my own unique spin to it. Since I’m pretty slow and my free time is limited, it’s unlikely that you’ll find me writing posts on the latest CSS feature, even if it is a subject that interests me greatly.</p></section>
<section><h2 id="who-are-you-writing-for">Who are you writing for?</h2><p>Shout out to my RSS fam! I’m never really sure who my audience is, I think I just try to publish every once in a while to remind people that I still exist. The typical reader is:</p><ul>
<li>Interested in front-end web development or accessibility</li>
<li>Cares about the web ecosystem</li>
<li>Not very likely to hire me, despite the original intentions of this blog, lol</li>
</ul></section>
<section><h2 id="whats-your-favorite-post-on-your-blog">What’s your favorite post on your blog?</h2><p>This one, <a href="https://www.nicchan.me/blog/what-tumblr-taught-me-about-accessibility/">What Tumblr taught me about accessibility</a> is probably my magnum opus. It feels like something that only I can write, which is the goal for this blog. It still pretty much sums up my approach to accessibility, which is more person-centered rather than compliance-centered.</p></section>
<section><h2 id="any-future-plans-for-your-blog-maybe-a-redesign-a-move-to-another-platform-or-adding-a-new-feature">Any future plans for your blog? Maybe a redesign, a move to another platform, or adding a new feature?</h2><p>I often toy with the idea of webmentions. I really like them in theory, I just feel like there’s quite a bit of a hurdle to jump through. I had them on a previous iteration of the blog, but when I changed the casing of my Twitter handle at the time, all my mentions disappeared, so I never bothered to bring it back.</p></section>
<section><h2 id="life-updates">Life updates</h2><ul>
<li>
<p>I’ve been a part of the <a href="https://www.thegreenwebfoundation.org/tools/grid-aware-websites/">Grid-aware Websites</a> (aka GAW) advisory board over the past few months. I’ve been delighted to get to work with the Green Web Foundation folks (particularly <a href="https://fershad.com/">Fershad</a>, Hannah and <a href="https://chrisadams.me.uk/">Chris</a>), and all the other members of the advisory board. When the world feels like it’s on fire, it’s encouraging to show up to a room of people who really care about making it a little better. I hope to publish something on Grid-aware websites in the coming months.</p>
</li>
<li>
<p>Switched some of my browsing over to Zen Browser! I still need to use Chrome for some tasks (I can’t seem to use Figma with my pointer device of choice, a drawing tablet), but overall I enjoy Zen, and I think it makes me a better developer to use non-Chromium browsers regularly. I did not like that I had to do some work/install extensions to get the scrollbar to show up in the UI though.</p>
</li>
<li>
<p>Managed to get all my Kindle books files before Amazon decided to be an ass and remove that feature. I’m definitely not getting another Kindle once this one dies, but I’ve had it since 2017 and it’s still somehow going strong. It feels more wasteful to throw away a perfectly functioning device, even if I want to boycott the company that makes it.</p>
</li>
<li>
<p>Been attending a lot of life drawing! I really enjoy it. I missed the entirety of the good plein-air weather in Hong Kong in my work-induced fervor, but this is something I can keep doing in the summer too.</p>
</li>
</ul></section>
<section><h2 id="tag-youre-it">Tag, you’re it</h2><p>I’m technically supposed to tag someone, but since I’m over a month late to the trend, I had difficulties finding anyone who is a regular blogger left untagged! If you’ve been in a rut and want to get back into blogging, take this as a gentle encouragement to use this post format. Let me know if it gets you back into blogging and I’ll link you here.</p></section>]]></content>
        <published>2025-04-13T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Shopify, accessibility lawsuits & 'Compliance']]></title>
        <id>https://www.nicchan.me/blog/shopify-accessibility-lawsuits-compliance/</id>
        <link href="https://www.nicchan.me/blog/shopify-accessibility-lawsuits-compliance/"/>
        <updated>2024-10-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Accessibility is your responsibility too.]]></summary>
        <content type="html"><![CDATA[<div class="post-callout"><h2 class="post-callout__title">Summary</h2><div class="post-callout__text"><ul><li>Accessibility is affected by both content and code.</li><li><p>An accessible theme won’t get you anywhere if you have inaccessible
content (or apps!)</p></li><li><p>Not ever thinking about accessibility and wanting to not ever think
about it is indeed, ableist</p></li><li>’Compliance’ is a loaded term</li><li><p>A perfect Accessibility Compliance Report (ACR) is not always perfect</p></li><li><p>Disabled people have very little means by which to ask for access other
than complaints and litigation</p></li></ul></div></div>
<p>I recently came across this <a href="https://www.reddit.com/r/shopify/comments/1femm9b/sued_for_ada_inaccessibility/">Reddit thread</a>, in which the original poster (OP) describes being sued for their Shopify storefront. The thread contains a lot of misinformation around accessibility, and I’d like to address some of the myths surrounding Shopify’s accessibility in this (very long) post.</p>
<p>I’m speaking from my position as a web developer with a focus on web accessibility. I hold a Web Accessibility Specialist certification from the International Association of Accessibility Professionals (IAAP, but don’t get me started on certifications), I’ve developed themes for Shopify stores and remediated Shopify stores on-and-off over the last 6 years. I will not comment on the legal aspects in great detail as I’m not qualified to do so. I recommend <a href="https://www.lflegal.com/">LFLegal</a> and <a href="https://convergeaccessibility.com/blog/">Converge Accessibility</a> (additional disclaimer: I’ve worked with Converge in the past) as sources to read if you are interested in the legal aspects.</p>
<div class="post-callout"><h3 class="post-callout__title">Disclaimer</h3><div class="post-callout__text"><p>I can’t overstate that I am not a lawyer! The following post is my personal
opinion. This post is for <strong>educational purposes</strong> only, and none of this
constitutes legal advice. Other accessibility experts and disabled people
may have different interpretations and experiences on the information
presented.</p></div></div>
<p>To summarize the thread briefly:</p>
<ul>
<li>The original poster is a small business who “has been sued for having a website that is inaccessible under the ADA”</li>
<li>They use a Shopify theme, <a href="https://themes.shopify.com/themes/refresh/styles/default">Refresh</a>, and other apps, all of which they claim has been marketed as accessible</li>
<li>They stated they’ve not altered any code and ran a program to ensure alt text has been added</li>
<li>The lawsuit appears to be from a firm that has sued dozens of businesses this year.</li>
</ul>
<p>I do feel for this original poster, their financial circumstance seems dire. Without looking at their site, I can’t verify any claims about accessibility, but it seems the original poster has done some legwork to ensure things are accessible, and the facts seem to suggest the lawyer/plaintiff are not acting with genuine intent. While I strongly believe that these kinds of lawsuits do more harm than good (see: <a href="https://nfb.org//images/nfb/publications/bm/bm19/bm1909/bm190902.htm">the National Federation of the Blind’s article</a> on the subject), the bulk of the responses frustrate me in that many merchants are trying to dodge responsibility completely.</p>
<section><h2 id="who-is-responsible-for-accessibility">Who is responsible for accessibility?</h2><p>The original thread, and several spinoffs, are filled with anger at Shopify for not making their storefronts accessible. Some merchants are upset at the idea that they can personally be held responsible for accessibility, while admitting that they have never thought about accessibility.</p><p>One thing I’d really like to hammer home is that <strong>accessibility is affected by both content and code.</strong> You can have the most accessible theme in the world, and create immediate access barriers with your decisions. For example, you could pick inaccessible colors for your brand, you could write marketing copy full of ‘click here’ for every link, you could fail to write (AI-assisted or not) adequate alt text for your images, the list goes on. People willingly take the responsibility of designer and copywriter in order to avoid hiring an expert, but they don’t want to take responsibility for any harm that their ignorance might cause.</p><p>On the code front, it’s not uncommon to see merchants cobble together complex functionality based on apps. I get it, most people do not have the budget to hire web developers, and it is a sad fact of the web landscape that most websites and apps are inaccessible by default, with 96% of the top million websites having automatically detectable Web Content Accessibility Guideline (WCAG) failures. But at the same time, while it is true that the web is inaccessible by default, which poses a challenge to those trying to do right, many merchants explicitly admit to never even thinking about accessibility at all.</p><p>I like to think of this example as renting a commercial space. The building may have been constructed in accordance with local fire regulations, but once you put your furniture in, and do a little DIY construction on the side, it’s entirely possible that you’ve created something that violates local fire regulations. Does that make the landlord responsible for the violations because they rented the space to you? Does ignorance of these fire regulations mean that you aren’t obliged to follow them?</p><p>Generally speaking, in the United States, the company (in this case, the merchant) is regarded as legally responsible for accessibility. You can read more about it in the Converge Accessibility blog post titled <a href="https://convergeaccessibility.com/2021/10/25/can-web-developers-be-sued-for-the-inaccessible-sites-they-create-for-clients/">“Can Web Developers be Sued for the Inaccessible Sites They Create for Clients?”</a>.</p><p>I have genuine sympathy for vendors in that it is hard to do things correctly while wading in a swamp of poorly constructed themes and apps. It can be devastating for a small business to be hit with an expensive lawsuit, especially in the case of the original poster, where it seems like they did actually consider accessibility to the extent which they were able to. I’d like to see the legal process become more remediation focused rather than settlement based.</p><p>But I do think the desire to never think about accessibility personally, to dump that responsibility onto someone else, has negative impacts on the accessibility of the internet as a whole. It is an attitude that harms disabled people on the internet every single day.</p></section>
<section><h2 id="how-can-i-figure-out-if-something-is-accessible">How can I figure out if something is accessible?</h2><p>Not enough merchants are aware of the existence of Accessibility Compliance Reports (ACR), which provide a useful tool for assessing accessibility. While most merchants never think about accessibility at all, those who do might search a marketing website for keywords such as ‘accessible’, and then take that at face value. Adrian Roselli has written extensively on why one should <a href="https://adrianroselli.com/2016/03/be-wary-of-accessibility-guarantees-from-vendors.html">be wary of superficial accessibility guarantees from vendors</a>. Oftentimes, the marketing team or sales team is simply not on the same page and may provide false claims.</p><div class="post-callout"><h3 class="post-callout__title">Sidenote</h3><div class="post-callout__text"><p>An Accessibility Compliance Report (ACR) is often called a Voluntary Product Accessibility Template (VPAT), which is technically the name for the blank template used by a completed ACR, so searching for both terms may be helpful.</p></div></div><p>A particularly egregious example of incorrect accessibility claims can be found in the original Reddit thread. The original poster says that upon reaching out to Shopify support, they were advised by the Shopify support staff to install an accessibility overlay tool. Those in the accessibility industry know that <a href="https://overlaysdontwork.com/">Accessibility Overlays Don’t Work</a>, and I’ve personally come across a case where the presence of an overlay tool blocked all screen reader users from being able to reach checkout. You know, the primary function on an e-commerce site. Yikes. I would really like to see Shopify do some kind of internal training to ensure that the support staff are all on the same page with regards to accessibility issues.</p><p>As a developer and not a lawyer, I’m not really able to look at an accessibility policy or accessibility guarantee from a vendor and draw meaningful conclusions about legal risk from it, but an ACR consists of technical information on how a product meets the WCAG success criterion. The presence of one or multiple ACRs by a vendor is generally a positive sign for accessibility. It means that an organization has at least thought enough about accessibility to invest some resources into producing the document.</p><p>There are certainly still issues with ACRs, as Brian DeConinck outlines in this post <a href="https://www.briandeconinck.com/vpats/">‘What’s wrong with VPATs?’</a>. To summarize Brian’s post, ACRs may often be seen as another arm of the marketing department, and it can be tempting to construct the ACR in a way that minimizes the number of issues found, either by reducing the areas of the site that are tested or by excluding specific assistive technology.</p><p>Truthfully, as an application grows in complexity, it is more likely to have accessibility issues. In an ACR, each WCAG criterion can have a corresponding conformance level: supports, partially supports, does not support, or not applicable. You would think that finding an ACR with every criterion marked as ‘supports’ is a good thing, but it can indicate that a vendor is more interested in having a ‘passing’ ACR rather than an accessible product. When I look at a ACR, I’m not looking for legal risk, I’m looking for the depth of information provided about the usability, and that means diving into the depths of ACR comments.</p><section><h3 id="the-dawn-theme-acr">The Dawn Theme ACR</h3><p>The Dawn theme is the default theme installed on any Shopify store. Shopify’s first-party themes share a bunch of code between them, so looking at this ACR in depth is useful for determining the overall accessibility. (Last time I checked, Shopify’s themes may even be largely identical in terms of code, and just provide different styling and content, but don’t take this as gospel, things may have changed!)</p><p>The <a href="https://www.shopify.com/hk-en/accessibility/vpat-theme-dawn">Dawn Theme ACR</a> has 13 criteria listed as ‘Partially Supports’. The rest of the criteria are marked as ‘Supports’, with no criterion marked as ‘does not support’ or ‘not applicable.’ While this may look bad to the untrained eye initially, one thing I’d like to call out is that the conformance levels in the VPAT template do not account for severity. I’d much rather see an ACR with detailed comments on all the success criteria, rather than a ACR with ‘Supports’ for every criterion without any detail at all. Additionally, there are inconsistencies in the way different assistive technology combinations handle things, even if something is perfectly coded to existing standards, and a good ACR will make note of these situations.</p><p>While looking into this specific case, I found some comments, even those from accessibility experts, stating that this ACR indicates that this Shopify theme is inaccessible and should not be used. I’d like to emphasize that WCAG compliance and accessibility are not exactly one and the same. Though the court may find otherwise, I personally disagree, this ACR is what I’d generally expect a <strong>realistic</strong> ACR for an actively developed project to look like.</p><p>It’s positive that Shopify mentions both automated and manual testing and that fixes are on the way, but my main gripe is that the ACR is from 2021 and it’s not immediately clear what the status is on some of these issues now in 2024. It is possible to follow up on the status of particular issues with some digging in the repository, as the Dawn source code is open source and the <a href="https://github.com/Shopify/dawn/issues?q=is%3Aissue%20state%3Aopen%20accessibility">Github issues</a> are public, but I recognize that there is a high level of knowledge required to be able to sort through these issues in a meaningful way.</p><p>Let’s take a deeper look at the Dawn theme’s ‘partially supports’ criterion to understand how one might interpret an ACR.</p><section><h4 id="111-non-text-content-level-a">1.1.1 Non-text Content (Level A)</h4><blockquote>
<p>Some imagery text alternatives duplicate heading text content.</p>
</blockquote><p>In my interpretation, this is not a huge deal. There was a time when labeling <a href="https://css-tricks.com/accessible-svgs/">SVGs accessibly</a> was not particularly straightforward, and sometimes the techniques used would cause the text to be <a href="https://github.com/nvaccess/nvda/issues/4782">announced twice in some (but not all) assistive technology combinations</a>.</p><p>When Shopify builds a theme, the theme has to work for the millions of different possible permutations for all the ways in which merchants may input content. I can imagine them erring on the side of duplicated text over having unlabelled imagery, and I personally would regard duplicate text as a minor issue.</p><p>Alternatively, this could be an issue with the alt text itself, which is easily addressable by content authors.</p></section><section><h4 id="131-info-and-relationships-level-a">1.3.1 Info and Relationships (Level A)</h4><blockquote>
<ul>
<li>Page landmarks may be missing.</li>
<li>Controls may be missing attributes to provide context.</li>
<li>Controls may be incorrect for current context.</li>
<li>Hidden content may provide an overly verbose experience.</li>
</ul>
</blockquote><p>This is one of the more problematic criteria. The wording here could mean anything from blocking issues that prevent a user from using the site entirely, but it is also vague enough that it could refer to ARIA attributes that are not supported consistently across all technology combos, such as the <a href="https://a11ysupport.io/tech/aria/aria-controls_attribute">aria-controls</a> attribute. Depending on the details, the severity of this criterion could range from blocking to minor, and I would want to know more details in order to make an informed assessment.</p><p>Hidden content being verbose is much less worrying to me. Verbosity is an individual user preference, and screen reader users have the option to skip past text they don’t want to listen to.</p></section><section><h4 id="211-keyboard-level-a">2.1.1 Keyboard (Level A)</h4><blockquote>
<ul>
<li>Dynamic changes in UI may not manage keyboard focus in an efficient manner.</li>
<li>Potential difficulties arise when the Challenge (reCAPTCHA) screen is displayed.</li>
</ul>
</blockquote><p>For not managing keyboard focus efficiently, I could see this as referring to something like the collections page, where depending on the number of filters present, it may create a flow where a user makes a change to a filter, and would need to tab through some extra tab stops before reaching the product list. Screen reader users have a way to navigate between landmarks, so this is less of an issue for them, but keyboard-only users have no choice but to tab between all the filters. Since this refers to efficiency, it comes across as a UI pattern some users may have frustration with rather than a blocking issue.</p><p>Spam is a notoriously difficult issue to solve, and I’m not a huge fan of <a href="https://developers.google.com/recaptcha">Google’s reCAPTCHA</a>. I’d really like to see better solutions to spam and the Captcha problem, but I understand this is not unique to Shopify and will take larger developments in the industry before this will be resolved. This issue has the potential to be blocking to some users, but a <a href="https://dl.acm.org/doi/fullHtml/10.1145/3524010.3539498">2022 study</a> found that only 2% of 479 surveyed blind users had an issue with reCAPTCHA v3, so maybe I need to reevaluate my stance here.</p><p>The Shopify documentation has indicated that they have switched over <a href="https://shopify.dev/docs/storefronts/themes/trust-security/captcha">HCaptcha</a>, a service I dislike even more, as it <a href="https://www.hcaptcha.com/accessibility">requires disabled users to identify themselves to receive an exemption cookie</a>, but at least HCaptcha can be disabled by the merchant.</p></section><section><h4 id="243-focus-order-level-a">2.4.3 Focus Order (Level A)</h4><blockquote>
<ul>
<li>Visually hidden content areas may be discoverable by screen reader virtual navigation.</li>
</ul>
</blockquote><p>Every single website that uses visually hidden <code>aria-live</code> regions may have this issue, due to a lack of a better way to communicate messages to screen reader users. This is why we need the <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Accessibility/AriaNotify/explainer.md">ARIA Notify API</a>, so that messages can be communicated to screen reader users without needing to exist in the DOM. This can be confusing, but screen reader users will likely be well accustomed to this kind of issue.</p></section><section><h4 id="244-link-purpose-in-context-level-a">2.4.4 Link Purpose (In Context) (Level A)</h4><blockquote>
<ul>
<li>Homepage sections include links with similar labels.</li>
<li>Facet filter link labels may not be clear out of context.</li>
</ul>
</blockquote><p>The first point seems like a copywriting issue to me. A lot of stores have text like ‘Shop Now’ for every button, while each ‘Shop Now’ points to a different URL. This can be somewhat confusing, but it should also be a fairly easy fix.</p><p>The second issue mentions the facet filters on the collection page. I took a peek at the live store, and my best guess is that this refers to the ‘Reset’ link under each filter. Each filter has a name, like ‘Color’, and a link to ‘Reset’ that filter is listed within the disclosure widget for that filter type. Shopify could consider adding some hidden accessible text so that the label says ‘Reset Color Filter’ instead. Screen reader users have the ability to navigate pages just by the list of links and buttons alone, so that would be a clearer label when looking at that list of links out of context. Because the link would only show up in the accessibility tree when the filter disclosure is expanded, in actual practice it’s fairly unlikely/rare that a screen reader user will be confused by this.</p></section><section><h4 id="251-pointer-gestures-level-a-21-only">2.5.1 Pointer Gestures (Level A 2.1 only)</h4><blockquote>
<ul>
<li>Some dynamic content may require gestures for content consumption (ex., 3D models.)</li>
</ul>
</blockquote><p>I would be curious to see what other dynamic content they are talking about. Shopify has a built-in 3D model viewer in their themes, and this comment indicates that it would not be accessible to users who cannot perform gestures. However, I do think the use of 3D models on a storefront is generally quite rare and I wouldn’t feel qualms about choosing this theme if I didn’t have any 3D models for my products.</p><p>The model viewer library is written by Google, and Scott Vinkle from Shopify has put in substantial work in <a href="https://github.com/google/model-viewer/issues/464">testing the library with various assistive technologies</a>, which shows the great discrepancy between different screen readers. For novel technology like this, there is often a lag between when the technology is created and when assistive technology creators figure out how to interact with it.</p></section><section><h4 id="252-pointer-cancellation-level-a">2.5.2 Pointer Cancellation (Level A)</h4><blockquote>
<p>Some custom keyboard functionality relies on the keydown event.</p>
</blockquote><p>The pointer cancellation specifically refers to mouse events, not keyboard events. I searched the repository for ‘mousedown’ and ‘touchstart’, and only found two references, one being a polyfill for focus-visible, and the other appeared to actually prevent activation on mousedown, so I would personally mark this as ‘Supports’.</p></section><section><h4 id="322-on-input-level-a">3.2.2 On Input (Level A)</h4><blockquote>
<p>Some links which open a new browser tab/window do not include a warning.</p>
</blockquote><p>This one should be fairly easy to fix, and is generally considered minor. For what it’s worth, I’ve worked with blind developers who are full-time screen reader users, and they have forgotten to implement this on occasion, so it’s definitely not blocking.</p></section><section><h4 id="331-error-identification-level-a">3.3.1 Error Identification (Level A)</h4><blockquote>
<ul>
<li>When submitting the Account address form with invalid data, no errors are present to warn the user.</li>
<li>Some forms use native validation which can be problematic for specific users.</li>
</ul>
</blockquote><p>I understand why Shopify has chosen not to provide address validations in this instance. The theme based account portals have largely been replaced with <a href="https://changelog.shopify.com/posts/now-launched-new-customer-accounts">Shopify’s new account system</a>, which launched in January 2023. With the Dawn VPAT being completed in November 2021, it is entirely possible that the plans for this new system were underway already. When I checked the new account system, I found that it does feature address checking and presents errors to the user in an accessible manner.</p><p><a href="https://adrianroselli.com/2019/02/avoid-default-field-validation.html">Native form validation (referring to the practice of just letting the browsers handle form validation) can be problematic</a> for some users. It’s also an area where accessibility shifts constantly, as each browser does their own thing. For example, I found numerous older reports saying that every browser/screen reader combo except for Android Talkback supports the ‘required’ attribute on inputs, but booting up Talkback today in 2024 found that it did work (at least with text inputs).</p><p>The most complicated accessibility issues are always those relating to a particular combination of browser and assistive technology, and the Shopify team may have concluded that the effort spent developing custom form validation would create more accessibility issues than it would solve.</p></section><section><h4 id="411-parsing-level-a">4.1.1 Parsing (Level A)</h4><p>The <a href="https://www.w3.org/WAI/WCAG22/Understanding/parsing#:~:text=This%20criterion%20has%20been%20removed,their%20handling%20of%20parsing%20errors.">Parsing</a> criterion is considered obsolete and has been removed from WCAG 2.2. This ACR is referencing WCAG 2.1, so I see why they included it, but violations here are not really considered a huge issue.</p></section><section><h4 id="412-name-role-value-level-a">4.1.2 Name, Role, Value (Level A)</h4><blockquote>
<ul>
<li>Some dynamic UI may contain incorrect stateful attributes for the current context.</li>
<li>Some dynamic UI may be missing attributes to convey the current context.</li>
</ul>
</blockquote><p>The assessment for this criterion is the same as 1.3.1 Info and Relationships. It’s not uncommon to find accessibility issues that fit multiple criteria.</p></section><section><h4 id="143-contrast-minimum-level-aa">1.4.3 Contrast (Minimum) (Level AA)</h4><blockquote>
<p>Some text components may not meet the minimum color contrast requirement.</p>
</blockquote><p>I could not find any text components that didn’t meet color contrast on the current version of the Dawn theme, but I have noticed that when themes generate other variations of a color based off a primary color, it’s easy for those variations to fail color contrast requirements. This is definitely an issue where the merchant may have some responsibility, unless the default palette provided by the theme causes these issues to start with. I’m hoping that the future <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-contrast">color-contrast</a> function in CSS will make implementing accessible color palettes a lot easier for everyone.</p></section><section><h4 id="246-headings-and-labels-level-aa">2.4.6 Headings and Labels (Level AA)</h4><blockquote>
<p>Challenge page (reCAPTCHA) missing primary h1 heading.</p>
</blockquote><p>I could not get this page to trigger personally due to hCaptcha relying on user behavior, but seeing as Shopify is now using hCaptcha instead of ReCAPTCHA, it’s likely that a disabled user will have more blocking issues to worry about than a missing heading if they manage to trigger this page.</p></section></section></section>
<section><h2 id="final-thoughts-on-shopify">Final thoughts on Shopify</h2><p>It is not my intent to defend Shopify or other large corporations, or to downplay the issues presented and the harm they may cause to users. Rather, it is to communicate the nuanced nature of accessibility. Although the guidelines lead us to think of accessibility as a binary, as ‘conforming’ or ‘non-conforming’, this couldn’t be farther from the truth. There is room for improvement in Shopify’s Dawn Theme ACR (and I’d like to see a newer one as this is almost four years out of date), but I appreciate the level of transparency in calling out potential issues, even if they are likely to affect a small subset of users, or are issues baked into the technology. They have also called out issues related to the software that their theme depends on, which is a level of responsibility that I don’t see many merchants taking.</p><p>When remediating Shopify themes, I feel confident recommending the Dawn theme to potential merchants. Compared to other themes, the issues with this theme are largely non-blocking, or can be avoided with some care. While it is always possible for any software to have regressions, the fact that there was clear effort in the past and a willingness to fix issues with the theme are positive signs in my book. Although we shouldn’t ignore the significance of non-blocking issues, an ACR like this indicates that there are a lot of things going right with accessibility. There is a level of intention here that I think matters a lot; you cannot stumble upon this level of accessibility accidentally, you need organizational backing to develop mature accessibility processes.</p><p>However, my review of Shopify’s accessibility is definitely not all sunshine and roses. I haven’t mentioned them much previously, but I’d like to call attention to the understated impact of third-party apps. Because they are so easy to install, it’s not always immediately obvious how devastating a single app can be towards a storefront’s accessibility. Any single app that touches the storefront can block the user’s ability to achieve their goals. While Shopify’s first-party themes may have done a decent job, they continue to run an app and theme stores that is largely filled with inaccessible apps, simply because it is profitable.</p><p>In a way, the Shopify app store and theme store create a parasitic ecosystem. Shopify gets to wash their hands of the responsibility of building all this complex functionality by themselves, and the third-party apps and themes earn them money without being subject to stringent guidelines and scrutiny the way first-party code is. I remember a time when there were discussions about implementing accessibility requirements, but it’s been several years and they have yet to come to fruition.</p></section>
<section><h2 id="im-a-merchant-and-i-want-to-do-better-where-should-i-start">I’m a merchant and I want to do better, where should I start?</h2><p>I don’t think there is any single thing one can do to avoid this kind of drive-by accessibility lawsuit, but the best thing you can do is to put effort into accessibility from the start. Knowing what accessibility is and trying to think about it as early as possible is always a good idea, and may potentially help you in getting frivolous lawsuits dismissed. An accessible theme is just a starting point, and in the end, it is the merchant’s responsibility to consider accessibility throughout the process.</p><p>In terms of cost, it is always cheaper to think about accessibility up front. A remediation job of an inaccessible site can take a skilled developer hundreds of hours. I know that auditing third-party vendors for accessibility is not a skill that everyone has, but short-listing a bunch of potential themes and apps and then consulting with an accessibility professional for a few hours is far cheaper than waiting till you’ve already been sued. Maybe, while you’re at it, you can even work on an accessibility statement together, so disabled people know where to turn to when they need assistance.</p><blockquote>
<p>When the only recourse available to people with disabilities is to sue, class actions get attention.</p>
<ul>
<li><a href="https://sheribyrnehaber.com/who-tests-the-ada-testers/">Sheri Byrne-Haber</a></li>
</ul>
</blockquote><p>As a final note, I can’t state enough that disabled people shouldn’t have to beg for accessibility, but the current state of accessibility legislation means that disabled people are often forced to rely on complaints and lawsuits. Asking nicely doesn’t alway work, and so, we’ve created the current adversarial state of affairs. A certain percentage of lawsuits are frivolous in that their claims are unsubstantiated, but until something changes, lawsuits are one of the few tools disabled people have in order to be treated equitably. Before complaining about frivolous lawsuits, I have to ask, have you done your part in making your site accessible?</p><section><h3 id="links--resources">Links &amp; Resources</h3><p>I’ve gathered up a few links that I hope will be useful to non-accessibility experts who are looking to make their storefronts more accessible.</p><ul>
<li><a href="https://www.shopify.com/blog/web-accessibility">Understanding Web Accessibility for Ecommerce Merchants</a>: Shopify has produced an overview of web accessibility tailored for merchants.</li>
<li><a href="https://www.wcag.com/authors/">Accessibility in Writing</a>: A look at the WCAG criteria that specifically relate to content authoring.</li>
<li><a href="https://digital.accessibility.princeton.edu/how/design">How to design for accessibility</a>: An intro to the visual design aspects of accessibility.</li>
<li><a href="https://adrianroselli.com/2024/08/things-to-do-before-asking-is-this-accessible.html">Things to Do Before Asking “Is This Accessible?”</a>: An actionable checklist of what you can do to find out if something is accessible. Some of these items are pretty technical, but there are many items that a non-expert can do.</li>
<li><a href="https://knowbility.org/blog/2020/third-party-vendor-accessibility-check/">Checking 3rd Party Vendors’ Product Accessibility</a>: A less technical approach to auditing third-parties.</li>
<li><a href="https://disabilityin.org/home-2/">Disability:INclusive Workplaces Accessible Technology Procurement Toolkit</a>: A detailed guide to procurement, especially helpful for large organizations.</li>
<li><a href="https://overlayfactsheet.com/en/">Overlay Fact Sheet</a>: Don’t even think about downloading that overlay tool, I’m still mad Shopify support suggested it.</li>
</ul><p>Thank you to EJ Mason for proofreading this piece.</p></section></section>]]></content>
        <published>2024-10-09T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building my new website: Why are pixel SVGs so difficult, table of contents & full-text RSS]]></title>
        <id>https://www.nicchan.me/blog/building-my-new-website-part-5/</id>
        <link href="https://www.nicchan.me/blog/building-my-new-website-part-5/"/>
        <updated>2024-09-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[If you wait long enough, sometimes people will implement the features you need without you having to lift a finger!]]></summary>
        <content type="html"><![CDATA[<p>This is a post in a series about building my new website, check out <a href="https://www.nicchan.me/blog/building-my-new-website-part-4/">the previous post in this series.</a></p>
<section><h2 id="full-text-rss">Full text RSS</h2><p>This was really where progress on the site began to stall. At the time I was building this out, Astro had no support for full text RSS out of the box, and there were several <a href="https://scottwillsey.com/rss-pt1/">hacks</a>, <a href="https://piccalil.li/blog/full-text-rss-is-back/">(another hack)</a> that would let you bypass this, but I really didn’t want to bother when I felt like the <a href="https://docs.astro.build/en/reference/container-reference/">Astro Container API</a> was going to happen soon. Luckily, my guess paid off, and Astro released the Container API, which worked without a hitch! Sometimes procrastination pays off!</p><p>I did hit a minor snafu in that the <a href="https://docs.astro.build/en/guides/rss/">Astro RSS Feed plugin</a> only outputs RSS format, not Atom. I wanted to use Atom to leverage the ‘Updated’ property in my posts, as I occasionally need to make corrections and revisions. Luckily, I was able to switch out the plugin for the framework agnostic <a href="https://www.npmjs.com/package/feed">feed</a> plugin, which is both more flexible and has more features.</p><p>I really dislike the trend where every new framework needs to have it’s own version of a basic functionality. While there are definitely valid reasons behind the phenonmenon, I feel like it does end up fragmenting the web ecosysytem and gives junior developers the false impression that some things are much more difficult than they actually are.</p></section>
<section><h2 id="pixels-revisited">Pixels, revisited</h2><p>While creating new pixel sprites, I noticed that the file sizes were much larger than SVG sizes I have seen in the past. Some of them were over 30KB for a single sprite! This was because my pixel art program, Aseprite, exports <a href="https://github.com/aseprite/aseprite/issues/1911">every single pixel as an individual rectangle</a>, even if the individual rectangles make up a larger blob of color.</p><p>I tried using PNGs while relying on <code>image-rendering: pixelated</code>, a technique recently outlined by <a href="https://frontendmasters.com/blog/keeping-pixely-images-pixely-and-performant/">Chris Coyier in more detail over at Frontend Masters</a>, but I noticed that at the sizes I was displaying the image, it would sometimes render line-widths inconsistently. After the time I took to hand-pixel everything, there was just no way I was gonna let this slide.</p><img alt="Pixel art of a folder with a website printed on it, but the lines are not even and vary in thickness inconsistently." loading="lazy" decoding="async" sizes="(min-width: 135px) 135px, 100vw" style="object-fit:cover;max-width:135px;max-height:121px;aspect-ratio:1.115702479338843;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_135,h_121,c_lfill,f_auto/v1725330165/personal-10 135w,
https://res.cloudinary.com/nicchan/image/upload/w_270,h_242,c_lfill,f_auto/v1725330165/personal-10 270w" src="https://res.cloudinary.com/nicchan/image/upload/w_135,h_121,c_lfill,f_auto/v1725330165/personal-10"><p>Luckily, I found this <a href="https://codepen.io/shshaw/full/XbxvNj">really cool codepen by Shaw</a> that converts a raster image into an SVG, so I ended up running everything through there instead. There’s a <a href="https://github.com/meyerweb/px2svg">code version</a> of this library that does this, but since I only have fewer than 10 individual sprites, it would have been overkill to implement.</p><p>As a side note, this serves as a gentle reminder to myself to ask for help sometimes. Upon looking at Eric Meyer’s library solution, I noticed Amelia Bellamy-Royds was listed as one of the contributors. I practically have Amelia on speed dial for all my tricky SVG questions, had I asked for help I might have saved myself many hours!</p><p>Once I had my SVGs, I cobbled together a quick Node script based on this <a href="https://hackernoon.com/how-to-create-svg-sprite-with-icons">helpful tutorial by German Makarov</a> in order to glue them into a big spritesheet that will allow me to minimize the HTTP requests for the icons. I know there are plugins like <a href="https://github.com/natemoo-re/astro-icon">astro-icon</a> that handle this in a more ‘elegant’ way, but given that there is a pretty manageable amount of icons, I opted for the path that was much less likely to involve breaking dependency upgrades. Lately, I’ve been just writing node scripts for various build things if tooling feels too much overhead. I typically only dramatically change my site every 5 years or so, so having a maintainable, low-key solution is really important to me. I’ve suffered through enough Grunt, Gulp, Snowpack, Webpack and Rollup migrations!</p></section>
<section><h2 id="build-time-generated-table-of-contents">Build-time generated table of contents</h2><img alt="A simple table of contents widget, each item has a corresponding number next to it. The active item has a highlighted number with a different color and an underline." loading="lazy" decoding="async" sizes="(min-width: 347px) 347px, 100vw" style="object-fit:cover;max-width:347px;max-height:352px;aspect-ratio:0.9857954545454546;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_347,h_352,c_lfill,f_auto/v1725331021/table-of-contents 347w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_649,c_lfill,f_auto/v1725331021/table-of-contents 640w,
https://res.cloudinary.com/nicchan/image/upload/w_694,h_704,c_lfill,f_auto/v1725331021/table-of-contents 694w" src="https://res.cloudinary.com/nicchan/image/upload/w_347,h_352,c_lfill,f_auto/v1725331021/table-of-contents"><p>Having a table of contents generated at build wasn’t always easy to implement. It’s not uncommon to have to resort to client-side JavaScript in order to scrape the rendered content for headings, and this can sometimes be really undesireable due to the potential layout shifts, as Table of Contents are often relatively high up on a page. Astro makes this super easy by providing a list of headings already, and with some finangling with <a href="https://kld.dev/building-table-of-contents/#building-a-nested-table-of-contents-array">a snippet from Kevin Drum’s tutorial</a>, I was able to get the headings in a nested format. It only took a little client-side JS to hook up an Intersection Observer to watch the content and highlight the section we’re currently on. Rendered HTML with a little sprinkle of JS for extra functionality, just the way I like it!</p></section>]]></content>
        <published>2024-09-29T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Launch day]]></title>
        <id>https://www.nicchan.me/blog/launch-day/</id>
        <link href="https://www.nicchan.me/blog/launch-day/"/>
        <updated>2024-09-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[My heartfelt thanks]]></summary>
        <content type="html"><![CDATA[<img alt="Nic Chan's personal site in dark mode, a very purple-heavy site with a pixel art/Windows 95 operating system aesthetic. The content is divided into little windows that you can minimize and maximize. The homepage features sections titled 'Introduction', 'Clients' and 'Featured Posts'." loading="lazy" decoding="async" sizes="(min-width: 2000px) 2000px, 100vw" style="object-fit:cover;max-width:2000px;max-height:1498px;aspect-ratio:1.335113484646195;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_479,c_lfill,f_auto/v1726633756/homepage 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_562,c_lfill,f_auto/v1726633756/homepage 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_620,c_lfill,f_auto/v1726633756/homepage 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_719,c_lfill,f_auto/v1726633756/homepage 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_809,c_lfill,f_auto/v1726633756/homepage 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_959,c_lfill,f_auto/v1726633756/homepage 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1249,c_lfill,f_auto/v1726633756/homepage 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1438,c_lfill,f_auto/v1726633756/homepage 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2000,h_1498,c_lfill,f_auto/v1726633756/homepage 2000w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1534,c_lfill,f_auto/v1726633756/homepage 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1917,c_lfill,f_auto/v1726633756/homepage 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2397,c_lfill,f_auto/v1726633756/homepage 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_2876,c_lfill,f_auto/v1726633756/homepage 3840w,
https://res.cloudinary.com/nicchan/image/upload/w_4000,h_2996,c_lfill,f_auto/v1726633756/homepage 4000w" src="https://res.cloudinary.com/nicchan/image/upload/w_2000,h_1498,c_lfill,f_auto/v1726633756/homepage">
<p>Welp, the cat’s out the bag! I posted my website on Mastodon as a ‘soft-launch’, expecting my normal audience of 20-30 loyal followers to see it. I certainly didn’t expect to hit the explore/trending page on multiple instances and nearly a hundred comments! Thank you so much.</p>
<p>I am honestly incredibly moved by the number of comments from Web Standards folks, browser developers, IndieWeb advocates, and random passerbys who are just nostalgic for the older web. The number of comments I have received from people who I consider my personal idols honestly floors me. I especially treasure the comments from those who have used an accessibility feature or enjoyed the keyboard-only experience, it makes all that extra work worth it.</p>
<section><h2 id="my-approach-to-personal-sites">My approach to personal sites</h2><p>This year has been pretty difficult in that I haven’t been able to do a lot of work that fully reflects my values. I have always wanted to build things that are cool and awesome without sacrificing the core values of progressive enhancement, performance and accessibility, and I’m glad that 1. that approach still resonates with so many people and 2. that I have somewhat managed to succeed in this goal! The idea to build an OS-themed site is certainly not new, but there aren’t a lot of them that don’t have a load screen, that work without JavaScript and on mobile-sized screens, so it’s been fun trying to solve those challenges.</p><p>I’m really glad to have a little space on my own where I get to explore new advancements in core web technologies and finetune my approach to <a href="https://gomakethings.com/on-craft-and-the-web/">the craft of making websites</a>. There are still things I’d like to do with the site, but I’m trying to treat it more like a garden — just because things might change and grow over time, it doesn’t mean that it isn’t beautiful and worth looking at in it’s current state.</p></section>
<section><h2 id="thanks-ej">Thanks, EJ</h2><p>I wanted to give a shoutout to my good friend <a href="https://front-end.social/@codeability">EJ Mason</a> for their unwavering support, and willingness to discuss accessibility issues and look at things in Safari, my worst nemesis. The process of getting this done was spread out over a year, stuffed between busy periods of client work, and I can’t thank them enough for being there throughout the process.</p></section>
<section><h2 id="whats-next">What’s next?</h2><p>One of the reasons I decided to stop tinkering and hit the publish button was effort of maintaining two versions of my blog simultaneously. It was really holding me back from publishing more in a way I didn’t initial expect. I have a few drafts in the backlog that I’m excited to share, and I intend to finish up <a href="https://www.nicchan.me/blog/building-my-new-website-part-1/">the series on building this very site</a>, if only to document some of the cool new things we have in CSS that I learned along the way! I hope you’ll stick around for the journey!</p><p>(PS: I really hope I didn’t break anyone’s RSS feeds by migrating! Please let me know if I did!)</p></section>]]></content>
        <published>2024-09-18T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building my new website: light-dark and blog posts]]></title>
        <id>https://www.nicchan.me/blog/building-my-new-website-part-4/</id>
        <link href="https://www.nicchan.me/blog/building-my-new-website-part-4/"/>
        <updated>2024-09-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Finally making headway]]></summary>
        <content type="html"><![CDATA[<p>This is a post in a series about building my new website, check out <a href="https://www.nicchan.me/blog/building-my-new-website-part-3/">the previous post in this series.</a></p>
<section><h2 id="blog-page">Blog page</h2><img alt="A blog page with a purple retro-styled Windows design. The largest window on the left has a list of all blog posts, and the right column has a list of blog post tags and an a11y-webring widget." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1725323189/personal-8 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1725323189/personal-8 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1725323189/personal-8 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1725323189/personal-8 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1725323189/personal-8 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1725323189/personal-8 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1725323189/personal-8 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1725323189/personal-8 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1725323189/personal-8 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1725323189/personal-8 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1725323189/personal-8"><p>Whenever I’m feeling like progress has stalled on a progress, I try to work on things that I know are quick wins. In this case, it’s making new pages! I throw together the main blog and blog tag pages really quick with components and layout primitives that have already been built. Finally, I feel like I’m at the point where things just flow together based on previous work.</p><p>I have added a component for the <a href="https://a11y-webring.club/">a11y-webring.club</a>. I’m not presently a member but would like to join once I get my new site running. Membership requires a public accessibility statement, so I make a mental note to draft one at some point.</p></section>
<section><h2 id="theming">Theming</h2><img alt="A blog page with a purple retro-styled Windows design. The largest window on the left has a list of all blog posts, and the right column has a list of blog post tags and an a11y-webring widget." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1725323445/personal-9 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1725323445/personal-9 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1725323445/personal-9 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1725323445/personal-9 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1725323445/personal-9 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1725323445/personal-9 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1725323445/personal-9 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1725323445/personal-9 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1725323445/personal-9 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1725323445/personal-9 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1725323445/personal-9"><p>I do a whole bunch of work with the color variables to flesh out the light and dark themes a bit more. The new light-dark function puts in a bunch of work here, and I’m really excited we no longer have to use mixin based approaches in order to manage this without unneccessary duplication.</p></section>
<section><h2 id="code-styles">Code styles</h2><img alt="Side by side of a dark and light theme for code snippets. The dark theme is mostly shades of purple, and the light theme uses magenta, teal and purple." loading="lazy" decoding="async" sizes="(min-width: 1282px) 1282px, 100vw" style="object-fit:cover;max-width:1282px;max-height:694px;aspect-ratio:1.8472622478386167;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_346,c_lfill,f_auto/v1725328992/code-styles 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_406,c_lfill,f_auto/v1725328992/code-styles 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_448,c_lfill,f_auto/v1725328992/code-styles 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_520,c_lfill,f_auto/v1725328992/code-styles 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_585,c_lfill,f_auto/v1725328992/code-styles 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_693,c_lfill,f_auto/v1725328992/code-styles 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1282,h_694,c_lfill,f_auto/v1725328992/code-styles 1282w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_903,c_lfill,f_auto/v1725328992/code-styles 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1039,c_lfill,f_auto/v1725328992/code-styles 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1109,c_lfill,f_auto/v1725328992/code-styles 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1386,c_lfill,f_auto/v1725328992/code-styles 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_2564,h_1388,c_lfill,f_auto/v1725328992/code-styles 2564w" src="https://res.cloudinary.com/nicchan/image/upload/w_1282,h_694,c_lfill,f_auto/v1725328992/code-styles"><p>I was delighted to find that Astro comes with code syntax highlighting out of the box, but unfortunately, none of those out of the box themes met contrast requirements. Making accessible coding themes is very difficult, as you want the colors to have enough contrast between each other, as well as the background, but also, not be so jarring that it’s hard to read. I found this <a href="https://themes.vscode.one/your-themes">online tool for making VSCode themes</a> and I just quickly slapped some of my website colors together so that I could at least have something that worked for the blog.</p></section>
<section><h2 id="blog-post-styling">Blog post styling</h2><p>Back to the more difficult task of sorting out the blog post content. I have a few things that I need to sort out when migrating my blog post content. First up, I have some small HTML pieces I need to style. This isn’t too bad either way, just a matter of adding CSS to those classes, phew.</p><p>I also need to convert my Cloudinary image Nunjucks snippet into an other format. I consider it necessary for performance to serve responsive, optimized images to the end user, and I used to rely on my Cloudinary Nunjucks snippet to do so. Although Astro does support optimizing your local images automagically in markdown, and the documentation currently implies that it does also do this for remote images, an Astro team member has informed me that it is currently not the case.</p><p>Astro does support MDX out of the box, which let me use components (framework or web) in my post, but I had noticed that it would make it an order of magnitude more complicated to <a href="https://scottwillsey.com/rss-pt2/">output the full post in your RSS feed if you used MDX.</a> In short, Astro exposes a method to get the HTML from a post if you’re using vanilla markdown, but not MDX. This is the first time I’ve hit major limitations with Astro, which is not unexpected (every framework has limitations!), but it definitely took the wind out of my sails.</p><p>Further complicating matters, a small handful of my posts have some minor scripting. This is small things, like a button to slow down videos, and a transcript that has timestamps that link to the player. The use of View Transitions and Svelte hydration means that making this all work is much more difficult than dropping 20 lines of script into a blogpost. I tinker around a bit with adding event listeners for component mounting and page swapping, but the Youtube player example is especially finicky due to requiring an external script event as well. In the end, I decide to ditch the linked transcript numbers, and opt for converting the little demo videos into a Web Component for greater portability in the future.</p></section>]]></content>
        <published>2024-09-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[When security and accessibility clash: Why are banking applications so inaccessible?]]></title>
        <id>https://www.nicchan.me/blog/when-security-and-accessibility-clash/</id>
        <link href="https://www.nicchan.me/blog/when-security-and-accessibility-clash/"/>
        <updated>2024-04-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A quick exploration of accessibility anti-patterns in banking applications]]></summary>
        <content type="html"><![CDATA[<p>Let’s be real, regardless of your ability level, most banking applications are not particularly fun to use. They tend to be bogged down with difficult interfaces that could make a UX designer cry.</p>
<p>While using different bank applications, I’ve noticed a disturbing trend where so-called ‘security’ features are actively making the website a more hostile experience for disabled users. Below, I’ve rounded up some of the worst anti-patterns to discuss how they are creating blockers for disabled people.</p>
<section><h2 id="disabling-copy-and-paste--password-managers">Disabling copy and paste / password managers</h2><p>It’s become increasingly common to see sites preventing copy and paste on password fields. <a href="https://humanwhocodes.com/blog/2023/07/disabling-paste-textboxes-security/">Nicholas C. Zakas has written an extensive blog post</a> on why this pattern is a bad idea in terms of security. The newly formalized Web Content Accessibility Guidelines (WCAG) 2.2 has added <a href="https://www.w3.org/TR/WCAG22/#accessible-authentication-minimum">Success Criterion 3.3.8</a>, which explicitly mentions support for copy and paste as well as password managers as valid mechanisms for satisfying this criterion.</p><p>Blocking copy and paste often has the effect of blocking password managers as well. I’m sure I don’t need to go into detail about how frustrating this can be for password manager users, who often use those password managers to generate long passwords of random alphanumeric strings that they now have to type in character by character. Logging in, a process that should take me 10 seconds at most, can take up to 5 minutes of painful trial and error.</p><p>For example, the mobile banking app for DBS blocks copy and paste on the authentication screen. They’ve also disabled the default keyboard in order to show their own keyboard instead, which uses their brand’s red lettering on a white background. This color scheme does not mean color contrast requirements, and even I, someone with decent vision, struggled to read it. I would include a screenshot here, but they’ve disabled the ability to take screenshots of their application.🤷</p><p>For voice dictation users, disabling password managers is even more disastrous. The voice dictation software Dragon is a hands-free experience that works entirely by the user dictating voice commands to the software. A loose example (not actual Dragon) of a command might be something like ‘Click [Password Manager] Extension’ &gt; ‘Search [Bank Name]’ &gt; ‘Choose second item on a list ’, which would populate the username and password fields automatically. By disabling copy and paste, the user is instead forced to dictate their entire password out loud, which means that a Dragon user must be in a place where no one is within earshot in order to maintain a reasonable expectation of privacy.</p><p>What’s worst of all, in contrast to what the application authors intended, disabling copy and paste and password managers may actually have a negative impact on security as it encourages the <a href="https://www.zdnet.com/article/were-all-still-using-the-same-passwords-even-after-theyve-been-breached/">reuse of passwords</a>. Though there is increasing public awareness on why password reuse is bad, people are unlikely to change their behavior until the alternative is easy, and right now, it’s definitely not easy.</p></section>
<section><h2 id="captcha">CAPTCHA</h2><p>A CAPTCHA is a type of test employed by web developers to be able to tell if the user is a real human or a robot. These days, it is rarer for users to see CAPTCHAs in the wild due to widespread adoption of reCAPTCHA v3, a service by Google that claims to be able to detect malicious actors without requiring an interactive challenge. Privacy concerns aside for a moment, it can be difficult for behavioral based heuristics to distinguish disabled users from bots. For example, a screen reader user may come across as a bot due to not having normal mouse movement. However, due to the legal compliance and privacy issues arising from using a third-party service, banks that have a CAPTCHA often roll their own solution, which may not be tested comprehensively with disabled users.</p><p>Traditional CAPTCHAs display text that is partially obfuscated, asking the user to decipher the text. If a site is using this type of CAPTCHA, at a bare minimum, some kind of audio CAPTCHA is required too, as blind screen reader users will not be able to parse images of text. Much has been said about the inaccessibility of CAPTCHA mechanisms, as detailed in this <a href="https://www.w3.org/TR/turingtest/">thorough report by the Accessible Platform Architectures Working Group</a>, but in short, CAPTCHAs provide major blockers to users with cognitive disabilities or sensory issues. Remembering an audio CAPTCHA string can be tricky and frustrating if the user needs to toggle back and forth between an input and a play button. Image and text CAPTCHAs are intentionally made to be difficult to read.</p><p>With Artificial Intelligence (AI) gaining traction, there is no question in my mind that there are malicious actors actively training AI to be able to solve CAPTCHA challenges. We maybe looking at a future where AI will be able discern CAPTCHA content much better than most humans, so we need to be looking at alternative solutions that aren’t based on people’s ability to discern and perceive.</p></section>
<section><h2 id="two-factor-authentication-2fa">Two-Factor Authentication (2FA)</h2><p>Though they tend to be better than CAPTCHAs, Two-Factor Authentication (henceforth abbreviated to 2FA) solutions often pose different types of challenges to disabled users. There are various ways in which 2FA typically works, but the most common format by far, with over <a href="https://cyberscoop.com/two-factor-authentication-duo-security-yubikey/">90% of sites using this method</a> is SMS, where a code is sent to you via text message and you need to enter that same code on the application. Email is also common, with specialized authenticator apps coming a distant third. These solutions do pose a similar challenge to voice dictation users as mentioned above, although the security risk is somewhat mitigated by the one-time nature of these temporary passwords.</p><p>2FA solutions almost always have a strict timeout, with some being as short as 20 seconds. Asking users to memorize arbitrary strings can pose a challenge for users with cognitive disabilities, and the short time limits are often frustrating to all users. I’ve yet to see a 2FA solution that allows the user to extend the the time as required by the <a href="https://www.w3.org/WAI/WCAG21/Understanding/timing-adjustable.html">2.2.1 Timing Adjustable</a>. Having an option to generate a new code may not be a sufficient replacement as the time window may be so short that a disabled user can never complete it within the allotted time.</p><div class="post-callout"><p><p>This is the process for me to pay myself from my business bank account using
my computer. Unfortunately, due to needing developer and accessibility tools
on my phone, it does not allow me to use the ‘streamlined’ process, but
gives me a horrendous 2FA filled nightmare.</p></p><ol><li>Log in to my bank account on my desktop with a password manager.</li><li><p>Authenticate the login by using the phone app to generate an 8 digit code,
which I enter on my computer</p></li><li>Type in the transaction details.</li><li>Get a code via SMS.</li><li>Enter a PIN number to unlock the phone app.</li><li><p>Enter the SMS code into the transaction approval screen on the phone app.</p></li><li><p>Get a response code from the phone app, which I now type into the desktop
transaction approval screen.</p></li><li><p>Contemplate if this whole internet business is really worth the hassle of
not having to talk to a human person.</p></li></ol></div><p>The fact that these 2FA solutions often require the use of multiple devices can be problematic. If a blind user is using a computer and needs to also use a mobile device to receive an SMS, they may need to switch their headphones over to the mobile device in order to listen to the code. Furthermore, some assistive technology setups are so specific that users may be limited to a specific device, such as certain kinds of Braille displays that may only work with one primary device.</p><section><h3 id="physical-security-keys">Physical security keys</h3><p>Though physical security keys are the least popular method for 2FA, they can be very beneficial for accessibility. Instead of requiring users to memorize or input arbitrary strings, physical security keys work by allowing the user to push a button on the key itself. The <a href="https://nfb.org/blog/physical-keys-your-digital-life-review-yubico-and-google-security-keys">National Federation of the Blind has written a generally positive review</a> of the technology, but it does note that there are some existing accessibility issues that may range from “somewhat annoying to showstopping”.</p><p>With these devices, accessibility depends on a third-party, whose applications are not under your control. While these apps may generally be accessible, it is not unheard of for regressions to occur, even in applications by major companies.</p><p>Another drawback to this form of 2FA is the upfront cost. Although the cheapest keys, found for $20 USD, may not break the bank for people in countries with a high purchasing power, this cost can be fairly exorbitant for some, and it feels unjust to shunt disabled users into buying an something extra to make up for the pitfalls of an ableist system.</p></section><section><h3 id="biometrics">Biometrics</h3><p>One final form of 2FA is tied to a users’ biometrics. In these implementations, the application relies on the device’s built-in ability to collect biometric verification in order to confirm a user’s identity. If you choose to utilize this form of 2FA, be lenient in what forms you accept. Fingerprints or facial recognition may be easier for users who can’t remember an arbitrary string, but they can be frustrating for users who are missing limbs, who have mobility issues, or have a facial appearance that is regarded as ‘non-standard’. Anyone who has had had the experience of trying to have a video call with an elderly relative who struggles to get their whole face into the frame will understand that some things that are perceived as trivial to some are very difficult for others, depending on their ability level.</p><p>There is no universally perfect method for 2FA. The best thing that you as an application developer can do is allow for the user to choose their method of 2FA so that they can choose a method that works for them.</p></section></section>
<section><h2 id="blocking-keyboard-events">Blocking keyboard events</h2><p>I had a enlightening conversation with a blind developer who banks at First Foundation Bank. They informed me that the bank’s web app directly interferes with or obfuscates the output of a keyboard, making them unable to use a screen reader at all with the application. Even though this user is an expert screen reader user and a web developer, they have call in to complete any banking services.</p><p>From the outside, it’s hard to know why First Foundation Bank implemented such a feature, but it could be possible that the bank thinks that preventing all keystrokes outside of designated inputs would prevent them from certain kinds of bad actors and security issues. It could also be the inadvertent result of a poorly crafted application that has not undergone screen-reader testing. Setting something like <code>role=&quot;application</code> or <code>aria-hidden=&quot;true&quot;</code> can easily cause an application to be completely broken or hidden for screen reader users. Either way, be careful about making drastic application-wide overrides of keyboard behavior.</p></section>
<section><h2 id="lack-of-feature-parity-between-desktop-and-mobile-apps">Lack of feature parity between desktop and mobile apps</h2><p>(This point is less about security, and more typically about teams trying to reduce complexity across different digital products, but I thought it was worth mentioning anyway.)</p><p>I recently received an email from HSBC noting that they were discontinuing services from the web application, and moving them exclusively to the mobile application. The discontinued features included key services such as setting an overseas ATM limit, activating a credit card, and settings for quick transfer limits. As mentioned previously, some kinds of disabilities require specific assistive technologies, and it’s not always possible for users to switch devices with ease. If at all possible, it’s always better to make sure key features are available on as many platforms as possible.</p></section>
<section><h2 id="conclusion">Conclusion</h2><p>For many disabled people, the internet is a vital way to stay connected and retain some measure of independence and autonomy. Banking applications have an extra responsibility to ensure their their services are accessible as possible and to think through new security measures extra carefully before implementation. If you absolutely need to make a process more difficult due to security reasons, provide multiple pathways for a user to complete the task.</p></section>]]></content>
        <published>2024-04-17T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building my new website: Modern problems require Modern CSS techniques]]></title>
        <id>https://www.nicchan.me/blog/building-my-new-website-part-3/</id>
        <link href="https://www.nicchan.me/blog/building-my-new-website-part-3/"/>
        <updated>2024-03-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I refactor things, several times]]></summary>
        <content type="html"><![CDATA[<p>This is a post in a series about building my new website, check out <a href="https://www.nicchan.me/blog/building-my-new-website-part-2/">the previous post in this series.</a></p>
<p>Author’s note: I’m abandoning the day format as I realized I tend to work on things during the downtime of other moments, so the ‘days’ became arbitrary units of when I felt enough stuff was accomplished to write about, thus defeating the purpose entirely!</p>
<p>It’s been quite a while! Towards the end of 2023, I got sucked into project work. I felt a bit mixed on this, I was grateful to not be struggling at a time when the industry faced mass layoffs, but at the same time, it really pulled me away from all the ‘extracurricular’ work I like to do. Nevertheless, 4 months later, I’m finally out of the woods and back on track.</p>
<section><h2 id="hurrah-it-lives">Hurrah, it lives</h2><p>At some point, I managed to <a href="https://nicchan-v2.netlify.app/">get a deploy going</a> on Netlify so I could be sure that the whole thing actually builds. I’m not exactly sure when this occurred, but I am very proud of my past self. I’d much rather debug a build as the process goes along, plus it gives me something to show people.</p></section>
<section><h2 id="css-logical-properties">CSS Logical Properties</h2><p>I suddenly remember that I want to switch over to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values">CSS Logical Properties</a>. I know I’m a little behind the curve on this one. I do often work on projects that involve Chinese, a language that can also be displayed vertically, so I want to try learn this ASAP. It’s a pretty easy conversion, but I do get stuck on if I want to set widths and heights using logical properties. I’m not entirely sure I want to account for re-orienting this very complex window layout, so I just focus on the text properties for now, to treat this as a learning experience.</p></section>
<section><h2 id="testimonials">Testimonials</h2><img alt="An about page, two windows, one with a bio and the other with a list of testimonials. The testimonials are styled like a chat interface." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1709706012/personal-6 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1709706012/personal-6 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1709706012/personal-6 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1709706012/personal-6 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1709706012/personal-6 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1709706012/personal-6 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1709706012/personal-6 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1709706012/personal-6 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1709706012/personal-6 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1709706012/personal-6 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1709706012/personal-6"><p>I build out the testimonials section in the style of a chat interface. After spending so long tinkering on little things, it’s nice to do some CSS work where the little things fall into place! Note to self, go ask more recent clients for a testimonial.</p></section>
<section><h2 id="blog-post-styles">Blog post styles</h2><img alt="A sample blog post, where there is a window for a table of contents on the left, and the full post body on the right." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1709706012/personal-7 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1709706012/personal-7 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1709706012/personal-7 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1709706012/personal-7 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1709706012/personal-7 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1709706012/personal-7 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1709706012/personal-7 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1709706012/personal-7 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1709706012/personal-7 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1709706012/personal-7 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1709706012/personal-7"><p>I’m at a point where I’m starting to work on the styles for my long-form content. I spend way, way more time on this than I want to, but I am really picky about these things. It’s all little things like tweaking the heading colors, the list styles, things like that. I love a good personal site where the text content just feels super polished and thought out.</p><p>I somehow find myself spending a significant amount of time working on the windows, again. There are slightly different behaviors for the windows based on things like its state (whether it’s minimized, maximized, or in its default mode), the size of the viewport and whether or not there are other windows present on the screen. Plus, each window accepts a wide variety of positioning and sizing props, which still need to work responsively. Getting all those combinations right is gonna take up even more time than I thought. I make a mental note to check in with some accessibility friends about the expected focus management behavior.</p><p>I really want to make a table of contents. I know I’m VERY rambly and therefore it’s nice to have a link to all the headings somewhere, but I realize that I need to build yet another window configuration if I’m to make it work. This variation on the window is positioned absolutely on middling screen size viewports, but centered on super large viewports. I get really sad that I can’t use CSS variables in media queries, this would make this an extremely elegant way to handle this, but I end up just going with a number that works for now.</p><p>Shortly after finalizing this approach, I make a breakthrough on a grid-based approach that I had experimented with and abandoned prior. I was having trouble making sure the contents of my windows would scroll within their containers, but I realized I just had to keep it simple and remove the absolute positioning from the grid children. D’oh! I adjust the blog post and about pages to utilize this new layout, which looks nearly identical but it pleases me to have a more elegant solution that looks better on more screen sizes.</p></section>
<section><h2 id="refactoring-the-scss-mixins">Refactoring the SCSS Mixins</h2><p>Here, I had another breakthrough and realized I could simplify my pixel borders. I was using this <a href="https://nigelotoole.github.io/pixel-borders/">pixel borders SCSS mixin</a>, and my major gripe with it was that it didn’t work with CSS properties, due to being written in the SCSS age. The mixin also supported different levels of roundedness, and I had decided pretty early that I on that I only want a single ‘pixel’ chunk cut out of each corner. This means I could replace all of this:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="scss"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@function</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> str-replace</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">$string</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $search</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $replace</span><span style="color:#4075BA;font-style:italic;--shiki-dark:#B993FF;--shiki-dark-font-style:italic">:</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	$index</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> str-index</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">$string</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $search</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">	@if</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $index</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">		@return</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> str-slice</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">$string</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 1</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $index</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> -</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 1</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) </span><span style="color:#1F7D89;--shiki-dark:#83CDFF">+</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $replace</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> +</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> str-replace</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">str-slice</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">$string</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $index</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> +</span></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">						str-length</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">$search</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">))</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $search</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $replace</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">	@return</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $string</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@mixin</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> pixel-borders</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	$color</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	$border-radius</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> calc</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) </span><span style="color:#1F7D89;--shiki-dark:#83CDFF">*</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 3</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> +</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 1</span><span style="color:#4075BA;--shiki-dark:#B993FF">px</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">)</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	$escapedColor</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> str-replace</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">$color</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> +</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;#&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;%23&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-style:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> solid</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-width:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-color</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-radius:</span><span style="color:#4075BA;--shiki-dark:#B993FF"> $border-radius</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-image-slice:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 2</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-image-width:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 1</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-image-outset:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border-image-source:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> url</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;data:image/svg+xml,&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;6&#39; height=&#39;6&#39;&gt;&lt;path d=&#39;M0 2h2v2H0zM2 0h2v2H2zM4 2h2v2H4zM2 4h2v2H2z&#39; fill=&#39;</span><span style="color:#4075BA;--shiki-dark:#B993FF">#{$escapedColor}</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&#39; /&gt;&lt;/svg&gt;&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>With this:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="scss"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@mixin</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> pixel-borders</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	border:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) </span><span style="color:#1F7D89;--shiki-dark:#83CDFF">solid</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-color</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	mask</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> conic-gradient</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span></span>
<span class="line"><span style="color:#4075BA;font-style:italic;--shiki-dark:#B993FF;--shiki-dark-font-style:italic">			at</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) </span><span style="color:#1F7D89;--shiki-dark:#83CDFF">var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">)</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">			#000</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 270</span><span style="color:#4075BA;--shiki-dark:#B993FF">deg</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">			#0000</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		) </span><span style="color:#1F7D89;--shiki-dark:#83CDFF">0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> /</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> calc</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">100</span><span style="color:#4075BA;--shiki-dark:#B993FF">%</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> -</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">)) </span><span style="color:#1F7D89;--shiki-dark:#83CDFF">calc</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">100</span><span style="color:#4075BA;--shiki-dark:#B993FF">%</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> -</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> var</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">--border-thickness</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">));</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>I took much inspiration from <a href="https://css-tricks.com/css-borders-using-masks/">this great article by Temani Afif</a>. Thanks Temani, you really saved me on this one!</p><p>At this point, I contemplate moving off of SCSS mixins briefly. I am not against utility classes and have a couple myself, but I’m using mixins primarily for the pixel borders and focus outlines, and I may want them within blog post content. I don’t particularly fancy applying a <code>focus-outline</code> class to every single link in my text content, either through using a parser or manually. As a result, I conclude that mixins are helping me enough to survive this brutal round of code culling.</p><p>After doing a bit of cleanup, I come to the horrifying realization that CSS masks cover the outline property, which is needed for focus outlines. I read the spec to make sure this is as intended, and confirm that it is. It doesn’t feel great to me since it means a wrapper element is necessary when using masks on interactive elements to make sure you don’t completely break your outlines, but I accept this as a tradeoff.</p><p>With all this tinkering, I’ve probably spent an entire workday on changes that are basically completely invisible outside of a couple of niche cases:</p><ol>
<li>the window arrangements don’t stack unnecessarily on small desktop screens</li>
<li>buttons don’t have this gross blurry border when subpixel rendering is forced (basically only if you’re zooming.)</li>
</ol><p>It’s hard not to feel a little despair here at the time to effort ratio. I take solace in the fact that during the process of sorting all this out, I did a deep dive into CSS masks/clip-paths/mask-borders, and feel confident that I could apply them on future client work, so it’s not too horrible.</p></section>]]></content>
        <published>2024-03-06T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[The types information available on the internet and why AI is bad for all of them]]></title>
        <id>https://www.nicchan.me/blog/the-types-of-information-available-on-the-internet-and-why-ai-is-bad-for-all-of-them/</id>
        <link href="https://www.nicchan.me/blog/the-types-of-information-available-on-the-internet-and-why-ai-is-bad-for-all-of-them/"/>
        <updated>2024-02-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Warning, the outlook is not positive.]]></summary>
        <content type="html"><![CDATA[<p>The Merriam-Webster dictionary defin- no, don’t worry, this post isn’t going to start that way! However, I do need to get some terminology out of the way lest the reply-guys come for me. What’s colloquially known as Artificial Intelligence (AI) generally refers to large language models (LLMs), used to generate text. This post only discusses the effects of these LLM based tools and not other applications of AI such as image or video generation, machine learning, and etc. Additionally, this post doesn’t even begin to touch on the ethical and environmental issues of LLMs, a topic that is covered at length by many other more knowledgeable folks.</p>
<hr>
<p>Those who know me probably know that I spend a disproportionate amount of time thinking about the state of the Internet. You don’t have to be chronically online to know that it feels harder than ever to get the information you’re looking for. LLM-generated content is not the sole reason behind this recent phenomenon, but it certainly plays a key role in the sharp downward trend in the quality of information available these days. This article aims to cover the key kinds of information that we as internet users tend to seek out, and how AI is ruining all of it.</p>
<section><h2 id="anecdata-opinions-and-reviews">“Anecdata”, Opinions and Reviews</h2><p>I’m definitely the kind of person that needs to thoroughly research any significant potential purchases before I make them. I have a list of sources I feel I can trust for different kinds of purchases, but I also seek out reviews from complete strangers, often from niche online communities. Although I don’t tend to take any single review as the sole reason why I should or shouldn’t buy something, this kind of anecdata often provides a different kind of information that’s not always evident in a product’s claims.</p><p>For example, if I’m looking to buy an ergonomic chair and I read a review that says “I’m a 6’3” man and this chair is absolutely perfect for my size,” this anecdote provides helpful information to me in a way that simply reading the measurements does not.</p><p>I shouldn’t need to explain why LLM-generated reviews, testimonials, recipes etc. are worse than useless. The main value from this kind of information comes from the fact that another living, breathing human has tried it. You may not necessarily agree with their take, but a made-up review is essentially garbage that litters the landscape of information.</p><p><a href="https://www.bbc.com/news/technology-65881800">Fake reviews were already a problem</a> when you needed a human to write them, and now that you’ve cut out another step in the process of generating fake reviews, the problem is worse than ever. A significant amount of time will be spent trying to fight and filter out the bad actors who have no problem with polluting the internet for a quick buck.</p></section>
<section><h2 id="factual-information">Factual Information</h2><p>I’ve been around long enough to know that the line between ‘objective truth’ and ‘subjective’ is a lot blurrier than some might realize, but for the purposes of this section, I’m calling what is generally agreed upon by the vast majority of people to be ‘factual information.’</p><p>Questions like “what time is this flight departing” and “are grapes safe for humans to consume” may differ slightly in terms of how many people can come to an agreement, but there is some general consensus that can be reached.</p><p>Sometimes, people just want information presented to them as quickly and as accurately as possible. At first glance, it may seem like LLMs are great for this. We’ve all seen cases where asking a LLM a question returns information that is both accurate and detailed. I have no doubts in my mind that LLMs are capable of providing information that is useful to users, some of the time.</p><p>What concerns me the most is when things go wrong. People often understand LLMs to be all-knowing, and it doesn’t help that they are often marketed this way. What some people do not know is that content summarized by AI may contain ‘hallucinations’, AKA factual inaccuracies, dressed up in a cutesy term to make their existence less egregious.</p><p>The rate of hallucinations appears to be far higher than some might believe. Vectara, an AI company (take this with a grain of salt!), has created a <a href="https://github.com/vectara/hallucination-leaderboard/">hallucination evaluation model</a> which aims to test the rate of hallucinations across popular models. To summarize their methodology very briefly, they used different LLMs on their most precise setting to summarize a specific set of documents. The hallucination rates ranged from <strong>3% - 16%</strong>, which doesn’t sound too horrible in terms of raw numbers, but should not be an acceptable margin of error on factual information.</p><p>Josh Collinsworth sums it up really well in his blog post, <a href="https://joshcollinsworth.com/blog/copilot">I worry our Copilot is leaving some passengers behind</a>.</p><blockquote>
<p>Why do we accept a product that not only misfires regularly, but sometimes catastrophically?</p>
<p>Let’s say you went out and bought the new, revolutionary vacuum of the future. It’s so amazing, it lets you clean your whole house in half the time! (Or so the vacuum’s marketing department claims, at least.)</p>
<p>Let’s say you got this vacuum home, and sure enough: it’s amazing. It cleans with speed and smarts you’ve never seen before.</p>
<p>But then, you start to realize: a lot of the time, the vacuum isn’t actually doing a very good job.</p>
<p>You realize you spend a lot of time following it around and either redoing what it’s done, or sending it back for another pass.</p>
<p>In fact, sometimes, it even does the exact opposite of what it’s supposed to do, and instead of sucking up dirt and debris, it spews them out across the floor.</p>
<p>You would find that entirely unacceptable. You would take that vacuum back to the store.</p>
</blockquote><p>If I had a watch that told me the incorrect time 3% of the time, that watch would be useless to me. If I used a travel agent and they gave me incorrect information, resulting in me having to pay more for my flight tickets, I would never use them again. The fact that Air Canada’s AI chatbot has made this very error on <a href="https://vancouversun.com/news/local-news/air-canada-told-it-is-responsible-for-errors-by-its-website-chatbot">their own website</a> and they refused to take responsibility for this mistake is ludicrous.</p><p>We as consumers should not accept companies rushing to implement AI chatbots in order to save on labor costs, and we especially should not accept those same companies refusing to take responsibility for the incorrect information that chatbot spews.</p><p>While there has been progress in cutting down on hallucinations, the remainder of the work is an uphill battle. You see, LLMs have an element of ‘randomness’ built in. To simplify a complex concept that is <a href="https://medium.com/@mariealice.blete/llms-determinism-randomness-36d3f3f1f793">better explained by others</a>, LLMs are not deterministic. Putting the same input into an LLM will result in slightly different outputs each time. This is part of the appeal, as it allows people to mass-generate content that is ‘unique’ each time, but results in a non-zero percentage chance for error.</p><p>I genuinely fear that one day, someone in a moment of crisis is going to rely on LLM-generated content for a life-or-death question such as ‘how should I help my drunk friend who passed out’ or ‘what should I do when someone is having a seizure’ and get inaccurate results. Even people who would normally think critically about a piece of information may be vulnerable to incorrect information in a moment of crisis. Letting something that cannot be fully verified or controlled run loose to spew misinformation seems <a href="https://medium.com/net-magazine/designing-for-crisis-9cab10b4c519">counter to our jobs as designers, designing for people.</a></p></section>
<section><h2 id="interpretive-information">Interpretive information</h2><p>LLMs have a really bad time with information that has inpretive intent behind it. For example, blind people rely on alt text, a written description of an image, so that their screen readers can read it out to them in lieu of being able to see the image. Certainly, some alt text is better than no alt text, but AI-generated alt text typically lacks the context behind why an image is included in a site.</p><p>For example, take <a href="https://www.shutterstock.com/image-photo/group-asian-people-enjoy-fun-outdoor-2217373711">this image from Shutterstock</a>. (I’m linking it so as to avoid paying to include the image on this site, sorry!) The included description for this image is as follows.</p><blockquote>
<p>Group of Asian people enjoy and fun outdoor lifestyle hiking and camping together on summer travel vacation. Man and woman friends having breakfast drinking brewed coffee near the tent in the morning.</p>
</blockquote><p>Certainly, this is a valid description for this image. I have no idea if this was human-written or AI-generated, but I would say it is perfectly serviceable for the context of a stock photo website. However, this description does not necessarily represent the reasons why people might include this image in their own content.</p><ul>
<li>A site selling this particular coffee setup might say: “A group of friends use the portable Coffee Maker 3000 with our kettle in brushed steel in the great outdoors.”</li>
<li>If this image were posted to social media, it might say: “Me and my best friends John, David and Beth enjoying our coffee while glamping at the local wetland camping reserve.”</li>
<li>If an extreme anti-camping environmentalist site were to post this image, it might say “Group of people engaging in the environmentally harmful practice of camping on federally-protected nature reserve.”</li>
</ul><p>There are as many ways to describe this image as there are ways to use it in context. Even if AI-generated alt text is accurate, it lacks context. Things like memes and jokes are especially ‘hard’ for LLMs to understand (written with irony here, given that they don’t actually have a consciousness!). To illustrate this point, I asked Copilot to explain the first joke I could find on the /r/Jokes subreddit.</p><blockquote>
<p>Me: Explain this joke:
My teeth were stained, so the dentist asked me, “Do you smoke or drink coffee?” I told him I drink it.</p>
<p>Copilot: Okay, I can try to explain the joke to you. The joke is based on the ambiguity of the word “it” in the sentence “I drink it”. The dentist is asking the person if they smoke or drink coffee, because both of these habits can stain the teeth. The person replies that they drink it, which could mean either coffee or smoke. The joke is that the person is implying that they drink smoke, which is absurd and nonsensical. The joke is meant to be funny because of the unexpected and ridiculous answer. I hope this helps you understand the joke better.</p>
</blockquote><p>Copilot’s explanation is partially correct. It correctly states that the joke is based on the dentist’s wording being ambiguously interpreted as either smoking or drinking <strong>coffee</strong>. However, it misses the mark in saying the person is implying they drink smoke.</p><p>I hope the joke example clarifies why <strong>interpretation</strong> is so important when it comes to understanding information. Sadly, I know there are entire masses of people out there who are excited to give even less of a shit about disabled people than they already do. They are excited to automate this alt text problem away so they don’t have to spend a single second thinking about what they as a sighted user do not benefit from. They don’t realize that crafting alt text is an art, I’m not ready to hand over art to the robots just yet.</p></section>
<section><h2 id="educational-content">Educational content</h2><p>There’s a type of content that I’m having a hard time describing in a short phrase. Essentially, by ‘educational content’, I’m referring to content that does have factual elements, but the primary value of the content is that it’s presented in a way that really enhances a reader’s understanding of the material. It’s not necessarily content that comes from educational instutions. The author may have put their own spin or understanding on it in a way that reading a summary of the information misses out on a lot of the value it brings.</p><p>I consider <a href="https://ishadeed.com/article/target-size">Ahmad Shadeed’s piece on Designing better target sizes</a> to be an absolute masterpiece in this genre. The article is chocked full of interactive examples, techniques and caveats that is helpful to a wide audience, regardless of background. The language is simple enough for a reader with no prior knowledge to understand, but the information is detailed enough that experienced readers can pick up a new insight or two. It’s what I aspire to with my technical writing.</p><p>As someone who’s dabbled in writing educational content, it’s not easy to create content like this, with good articles taking days, weeks or even months to finish. The thing is, it’s already really difficult to find people willing to pay for this content. Inside the tech bubble I work in, with the exclusion of paid software, there’s often not enough money going around to pay technical writers. Although technical writers must be both technical enough to understand the subject matter on a deep level, and good enough of a writer to be able to communicate that knoweldge, it’s simply <a href="https://whopaystechnicalwriters.com/">really hard to get the same rates</a> you would for writing about code as you would for writing actual code.</p><p>From what I’ve seen, LLMs aren’t good enough to create something as helpful as a good piece of human-authored technical writing, period. Try as I might, I could not get Copilot to output something about target sizes that even came anywhere close to the article written by Ahmad. The problem is that people believe that it can, thus shrinking the already tiny pool of jobs for good technical writers. Even MDN, a resource that I’d wager every single web developer has looked at at one point, felt that <a href="https://www.theregister.com/2023/07/06/mozilla_ai_explain_shift/">AI was worth implementing on their docs</a>, though it was met with much public outcry. In an age where documentation is being replaced with discord servers, it feels like the trajectory of software development is moving towards reviewing the code output of AI, and when the aforementioned code doesn’t run, ask the AI chatbot for help. Pretty bleak.</p></section>
<section><h2 id="conclusion">Conclusion</h2><p>Unfortunately, the cat (or the Bullshit Generator) is already out of the bag on this one. We can’t really take it back at this point, and the message from the technocrats is clear, adopt or fall behind. The personal impact of failing to adapt can be devastating when your entire livelihood depends on meeting a certain level of producitvity. My only bit of advice is to push back in your own way. Whether it be fighting unethical use of AI at your workplace, occasionally writing a blog post, curating a list of human-authored content, or logging off, my hope is that we all do what we can to stem the rising tide.</p></section>
<section><h2 id="related-reading">Related Reading</h2><ul>
<li>The exact scope and scale of the problem is difficult to determine, but if <a href="https://www.theverge.com/2024/1/12/24036156/openai-policy-amazon-ai-listings">these Amazon listings</a> are anything to go by, we are in a world of trouble.</li>
<li><a href="https://www.theverge.com/c/23194235/ai-fiction-writing-amazon-kindle-sudowrite-jasper">Josh Dzieza</a> dives into the world of AI-generated fiction</li>
<li>AI isn’t explicitly mentioned in this piece entitled ‘<a href="https://housefresh.com/david-vs-digital-goliaths/">How Google is killing independent sites like ours</a>’, but if the quality of the content doesn’t matter, it’s only a matter of time</li>
<li>Headnodding along to <a href="https://thejollyteapot.com/2024/02/7/what-bothers-me-about-chatgpt-and-a-I">this piece by Nicolas Magand</a></li>
<li>This mastodon post by <a href="https://hachyderm.io/@hbuchel/111938325310418356">Heather Buchel</a> is short, but apt.</li>
<li>Dave Rupert has <a href="https://daverupert.com/2024/02/robo-barf/">A dozen short thoughts about AI</a></li>
<li>I actually came across <a href="https://www.joedolson.com/2023/06/accessibility-and-artificial-intelligence/">this post by Joe Dolson about AI-generated alt text</a> after I wrote the section on it, it adds a lot of valuable Wordpress development context.</li>
<li><a href="https://piccalil.li/blog/a-global-documentation-platform/">Andy Bell</a> digs into the problem of MDN</li>
<li><a href="https://robertkingett.com/posts/6230/">Robert Kingett</a> shares his opinion on the usefulness of AI for the blind community</li>
</ul></section>]]></content>
        <published>2024-02-20T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Have we forgotten how to build ethical things for the web?]]></title>
        <id>https://www.nicchan.me/blog/have-we-forgotten-how-to-build-ethical-things-for-the-web/</id>
        <link href="https://www.nicchan.me/blog/have-we-forgotten-how-to-build-ethical-things-for-the-web/"/>
        <updated>2024-02-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Reflections on the arcane art of making good, ethical websites]]></summary>
        <content type="html"><![CDATA[<p>I’ve worked on a few projects where values and ethics were a key part of the clients’ mission. They weren’t putting profits over everything, they were trying to deliver something that would ultimately help people. They were innovators in their respective spaces, and knew how to connect to their audiences. It was for these reasons that I wanted to work with them in the first place.</p>
<p>But in the end, it always came down to us, the project team, to prevent the implementation of deceptive patterns that would erode users’ trust. It was small things, like offering a free service in exchange for email addresses that clients would blast newsletters to without explicitly collecting consent, or a client who works with marginalized people wanting to place a behavior tracker that logs personal user information.</p>
<p>Many of these things are commonplace, even the norm on other websites. A good number of users are ok with these patterns (or at least, an amount of users large enough to make a difference on the bottom line). When discussing these issues with clients, I could sense confusion as to why I was fighting for the hypothetical users who would care about these things, when most users either don’t know or don’t care. For a web practitioner who cares about the craft, using a website these days can feel like a death by a thousand cuts. The most horrifying of all to me, is that the average user, even those with a strong sense of ethics and justice, doesn’t even know what a good, ethical website is anymore. Being coerced into handing over your money/data and having your personal information tracked and sold is now par for the course.</p>
<p>So how did we end up here? To a casual observer, it may seem that somewhere along the way on the grand journey of building the web, the knowledge of how to build good, ethical things has been lost, buried with our Myspace and Geocities sites.</p>
<p>But is that really the case? Every day, I get to work with people who do good work, who care about things like privacy, ethics and accessibility. We exist. We just are drowning, drowning in algorithmically-guarded walled gardens that are nothing more than quagmires of <a href="https://pluralistic.net/2023/01/21/potemkin-ai/">enshittification</a>, of AI-generated content, of <a href="https://overlayfactsheet.com/">snake-oil solutions</a>. It’s like a walking into a jet spray of bullshit, so much so that even those with good intentions get easily overwhelmed.</p>
<p>Though I try, my efforts rarely bear fruit, even with the most well-meaning of clients. And look, I get it, no on wants to be the tall poppy. It’s hard enough to squeeze money from the internet-stone these days. Why take a stance on a tiny issue when your users don’t even care? Your competitors certainly don’t. I usually end up quietly acquiescing to whatever demands are made, praying no future discerning user will notice and think badly of me.</p>
<p>It’s pretty clear to me that we can’t rely on individual people to make a difference here. Unfortunately, large-scale, societal measures don’t seem to work either. Remember GDPR? It was supposed to save (some of) us from invasive tracking by giving users the power to choose how their data was collected. Now, everyone just hates the clutter of cookie popups and there is an entire industry built around helping companies to comply with GDPR in a way that makes it as hard as possible for users to express their newfound rights. When big corporations like <a href="https://brucelawson.co.uk/2024/apple-breaking-pwas-out-of-malicious-compliance/">Apple</a> spend a huge amount of time and resources actively fighting the idea of a better Internet, there doesn’t feel like a lot a single Person-Who-Gives-a-Shit can do.</p>
<p>I don’t really know what the answer to any of this is. I just know that I (belatedly) enter 2024 with a whole lot of burnout and an increased resolve to be more precious with the limited time and resources I have. If you need someone to build you a website, or gripe about the state of the web, I’ll be over here in my corner.</p>
<section><h2 id="related-reading">Related Reading</h2><p>This was a short piece on a huge topic. Here are some recent posts I enjoyed that touched on similar issues.</p><ul>
<li><a href="https://blog.jim-nielsen.com/2024/inbox-o-receipts/">Jim Nielsen laments how buying one thing now opts you into a lifetime of unwanted emails</a>, unless you opt out</li>
<li><a href="https://toddl.dev/posts/the-decision-to-leave-tech/">Todd Libby</a>, web developer and accessibility engineer, on his decision to leave tech</li>
<li><a href="https://daverupert.com/2024/01/what-can-a-website-do/">Dave Rupert</a> on the what a website can do</li>
<li><a href="https://blakewatson.com/journal/magnoliajs-2023-the-joys-of-home-cooked-apps/">Blake Watson</a> on building your own things</li>
<li><a href="https://www.fromjason.xyz/p/notebook/where-have-all-the-websites-gone/">Jason Velazquez</a> writes beautifully on where all the websites have gone</li>
<li><a href="https://www.craigabbott.co.uk/blog/stop-trying-to-recruit-unicorns-with-acorns/">Craig Abbott</a> sums up why we’ve gotten ourselves into this position from an accessibility perspective</li>
</ul><p>Thank you to my friend <a href="https://beckyisj.substack.com/">Becky</a> for her thoughts on an initial draft of this piece.</p></section>
<section><h2 id="responses">Responses</h2><ul>
<li><a href="https://hidde.blog/links/questioning-practices-for-a-more-ethical-web/">Hidde de Vries</a> outlines specific ethical things we can be aware of <a href="https://hidde.blog/what-kind-of-ethics-do-front-end-developers-need/">in this banger of an old post</a></li>
<li><a href="https://adactio.com/journal/20866">Jeremy Keith</a> shares an anecdote and a talk recommendation I’ll be looking out for</li>
</ul></section>]]></content>
        <published>2024-02-09T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[View transitions and stacking context: Why does my CSS View Transition ignore z-index?]]></title>
        <id>https://www.nicchan.me/blog/view-transitions-and-stacking-context/</id>
        <link href="https://www.nicchan.me/blog/view-transitions-and-stacking-context/"/>
        <updated>2023-10-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Thankfully, there's a workaround!]]></summary>
        <content type="html"><![CDATA[<p>Readers Of The Blog™ may know that I have been experimenting with the View Transitions API as a way to provide animations in my new portfolio site. Once I understood the <a href="https://daverupert.com/2023/05/getting-started-view-transitions/">basics of how View Transitions work</a> (in Dave’s words, think Animorphs), I found it relatively simple to get up and running with the default morph animation. In my case, I’m trying to replicate how an operating system will shrink and fadeout a window when you minimize it, within the same page. However, shortly afterwards, I noticed that there was a small issue, the animation was actually playing on top of the footer. Not ideal!</p>
<section><h2 id="the-problem">The Problem</h2><p>I know this can be hard to see at full speed, so I’ve quickly thrown together a little interface below so you can adjust the playback speed in browsers/feed readers that don’t show a playback speed toggle by default. It’s probably a little janky, and may not work in RSS feeds. My apologies.</p>  <p>Unsure of whether or not this was just my implementation, I tried to track down other examples in which this issue occurs. I was able to reproduce this in <a href="https://astro-records.pages.dev/">Astro’s demo for View Transitions</a> as well. Given that Astro got a nice little shoutout on the <a href="https://developer.chrome.com/blog/astro-view-transitions/">Chrome Developer blog</a>, I presume it meant someone from Chrome did take a look at the implementation at one point, so seemed to point something to do with the browser rather than developer error.</p>  <p>If you couldn’t spot the problem, here’s a static screenshot of the demo at the exact point of failure as well:</p><figure><img alt="An album playing interface with an audio player stuck to the bottom of the viewport. There is a Franz Ferdinand cover overlaying part of the audio player." loading="lazy" decoding="async" sizes="(min-width: 1090px) 1090px, 100vw" style="object-fit:cover;max-width:1090px;max-height:830px;aspect-ratio:1.3132530120481927;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_487,c_lfill,f_auto/v1698457282/56 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_571,c_lfill,f_auto/v1698457282/56 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_630,c_lfill,f_auto/v1698457282/56 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_731,c_lfill,f_auto/v1698457282/56 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_822,c_lfill,f_auto/v1698457282/56 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1090,h_830,c_lfill,f_auto/v1698457282/56 1090w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_975,c_lfill,f_auto/v1698457282/56 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1270,c_lfill,f_auto/v1698457282/56 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1462,c_lfill,f_auto/v1698457282/56 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1559,c_lfill,f_auto/v1698457282/56 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2180,h_1660,c_lfill,f_auto/v1698457282/56 2180w" src="https://res.cloudinary.com/nicchan/image/upload/w_1090,h_830,c_lfill,f_auto/v1698457282/56"><figcaption><p>My computer really wants to load the Franz Ferdinand cover first.</p></figcaption></figure><p>I sent out a few feelers in the ever-helpful <a href="https://shoptalkshow.com/">Shoptalk Show Discord</a> and tried various things with no success, such as adjusting the z-index, transform and will-change properties on the animating elements, but without any success whatsoever. I ended up filing a <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1496143">bug report for Chromium</a>, where the I was directed to this <a href="https://github.com/w3c/csswg-drafts/issues/8941">Github issue</a> with a detailed explanation from Khushal Sagar, a Chromium engineer, which is worth checking out.</p></section>
<section><h2 id="the-workaround">The Workaround</h2><p>Basically, here’s my understanding of the issue, from a non-browser developer perspective. View transitions aren’t actually moving your elements around in the DOM, the browser is doing secret work to take a snapshot of what the elements would look like as a flat raster image, and doing Animorphs Magic (Thanks Dave!) to morph between these images.</p><p>Paint order, the order in which your elements are painted, cannot be easily calculated without applying a complex algorithm, especially since many uses of View Transitions involve transitioning between pages. There isn’t really a way to figure out the shared stacking context between two pages, because if we had to consider the z-index of every page simultaneously to account for every possible transition, our heads/computers might explode. The compromise that exists is that the browser tries to paint the elements in the following order (paraphrased from Khushal):</p><ol>
<li>Paint all the view-transition-names in the old DOM, while respecting the existing order of these elements</li>
<li>Paint all the view-transition-names that exist only/are visible in the new DOM, while respecting the existing order of these elements. This goes on top of all the other things above.</li>
</ol><p>You will note here that elements without a view-transition-name are not included in this paint order. I didn’t have a transition name on my footer, as I had only worked on a single page, and footer had no animation at all. So, in order for my footer to be included in this paint order within this animation, I just had to stick a <code>view-transition-name</code> property onto the footer, even though it’s not actually being transitioned.</p><p>If your use-case is slightly different and you are transitioning between elements that don’t exist, whether it be due to an element being hidden with <code>display: none</code> or transitioning between pages with unique elements such as in the Astro demo, you will want to make sure that:</p><ol>
<li>Your problematic sticky element has a <code>view-transition-name</code> property on it.</li>
<li>You are rearranging the order of the painted element with z-index, to make sure it appears on top of all your other animated groups.</li>
</ol><p>This results in code that looks like this:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">.myFixedFooter</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	position:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> fixed</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	view-transition-name</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> footer;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">::view-transition-group(</span><span style="color:#4075BA;--shiki-dark:#B993FF">footer</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	z-index:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 100</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>Initially, I was confused as to why things weren’t working in the way that I expected. It felt not intuitive to apply a transition name to something I wasn’t transitioning, and I wasn’t sure why my z-index outside of the transition had no effect on the transition!</p><p>However, after reading through the explanation and finding the workaround, I’m personally okay with this limitation for my use case. Z-index + transitions has always been slightly iffy, and in this case, I’m just really glad there’s a workaround.</p></section>]]></content>
        <published>2023-10-28T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building my new website: Windows and View Transitions]]></title>
        <id>https://www.nicchan.me/blog/building-my-new-website-part-2/</id>
        <link href="https://www.nicchan.me/blog/building-my-new-website-part-2/"/>
        <updated>2023-10-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A look at exactly how much existential despair goes into the process of building a website.]]></summary>
        <content type="html"><![CDATA[<p>This is a post in a series about building my new website, check out <a href="https://www.nicchan.me/blog/building-my-new-website-part-1/">the previous post in this series.</a></p>
<section><h2 id="day-3">Day 3</h2><img alt="The homepage, now in dark mode. There's some more copy there, and an early version of the close icon for each window." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1698224962/personal-4 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1698224962/personal-4 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1698224962/personal-4 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1698224962/personal-4 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1698224962/personal-4 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1698224962/personal-4 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1698224962/personal-4 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1698224962/personal-4 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1698224962/personal-4 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1698224962/personal-4 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1698224962/personal-4"><p>Feeling ambitious, I write a Svelte store that allows me to manage the minimized/maximized state of all the windows. Unfortunately, I notice that the order in which the window ‘registration’ occurs is random, and I’d like it to reflect the order on the page, so I spend some time trying to coax it into order while cursing my existence. I end up with a workable solution that doesn’t take too long, as I’m personally ok if the window list at the bottom of the page comes in after JavaScript renders as it’s mainly for ‘flavor’. If it was a part of the main content, I probably wouldn’t accept this tradeoff.</p><p>Deciding that I’m done with JavaScript for the day, I move on to some of the design aspects. I first establish the kind of button style I want for the UI buttons, which requires attempting to rewrite the <a href="https://nigelotoole.github.io/pixel-borders/">pixel-borders</a> mixin. I successfully refactor the mixin to use relative units, so that it scales nicely with the rest of my UI, but I cannot get it to take my CSS variables as a color input. This is because the mixin uses SVGs within a CSS url attribute, and these are treated as as external documents. I experiment with using mask borders instead of border images, but the browser support is pretty bad. Defeated, I accept that my design system will no longer be ‘perfect’, and accept that I have to pass in a hex code for my borders.</p><p>I throw together a list of client logos and hope no one gets too mad at me for ruining their brand. I’m going for the crunchy 88*31 blinkie look, though I decide to increase the size for legibility. Think geocities, but purple.</p><p>I hastily scribble together a close icon (a little ‘x’) to see what it looks like in context. I fall down a bit of a rabbit hole, making many different versions of the icons across different sizes and resolutions. My normal SVG workflow is to run them through <a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a>, but this has a disastrous effect on the pixel art style. The default export from the spriting program I’m using draws individual rect elements for every pixel. As you can imagine, this doesn’t generate the smallest file size, but preserves the pixel art as one would expect. SVGOMG converts the rects within the svgs into a path, which smooths the whole thing out. While this behavior is normally desirable, this is exactly what I don’t want for this project.</p><p>Through my experimentation, I discover the <code>image-rendering: pixelated</code> property does exactly what I want it to as well! I now have a suitable method for rendering both SVG icons and raster images! I can pick and choose based on the file size, since the SVG exports won’t be as tiny as they normally are. Chuffed with my progress, I leave it here for today.</p></section>
<section><h2 id="day-4">Day 4</h2><img alt="The homepage now has a fully illustrated backdrop, a purple-y city sunset. There is also a classic 'My Computer' icon, except it features my ergonomic keyboard instead of a standard keyboard." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1698224962/personal-5 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1698224962/personal-5 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1698224962/personal-5 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1698224962/personal-5 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1698224962/personal-5 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1698224962/personal-5 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1698224962/personal-5 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1698224962/personal-5 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1698224962/personal-5 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1698224962/personal-5 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1698224962/personal-5"><p>Day 3 and day 4 happen with a <strong>huge</strong> gap between them. This is because I feel the need to flesh out the illustration work a lot more before moving forward. I’ve been working on a client project where illustrations were involved, and it was difficult to progress with the final design work until the illustration style was fully locked in.</p><p>In my case, this meant working on at least one of the ‘desktop icons’ as well as the background. This may lead you to think I spent a whole bunch of time painstakingly crafting the illustrations, but actually I just procrastinated until I felt too much guilt and finally opened up <a href="https://www.aseprite.org/">Aseprite</a>, the new pixel art editor I bought just to make these illustrations. Sometimes, I find myself needing to invest in something that will help me get stuff done as a motivational tool.</p><p>I’m not entirely happy with either the background illustration or the icon, but now that I have a sense of direction, it helps me out quite a bit. I adjust some colors to make sure they meet contrast requirements, something that was hard to do without having a background in place. Right now, I’m dumping all my images in the repository instead of Cloudinary, because I can’t make any decisions. Much to my chagrin and embarrassment, I’m up to 8 versions of the close button alone, and 5 versions of the background. I hate to say it, but I’m up there with the most indecisive of clients.</p><p>The particularly observant of you may note that although I have 8 versions of the close button, but don’t have any close buttons at all in the screenshot above. Fear not, they will return again, on another page! I think it doesn’t make sense to have the windows be closeable on the homepage, so yeah.</p><p>I did a lot of work developing the window component and how to make it usable for various different contexts, like what happens if there’s just one content window vs. many, how they stack on top of each other and etc. This didn’t result in a lot of ‘visible’ changes, but it helped me feel more confident in the overall direction of the project. If I know the fundamentals work, I hopefully won’t reach a scenario where the entire UI idea falls apart. Hopefully.</p><p>I spent a long time playing around with the new View Transition API to animate how the windows would maximize and minimize. For the most part, it looks really slick when it comes to moving things around, even with the defaults. The one animation I don’t like is the maximizing window option. I was expecting the text to reflow like how the browser does it, but since learning that the API basically transitions static images, it meant the text scaled up statically, then janked back into place, and there would be no way around this.. I opted for the default cross-fade animation for maximizing instead. I know there are ways to make this work with animation libraries, but I didn’t want that kind of overhead.</p><p>Writing that last paragraph sent into a small existential crisis as I contemplated if Svelte was the right technology to use. I like Svelte and enjoy building with it, but I wonder if I would have been better served by making the Window svelte component a web component. Putting your money where your mouth is, as one might say. The premise seems simple, but I don’t feel too optimistic, as there is quite a bit of state that needs to be managed externally (ie. the footer windows), and I do need to reach out of the component to calculate some sizing and positioning attributes. I tell myself it was the right decision to build it as a Svelte component, <del>and cry myself to sleep.</del></p><p><a href="https://www.nicchan.me/blog/building-my-new-website-part-3/">Read part three</a></p></section>]]></content>
        <published>2023-10-25T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building my new website: Ideation]]></title>
        <id>https://www.nicchan.me/blog/building-my-new-website-part-1/</id>
        <link href="https://www.nicchan.me/blog/building-my-new-website-part-1/"/>
        <updated>2023-09-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Warning: it gets messy!]]></summary>
        <content type="html"><![CDATA[<p>I’ve been getting the itch to redo my site for a while now. There’s nothing wrong with my current site per se, but I felt like my abilities have levelled up a bit beyond what is on display. Additionally, the thought of writing new case studies when I wasn’t going to be fully happy with the outcome proved to be a task so daunting that I never got around to writing them. Whoops. Finally, a lot of the copy and design aspects of my current site were created when I was a bit more aimless in my freelance career. Now that I have a stronger sense of what I’d like to focus on, it seemed like a good time to refresh both the brand and the copy.</p>
<p>I know I wanted to do something with a lot more fun and flair, while still remaining accessible. I know a lot of designers feel ‘constrained’ by thinking accessibility, so I wanted to build something that showed that you can still build something fun and beautiful while thinking about accessibility. I have a deep love for pixel art and retro games, and felt with the View Transitions API coming out, I could experiment with building some kind of operating system like interface for my new site.</p>
<p>Folks have occasionally asked me to share my work process, but I typically work on sensitive projects that I can’t share too many details on. Writing about this process as I go about it provides a rare opportunity to share a little about what I’m doing for once, so I hope you enjoy! Feel free to send me a <a href="https://indieweb.social/@NicMakesStuff">toot on Mastodon</a> or an email if you enjoy this series so I know whether or not I should keep writing these.</p>
<section><h2 id="non-development-work">Non-development work</h2><p>I am not a designer, and I have limited ability to use any design software to produce designs. When I am in a position where I do have to design, I tend to come up with stuff more iteratively. I typically tend to only nail down the broader stroke stuff first like colors and type, then I refine as I go on. I looked at pixel style video games, ‘vaporwave’ UI designs and the good ol’ Windows 95 UI to in order to get a sense of overall design language.</p><p>Because I liked my current site colors, I grabbed all my primary brand colors and shoved them into <a href="https://hihayk.github.io/scale/#2/2/50/80/94/67/20/14/a75293/167/82/147/white">this color scale generator</a>. I tinkered with it to make 2 lighter and darker tints/shades for each primary color I had, then I plugged everything into my all-time favorite, <a href="https://www.aremycolorsaccessible.com/palette">Are My Colors Accessible</a> to make this monster color chart that I could reference to find accessible color combinations.</p><img alt="A chart where every single possible combination is overlaid on top of each other, with the word 'Yup' or 'Nope' if the combination is accessible." loading="lazy" decoding="async" sizes="(min-width: 980px) 980px, 100vw" style="object-fit:cover;max-width:980px;max-height:703px;aspect-ratio:1.3940256045519204;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_459,c_lfill,f_auto/v1698225054/color-wheel 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_538,c_lfill,f_auto/v1698225054/color-wheel 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_594,c_lfill,f_auto/v1698225054/color-wheel 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_689,c_lfill,f_auto/v1698225054/color-wheel 960w,
https://res.cloudinary.com/nicchan/image/upload/w_980,h_703,c_lfill,f_auto/v1698225054/color-wheel 980w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_775,c_lfill,f_auto/v1698225054/color-wheel 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_918,c_lfill,f_auto/v1698225054/color-wheel 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1197,c_lfill,f_auto/v1698225054/color-wheel 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1377,c_lfill,f_auto/v1698225054/color-wheel 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_1960,h_1406,c_lfill,f_auto/v1698225054/color-wheel 1960w" src="https://res.cloudinary.com/nicchan/image/upload/w_980,h_703,c_lfill,f_auto/v1698225054/color-wheel"><p>I did a few font tryouts but settled on <a href="https://fontsarena.com/w95fa-by-alina-sava/">W95FA</a> as my main font. I liked how legible it is compared to other pixel fonts, but I also knew that I probably needed to make a toggle to allow for a high-res font in case anyone struggles with reading it. After perusing <a href="https://modernfontstacks.com/">Modern Font Stacks</a>, I settled on the ‘Humanist’ font stack as a nice fallback font stack.</p><p>I consulted the hivemind over in the <a href="https://array.chat/">Array Discord</a> on sites with great about/services page, since that was an area of my site I felt was particularly lacking. Special shoutout to <a href="https://stevenwoodson.com/">Steven Woodson</a> who gave me a list of references he consulted when he went through the process of rebranding recently. Having some sense of what the copy on each page looked like made it much easier to wireframe, I’m very much a fan of designing the site to fit the content you have and not the other way around. I ended up scribbling some very loose wireframes with good ol’ pen and paper.</p><p>Finally, onto the actual development!</p></section>
<section><h2 id="day-1-panic-in-my-office">Day 1: Panic, in my office</h2><img alt="Very barebones layout, there are some placeholder navigation icons, an unstyled start bar and a window with some homepage text." loading="lazy" decoding="async" sizes="(min-width: 704px) 704px, 100vw" style="object-fit:cover;max-width:704px;max-height:540px;aspect-ratio:1.3037037037037038;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_491,c_lfill,f_auto/v1694838055/personal-1 640w,
https://res.cloudinary.com/nicchan/image/upload/w_704,h_540,c_lfill,f_auto/v1694838055/personal-1 704w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_575,c_lfill,f_auto/v1694838055/personal-1 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_635,c_lfill,f_auto/v1694838055/personal-1 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_736,c_lfill,f_auto/v1694838055/personal-1 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_828,c_lfill,f_auto/v1694838055/personal-1 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_982,c_lfill,f_auto/v1694838055/personal-1 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1408,h_1080,c_lfill,f_auto/v1694838055/personal-1 1408w" src="https://res.cloudinary.com/nicchan/image/upload/w_704,h_540,c_lfill,f_auto/v1694838055/personal-1"><ul>
<li>Decided to stream for my mentees</li>
<li>I had been struggling to choose between using 11ty (what my site uses currently) and Astro (a tool I’d like to play around more with. I decide on Astro as I think this iteration of the portfolio will be a little more JS heavy and I wanted to use Svelte for the component-y bits.</li>
<li>Grace showed up to watch me stream! I panicked as I soon realized that I cannot talk and problem solve at the same time. I tried to get around this by only coding the most absolute basic markup elements while discussing CSS with Grace.</li>
<li>I contemplated using SCSS or not, but ultimately decided on including it, because the Dark mode toggle pattern I use 1. relies heavily on CSS Variables 2. respects the OS preference for the color scheme but allows for overriding it via a control on the website. I do NOT want to have to copy and paste the variables in two places, as I know this will only bring me pain. For this alone, I decide it’s worth it to include SCSS.</li>
</ul><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="scss"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@mixin</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> dark-colors</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	// all my variables here</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@media</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> (prefers-color-scheme</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> dark</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">	:root:not</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">([</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">data-user-color-scheme</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">]) {</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">		@include</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> dark-colors</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	}</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">[</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">data-user-color-scheme=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">dark</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">] {</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">	@include</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> dark-colors</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><ul>
<li>My dear friend <a href="https://www.ejmason.com/">EJ Mason</a> came to my rescue and helped me answer some tough questions about CSS architecture. We vent about comboboxes. Grace listens to us very patiently.</li>
<li>We soon realized that with this current design, when you are on the home page there are actually 3 links that point to ‘Home’. I contemplate making the bottom left ‘Home’ button say ‘Start’, but EJ correctly points out this is a WCAG violation. For now, I make the second ‘Home’ in the bottom bar just text, and decide to solve this later.</li>
<li>Copilot offers me a suggestion to generate the JS and markup for the Minimize, Maximize and Close buttons. I accept this suggestion to save myself the pain of typing it out. EJ and I realize that while the JS for the buttons is functionally correct, the HTML for the buttons is all unlabelled divs. We lament the state of AI in modern web development.</li>
<li>I decide to incorporate this <a href="https://nigelotoole.github.io/pixel-borders/">pixel borders SCSS mixin</a> as it looks awesome and justifies my use of SCSS, but realize it only uses pixels instead of rems and does not take CSS variables. I don’t think the use of pixels is ultimately terrible as the borders not resizing with font sizes isn’t the end of the world, but I would like to see if I can rewrite it better for my own modern workflow later. I include it as a utility class for now with the intention of rewriting it.</li>
</ul></section>
<section><h2 id="day-2-looking-at-the-view-transitions-api">Day 2: Looking at the View Transitions API</h2><div style="display: flex; gap: 1rem"><figure style="flex: calc(1024 / 768)"><img alt="Our layout now has a big heading that says 'Home', and two windows of placeholder content. Our two windows are placed in an offset way to give the feeling of a messy desktop." loading="lazy" decoding="async" sizes="(min-width: 1024px) 1024px, 100vw" style="object-fit:cover;max-width:1024px;max-height:768px;aspect-ratio:1.3333333333333333;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_480,c_lfill,f_auto/v1694838056/personal-2 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_563,c_lfill,f_auto/v1694838056/personal-2 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_621,c_lfill,f_auto/v1694838056/personal-2 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_720,c_lfill,f_auto/v1694838056/personal-2 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1694838056/personal-2 1024w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_810,c_lfill,f_auto/v1694838056/personal-2 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_960,c_lfill,f_auto/v1694838056/personal-2 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1251,c_lfill,f_auto/v1694838056/personal-2 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1440,c_lfill,f_auto/v1694838056/personal-2 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1536,c_lfill,f_auto/v1694838056/personal-2 2048w" src="https://res.cloudinary.com/nicchan/image/upload/w_1024,h_768,c_lfill,f_auto/v1694838056/personal-2"><figcaption>Desktop progress</figcaption></figure><figure style="flex: calc(416 / 768)"><img alt="The same layout on mobile, where our two windows are stacked vertically." loading="lazy" decoding="async" sizes="(min-width: 416px) 416px, 100vw" style="object-fit:cover;max-width:416px;max-height:768px;aspect-ratio:0.5416666666666666;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_416,h_768,c_lfill,f_auto/v1694838056/personal-3 416w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_1182,c_lfill,f_auto/v1694838056/personal-3 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_1385,c_lfill,f_auto/v1694838056/personal-3 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_1529,c_lfill,f_auto/v1694838056/personal-3 828w,
https://res.cloudinary.com/nicchan/image/upload/w_832,h_1536,c_lfill,f_auto/v1694838056/personal-3 832w" src="https://res.cloudinary.com/nicchan/image/upload/w_416,h_768,c_lfill,f_auto/v1694838056/personal-3"><figcaption>Mobile progress</figcaption></figure></div><ul>
<li>I promised all my friends banana bread today but realized I actually tossed my banana bread pan, so I need to go buy a new one. Unfortunately, the kitchen supplies store was closed so I had a few weekend hours to kill. Back to the website it is!</li>
<li>I wanted to make it so that when only the screen is large enough (to account for high zoom levels), the footer and sidebar icons remain sticky, and the content of my little window panes scroll within the allocated space remaining. However, CSS Grid is NOT cooperating with me and despite setting <code>max-height: 100dvh</code> on my grid container, the main content grid item decides to bust out of it. I spend an hour or two pouring through stack overflow until I found <a href="https://stackoverflow.com/questions/43311943/prevent-content-from-expanding-grid-items">this answer</a>, which informed me that grid items have an implicit <code>min-height: auto</code> on it. I set <code>min-height: 0</code> on the main window pane area and the layout snapped into place as it should. Who knew!</li>
<li>I contemplated stuffing each page’s content into one giant window vs. creating little windows, and even contemplated the dreaded possibility of having different windows for small screens and large screens. Decided that multiple little windows make for good sectioning elements and that it would be fun to fill the horizontal space on larger screens. I made a note to write the rest of my homepage copy with urgency.</li>
<li>Came up with a little way to pass in different positioning props into the windows so I could arrange them nicely on desktop for each page. I get the window ‘maximize’ functionality working without a hitch.</li>
<li>I thought the default View Transitions way of cross fade-ing the windows looked pretty cool. Tried to come up with a better custom transition that scales the window size, but ultimately returned to the cross fade for now as I noticed that the text size scales up with the animation in a weird way.</li>
<li>I realized that I have inadvertently solved the start bar ‘Home’ design problem by breaking up each pages content into multiple windows as I can just have the list of windows titles, but this creates a lot of extra work if I want to support ‘minimizing’ a window. I wanted this to be possible as the original plan was to create some absolutely gorgeous pixel artwork for the background that hopefully people wanted to look at. Implementing this as planned meant creating some kind of global window store object that tracks the state of each window. I also agonized over ‘Close’ button, which I was going to treat as a ‘back button’, where closing the window would return to either the Home page or the parent page (ie. all blog posts if you are on a blog page, etc.) I then wondered if that’s going to be confusing UX, and realized I had no idea what to do with the ‘Close’ buttons on the homepage. Finally, in classic anxious form, I contemplated omitting this section completely as it was likely to be too confusing to read, but settled keeping it as sometimes Web Sites Are Pain and this log should reflect that.</li>
<li>Wanted to create a live link somewhere to gather some inital feedback, but realized I broke the default Astro blog template somewhere along the way.</li>
<li>I decide all of this is just way too much for a Saturday morning and notice that the kitchen goods supplies store is now open, time to make banana bread!</li>
</ul><p><a href="https://www.nicchan.me/blog/building-my-new-website-part-2/">Read part two</a></p></section>]]></content>
        <published>2023-09-16T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Talk: Developing an Accessible 'Add to cart' Flow ]]></title>
        <id>https://www.nicchan.me/blog/talk-developing-an-accessible-add-to-cart-flow/</id>
        <link href="https://www.nicchan.me/blog/talk-developing-an-accessible-add-to-cart-flow/"/>
        <updated>2023-09-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A talk I gave at the W3C/OGC Maps for the Web Workshop on map accessibility.]]></summary>
        <content type="html"><![CDATA[<p>I was excited to see my axe-con 2022 talk was posted on Youtube! There are captions in the video and transcript side by side, as well as below the video. Thank you Deque for allowing me to speak.</p>
<p><a href="https://add-to-cart-slides.netlify.app/">Standalone Slides</a></p>
 
<section><h2 id="introduction-000">Introduction (0:00)</h2><p>Hi, everyone, my name is Nic Chan and today I’ll be speaking to you on “Developing an Accessible ‘Add to Cart’ Flow.” If you have any questions or comments, you can find me on my website at <a href="http://www.nicchan.me">www.nicchan.me</a>. That’s spelt N-I-C, C-H-A-N, or on my Twitter handle, @NicMakesStuff. I’m a Hong Kong woman with wavy black hair, and I’m wearing a purple an Axe-con shirt much like Liz. To give you a brief introduction, I’m a freelance front-end developer. And the insights from this talk are sort of gleaned from my years of experience working on Shopify stores. But hopefully the insights can also be applied to other e-commerce platforms. And fun fact, I’m definitely a cat person.</p></section>
<section><h2 id="overview-053">Overview (0:53)</h2><p>To give you a sense of how this talk is gonna go, today, I’ll be covering four different patterns, first being buttons, then inline notifications, then toasts, and then dialogs. And for each pattern, I’m gonna walk you through some different iterations, so some different variations of what these patterns might look like in the wild. And then pros and cons for each pattern, as well as tips and tricks for implementing them in a more accessible way.</p><p>So to make sure we’re all on the same page, here’s a typical Product page. I’m sure many of you have seen some variation of this in the wild before. On top, we have our breadcrumbs, which is our links that point to different parts of the website. Underneath that we have our slide images, and some thumbnail images. On the right, we have a product title and a product price, we have some variants for selecting a different color. And below that we have our star of today’s show, it’s the Add to Cart button. Underneath that we have a product description. There’s lots of things to comment on this with regards to accessibility but for now, we’re just gonna be focusing on what happens after that Add to Cart button.</p><p>So the first thing we wanna do is make sure that we’re always using a form element. The reason why we would wanna do this is for two main reasons: progressive enhancement and semantics. There’s this common misconception that screen reader users can’t use JavaScript, but that’s just a myth, it’s not really relevant. The main reasons are for progressive enhancement and semantics. Progressive enhancement being the idea that we want our site to work for as many users as possible. So if for some reason the JavaScript fails, we’d still want our key actions to be able to be performed by our user and that includes our Add to Cart. In terms of semantics, assistive technologies offer the functionality to jump to things like different elements on the page, including form elements.</p><p>So by including a proper form element here, we can provide users that functionality. It’s much like a table of contents in a book. So if someone wanted to skip to a particular point, they can just browse that table of contents, and then jump to what they need instead of just having to listen to the entire page before reaching that form. So what happens with this default form example?
Well, we can see that the form element has an action attribute on it, which points to a /cart/add route.And when you hit that Submit button, it’ll take you to that /add route and it’ll add that item to your cart and then presumably, it’s gonna redirect you to a cart page. This solves a whole bunch of accessibility issues for us. Because navigating to a new page is a fundamental interaction of the web, almost all assistive technologies support that
and your users understand that, okay, like we’re on a new page, it’s titled your cart, and I can see that the item is now in my cart, and I understand what has just happened here.</p><p>So if this approach is so robust, why would we ever wanna use an alternative? Merchants realized that this was kind of like pushing users into a single flow, it’s forcing them to Checkout when they might not wanna always go to checkout, they might wanna continue shopping on the site or do some other action first. So the following patterns are kind of developed as a way to both notify users and offer them a choice of whether or not to continue shopping or proceed to checkout.
Now that we’re all on the same page, let’s move into the actual patterns.</p></section>
<section><h2 id="button-pattern-502">Button Pattern (5:02)</h2><p>The first pattern is a button pattern. And here I have an image. And on top, we have our first button, and it just says Add to Cart. Underneath that we have a second button, and there’s a checkmark in it, and it says Added to Cart instead of Add to Cart. When I refer to the button pattern, I’m referring to when the buttons text is changed when it is activated. This is a pattern that can be used in combination with other patterns, and I’d recommend it for the following reasons, and that is super great for users who use magnification, whether it be the default browser zoom, or special zooming software.</p><p>So here I have a screenshot of a user who is using ZoomText to browse our page. And on this, they can only see a small region of the page,
they can only see that price, and then the color variant, our Add to Cart button, and then one or two lines of description, so they only see a pretty small region. It’s really good to have an indication of the state of the page in the place that they’re currently looking at. So if we use the button pattern it might look something like this.</p><p>And here we can see that very same region of the page and our Add to Cart button now says ‘Adding’. So moving on to implementation details. This type of pattern is typically implemented by using JavaScript to change the text.</p><p>So here we have, and we have our form element. And then below that we have some JavaScript where we’re just adding the item to the cart, and then we’re changing the inner text of that button to say ‘Adding’. In isolation, this isn’t a recommended approach because screen reader users aren’t that great at handling text changes consistently. So while some screen readers may support changing the inner text of a button that’s focused, a lot of screen readers don’t. So we can consider using live regions in order to provide better support for screen reader users. If the button is your only indication of state, you definitely want to be using live regions.</p><p>Live regions are a way to announce changes in your site to assistive technology users. You can kind of think of them as a web equivalent of the inflight announcement on a plane, like you’re just watching a movie, you’re having a good time, and then suddenly, the captain’s voice just interrupts and tells you that you’re about to land. That’s kind of like what a live region is. There are only for text only announcements, you can’t really do anything with them. Live regions can be created by adding the aria-live attribute with a value of either polite or assertive. By using polite, you’re telling the assistive technology for the current text to be finished before announcing what is in the live region whereas if you use assertive, it’ll just interrupt the user, no matter what they’re listening to. Live regions work most consistently when they’re already present in the DOM. You don’t wanna be adding them dynamically using JavaScript, you typically wanna have a live region already on your page, and then use JavaScript to add or remove text to it whenever relevant.</p><p>So here we have our code sample. And we have our form element. And below that we already have a live region with the attribute aria-live=“polite.” And below that we have our JavaScript. When we add the item to the cart, we’re grabbing that live region and we’re grabbing that button. And we’re changing the text of both of these elements to be the same thing. Sometimes developers might wanna disable that Add to Cart button while the action is being assessed in order to prevent double submission. So sometimes users accidentally click it,
click the Add to Cart button twice, and then you have two items in your cart instead of one. And developers often use the disabled attribute to kind of prevent this. Using disabled isn’t always the best choice because if you’re not managing the focus correctly after,
you can cause the user to lose focus, and then lose keyboard focus.</p><p>So an alternative might be to use the aria-disabled=“true” attribute instead, and use some additional code and styling to make sure that the same functionality is provided. But using aria-disabled might really not be any better because what can be announced to screen reader users can sometimes be confusing. For example, a screen reader might say something like Add to Cart unavailable. And that’s especially confusing in an e-commerce context because, you know, how will the user know if the button is unavailable, or the product that they’re trying to buy is unavailable.</p><p>So in this case, you would really wanna consider your user base. If your website is selling to a technical audience, maybe your audience will have a higher level of technical proficiency and they’ll understand that this is actually unavailable as being applied to the button and not the product, and it will be okay. But you’d really wanna consider testing with users whether or not the disabled attribute is even needed or if you should just disable the click with JavaScript.</p><p>One final consideration for the Add to Cart button is wording. So here we have a diagram of two different possible wording flows. On the left, we have a button that says Add to Cart, and then it says Adding, and then it reverts back to Add to Cart. This can be confusing if it’s the only indication of state because it requires users to be very actively paying attention to what might be a small difference. You’d have to imagine your user just staring at that region of the screen. And if your website loads fast as hopefully it should do, it’s gonna revert very quickly back to the original Add to Cart state. And if that’s your only indication, it’s not too clear that anything has happened.</p><p>On the right, we have a button that says Add to Cart and then Adding and then the final state is Added to Cart. This poses a different problem of if you want to reset that button to the original state. There tends to be room for confusion about now, it’s pretty clear that an Add to Cart button allows you to add something to your cart but what does an Added to Cart button do? Does it take you to Checkout? Does it let you add that item again? It’s a mystery. So we might wanna consider using the button pattern in combination with other patterns in order to alleviate some confusion. And this leads us to our next pattern.</p></section>
<section><h2 id="inline-notifications-1234">Inline notifications (12:34)</h2><p>What do I mean when I refer to the inline notification pattern? By inline notification, I mean an element that is not positioned sticky, absolute or fixed. A notification is inserted into the reading area and then content is often shifted in order to make space for this notification.</p><p>So here we have an image. It’s our same Product page as before and this time, underneath the Add to Cart button, we have a notification that lets us know that our product has been added to the cart. So we’ve already discussed the idea of using live regions as a way of presenting content to assistive technologies, should we be using them here? Live regions can best be thought of as temporary notifications.They’re the web equivalent of an announcement blaring at you over the loudspeaker. If you’re not in a logical place, like how I’ve placed it right under the Add to Cart button here, it might not be obvious where in the DOM they are, and a screen reader user has no option to go back and listen to that text again. Furthermore, zoom magnification users who don’t use accompanying audio could be left out and they could miss this notification entirely.</p><p>A better approach would be to programmatically move the focus to the notification once it enters the DOM. And then this will be announced to screen reader users and other groups. So here we have our code samples. And we have a notification that has a tabindex of negative one,which lets the browser know that this element will be programmatically focused, but not included in the default focus order. And below that, we have our JavaScript and we’re once again we’re just adding that item to the cart and then we are grabbing our notification, the text of that notification to what we want it to be and then we’re moving focus.</p><p>Sometimes our notification isn’t just text, but it also includes interactive elements. So here we have the same image as before with our Add to Cart button and then below that is our notification. This time, I’ve added two links to this notification. So underneath that notification text, there’s a link that says Continue Shopping. In these situations, we have to move focus, because our aria-live regions strip semantics of child elements. So a screen reader user might be able to tell that these are actions from the words, but they’re gonna hear the words Continue Shopping or View Cart that has interacting with them.</p><p>Some implementations will have this notification disappear after a short amount of time. While this is not advisable due to being easily missable, it’s also a WCAG failure of the 2.2.1 Timing Adjustable Criteria. Essentially, this criterion exists to make sure that we’re allowing people to have adequate time to complete interactions. Because we’re providing different interactions within the notification, hiding the notification automatically can prevent users from completing them. So a better way around this would be to add a button to dismiss the notification manually instead.</p><p>In the wild, I often see different placement options for this pattern. So here we have two examples of where we might put our notifications. And the first one is directly below our breadcrumbs, but above all of our product stuff. And the second one is in a similar location to the original notification, but it’s directly above the Add to Cart button. I’d argue that the best place for the notification is after the Add to Cart button. Well, if we’re moving focus, technically, we meet the requirements either way, but we can improve usability by not taking users too far out of the context they were originally in. Placing it near the button helps minimize tab stops for keyboard users and it will disorient users with dramatic content shifts if you’re kind of like shifting things around the page. If we put it under the button that they’re probably looking at, we have less room for disorientation.</p></section>
<section><h2 id="inline-notifications-1717">Inline notifications (17:17)</h2><p>The next two patterns I will be discussing are toasts and Dialogs. Before I get into the details, I’d like to emphasize that these patterns often lack distinctions in the wild. I’ll be describing the behaviors of both toasts and Dialogs in terms of how they should be behaved, but the lines are really blurred. Because they pop out on the screen and overlay content, people kind of just tend to see a toast as a little dialog, and they code them in a really similar way. This ignores distinctions and semantics and the requirements for each of these elements to be accessible. A lot of accessibility errors come about from developers treating these two components as interchangeable when they’re actually quite discreet.</p><p>So what is a toast? A toast is named after the behavior of a toaster.It’s an element that kind of pops up in a fixed position on the screen, typically in the top right, bottom right,underneath the nav bar, and other places like that. And they should have a role=“status” or role=“alert” attribute on them, or an aria-live attribute so that they’re announced to assistive technology users. Focus typically isn’t moved to a toast. Contrary to how toasters work in real life, toasts on the web often disappear after a few seconds.</p><p>Should we be using toasts in an e-commerce context? A lot of the problems with toast stem from the fact that by definition, focus, isn’t moved to it. Furthermore, toast inherently have a problem shared with a lot of fixed position elements, they’re really easily missable by zoom users. You can imagine that if we’re zoomed into that Add to Cart button and something pops up in the top right of the page, it’s not that useful for a user who’s just looking at that Add to Cart button region. Here we have a visual and we have a example of a toast.</p><p>Our toast looks really similar to the inline notification that we have previously but it’s overlaying our content and it lives on the top right of our page, and it disappears after a few seconds. Early on, we discussed the 2.2.1 Timing Adjustable Criteria and the problem with child elements and this applies very much to this example right here. Trying to interact with a notification that disappears is about as frustrating as your toast vanishing off your plate while you try to eat it. Not recommended.</p><p>So how do we make sure that we don’t fail the 2.2.1 Timing Adjustable Criterion while keeping tools behavior? We have some options. The first is that we could have a setting that lets you set that notification display time. And here we have an example of a settings page where we just allow the user to set the notification to five seconds. On the right, we have an example of a notification drawer and it looks a lot like a notification jar on your phone. Basically, it shows you all the toasts that have popped up previously with a timestamp of how recently they popped up. Sure, we can use both of these options to kind of remediate the issues with toasts, but it sort of defeats the purpose of toasts as a quick and easy way of displaying things. Plus no one really wants a drawer full of stale toasts. Unfortunately, I don’t really think that toasts live up to their real life namesake. They’re neither as useful nor as delicious as their real world counterparts, especially in e-commerce contexts where the Add to Cart is the primary action. What developers often try to implement as toasts should really be dialogs instead. And this leads us to our final section.</p></section>
<section><h2 id="dialogs-2118">Dialogs (21:18)</h2><p>Dialogs. Dialogs are often a misunderstood component, there is a misconception that all dialogs are basically what we understand to be an alert. Here on the screen, I have an example of a Windows 98 Style dialog. And it basically says “The system registry has been backed up already today. Would you like to back it up again?” And it provides option of Yes or No. All of the following patterns are examples of things I regularly see on online stores. While visually they look really different, they can all be considered dialogs. And so they can use similar semantics and functionality underneath the visual styling.</p><p>This is a dialog. So what we have here is a Product page that we had before and we have something that looks really similar to our toast example. It has “Your product has been Added to Cart” text, and it has our to Continue Shopping or View Cart links. And the only difference is that we’ve now added a little Close button in the top right so a user can dismiss it.</p><p>This is also a dialog. So here we have a Products page and there’s a white semi-transparent overlay on top of that page. And on top of that, we have a kind of more expanded dialog. And we have our “Product has been Added to Cart” text, we have an image of product and then below that, we have our Continue Shopping and View Cart links styled more prominently. And there’s also a Close button here.</p><p>This is also a dialog. So here we have a Product page and the difference is now we have a navigation bar and in the top right we have a button that will trigger the cart, and below that, we have our dialog and it has a title of your cart and within it, it has all the products that are in our cart, the total price, and then the option to Checkout.</p><p>This is also a dialog. So here we have our Product page, we have our semi-transparent overlay overlaying the content. And on the right hand side of the page, we kind of have like a cart and floor type situation that slides out from the right. And we have a title of your cart, all our products, the price and the Checkout button. The primary difference between these dialogs is whether or not they’re modal or non-modal.</p><p>Non-modal dialogs don’t prevent interactions between the rest of the webpage when they’re open, but modal dialogs do. So if you have a backdrop behind your dialog, that probably would suggest that it is modal and your dialog should be coded in a modal way.</p><p>Now that we know that all these seemingly different patterns are actually just dialogs under the hood, I’m gonna give you some advice on implementation details.</p><p>It’s my opinion that if web development were a video game, hand rolling your own dialog would be the final boss. I’m not gonna go into all the intricacies of building an accessible dialog because it is super complex, but here are a few tricks that can help you when evaluating dialogs on your site and perhaps letting you know if your current solution is okay or if you need to find something else.</p><p>Focus management. So regardless of whether or not your dialog is modal or non-modal, we wanna be moving focus to the dialog. If your dialog is modal, you also want to trap focus, which means that you shouldn’t be able to move focus to the document that’s in the background. When the dialog is open, hitting Tab a whole bunch of times suggests cycle between the elements within the modal and then shift to the browser UI. This browser UI is often forgotten part of the focus trapping.</p><p>When we close the dialog, we should return the focus to the element that opened the dialog. In this case, it’s our friend the Add to Cart button. But if you’ve opted for a mini cart, or some other kind of cart thing that can be opened through another part of the page, you’ll probably wanna account for handling different mobile triggers and moving focus to the correct element that opened the modal in that scenario.</p><p>Keyboard. We don’t wanna forget our keyboard shortcuts. When you hit the Escape key, the dialog should close. If the dialog is modal, we wanna make sure that assistive technology users can’t escape from the modal via other means. So oftentimes, I find developers will correctly implement focus trapping, which traps focus within the dialog but you also need to make sure that the document behind the modal is hidden from users when the modal is open. You can consider using the aria-modal=“true” attribute in the future but the browser support is not that great right now. So in the meantime, you can probably consider using the inert attribute with a polyfill in order to make sure that the rest of the document is correctly hidden from assistive technology users.</p><p>And finally, we don’t wanna dismiss the dialog automatically.Well, the dialog element is just about here. I have to give a shout out to Safari,which just dropped an update, 15.4, like two days ago, which caused me to frantically update these slides. The dialog element is so close, it’s a great time to begin experimenting with deploying it in the future, but if you’re a developer who needs to support older browsers, you might still wanna wait and you can continue using a with a role=“dialog” until the browser support is just a little bit better.</p><p>Finally we wanna make sure that we’re labeling all the things. We wanna make sure we’re labeling the dialog itself. Consider using a aria-labelledby attribute, which will point to the ID of the element that labels the dialog. So it might be the text of the dialog or it’s a heading like your shopping cart, we just wanna make sure that our dialog is labeled.</p><p>It’s a really common issue that’s found in modal carts and things like that so if your dialog has repetitive interactive elements, so like multiple Increase Quantity buttons, or multiple Remove Product buttons, one for each of your products, make sure that they have unique names. It can be annoying for a screen reader user to just list all the buttons, and hear five different buttons that say Remove with no indication of what product they’re actually removing. So we wanna make sure that each of these buttons are labeled with a unique name.</p></section>
<section><h2 id="inline-notifications-2905">Inline notifications (29:05)</h2><p>Some final takeaways. I don’t think there’s a clear winner amongst the pattern. How you might use them really depends on the context of your site. Things like how many products you have, or what you want a user to do after adding an item to the cart, it sort of really drives which one of these patterns you’ll wanna use.</p><p>And while testing individual components is great, we really wanna test the whole flow. So you can see that when we combine the patterns in different ways, different considerations pop up. So it’s really necessary to not just individual components in our design system, but to test the entire flow to make sure that once you put it all together, it works in an accessible way. Finally, toasts and dialogs are not the same.</p><p>A whole bunch of accessibility errors come about from treating different components interchangeably. If you only learn one thing from this talk, let it be that toasts and dialogs are not the same. And with that, I’m pretty much done. So I guess I’ll hand it back to you Liz for questions.</p></section>
<section><h2 id="questions--answers-3014">Questions &amp; Answers (30:14)</h2><p>Liz: Awesome. Thank you so much Nic for a great presentation here. And thank you for adding questions for all of our attendees. Please still feel free to add any questions that you have for Nic in the Q&amp;A portion. And definitely Feel free to upload them and I’ll make sure to go with the ones that have the most votes. So let’s dive into the first question. This one’s on toasts. Should toasts be used for simple success confirmation messages that would have no interactions with it, such as Saved Successfully, Copy to Clipboard, et cetera?</p><p>Nic: Yeah, I think if there’s any place for toasts, it would be on those simple confirmation messages, as you’ve just said, especially if a user is gonna be triggering that action several times in a session. Like, if you are, you know, working in Google Docs or working in an art program, you wanna make sure your stuff is saved, and you probably save like 500 times an hour like I do, so in that case, so toast is probably appropriate.</p><p>Liz: Definitely. Okay, awesome. Next question here. Okay, this is on Add to Cart button. So could we use the Add to Cart button as a live region, instead of creating a live region and a separate div element?</p><p>Nic: I think that would conflict. I think it would cause a conflict because live regions, they will strip semantics from things. So because the button is a semantic element, we probably shouldn’t use a live region on it. I’m sure reading the spec of what it’s appropriate to add aria-live to will reveal whether or not it’s an actual spec incompatibility, or if it’s just something that we probably shouldn’t do, but I probably wouldn’t do it.</p><p>Liz: Okay. Great. Thank you, Nic. All right, got some more questions coming on here. Okay, what tools would you recommend someone use who is new to accessibility?</p><p>Nic: There are so many different tools for different things, I can’t really think of any e-commerce ones off the top of my head. I have written an article for Smashing Magazine that is just entirely a ginormous list of different accessibility tools, and they’re categorized by different uses. So for example, there’s like tools for reading level, which can help with cognitive accessibility, or tools for color so you might wanna check that out.</p><p>Liz: Awesome. Thank you. Okay, another one on toast messages. Should toast messages be removed automatically after a set time, or both
hide and remove it?</p><p>Nic: So if you add a button to a toast, it kind of becomes a dialog. And that’s part of where the issue is. Toast by definition should be removed after a set period of time, which makes them not great for a lot of things, but a true toast would just be removed after a short period of time.</p><p>Liz: Got it. Thank you. Okay. Somebody says, thank you for calling out including the browser UI in the focus trap. I hadn’t considered that. Do you know of any good resources for how to do that?</p><p>Nic: I’m not sure actually. I think some libraries will handle this and some won’t. But it would definitely be something that I would test for when looking at it. I can’t name any off the top of my head, I would wanna bet that Kitty Giraudel’s a11y-dialog Python dialog library should do this, but I haven’t looked at it in a while.</p><p>Liz: Awesome, thank you. Okay. There’s another one. You suggested moving focus to an announcement text. How does that work with the rule to not move focus around in response to user interaction?</p><p>Nic: I think because there are certain components that just sort of require you to move focus around, like there isn’t really an option to have a dialog and not move focus to it because then the user would basically just completely miss that the dialog is there. I do know which rule you’re talking about, I just I’m not entirely sure that applies in this particular context. I think you probably don’t wanna be moving it around willy nilly but because this is sort of like on other site, it’s not really acceptable that the user misses it. So we wanna make sure that when we do move it in a way that’s like respectful and like not intrusive, but I’m pretty sure that we should move focus. - Thank you.</p><p>Liz: All right and thank you everyone for these great questions. Let’s see here. Should we implement the Escape button to close toasts and notifications or only for modal dialogs?</p><p>Nic: That’s a good question. I think it could be a worthwhile enhancement but obviously, there could be a complicating factors within your design, that would make it not ideal, for example, we’ve talked just like a Product page. But for example, I’ve seen, like, quick shop modals where the Product page is in a modal. So if you have a modal open, you’d wanna like consider, what kind of our modals are we gonna close with Escape, and things like that. So like I hate to give you, it depends answer, but I think in this case, it does.</p><p>Liz: Makes sense. Okay, could you follow up with a little more about how aria-live strips semantics from elements?</p><p>Nic: Yeah. For example, if we’re looking at our notification, and if a screen reader user is just listening to it on the page, they’ll hear product is an Added to Cart link, Continue Shopping or link View Cart. So they’ll hear that they’re interactable elements within it, and they can choose to interact with them, but when you announce it, a live region will basically just ignore all the stuff that you have in there. So if you have a heading in there, if you have links or buttons in there, it’ll just read the inner text. So it’ll say product has been Added to Cart, View Cart or Continue Shopping, but then the user can’t really tell it’s a link, except for hearing the words that are kind of like actiony.</p><p>Liz: Right. Thank you. All right, this one is on modals. Do you ever use modals for checkout, such as pop up modal for login, et cetera,
or is that something that you would just discourage?</p><p>Nic: So I’ve largely worked with the Shopify platform, and they have, I think, just locked all of the checkout to their platform. So there’s basically no customization there. I do know that, like, one thing I really respect about the Shopify team is that they regularly have disabled users go and test all their flows. And they don’t include modals in their checkout flow as far as I know, but that doesn’t mean that you should never use it. I think, in their login situation, they will show a form in place, but they won’t show it in a modal.</p><p>Liz: Right. Thank you. Okay, what is one piece of practical advice you would give to someone starting out? I think starting out in accessibility.</p><p>Nic: Yeah, I think accessibility is really hard. I think that we kind of tell people it’s easy so that they won’t be afraid of it and then you start to learn a bit about it and it’s oh, my gosh, there is so many considerations. And not just that, it’s like sometimes people’s access needs will actually conflict with each other. So what will be good is actually good for other users. And I think the most important thing is to not be too dogmatic and just listen to feedback and accept that. It’s like, Sisyphus’s boulder like it’s a never ending task and just simply it’s part of the process.</p><p>Liz: Definitely. All right. What is the best e-commerce Add to Cart flow you have ever come across?</p><p>Nic: That’s hard to say. I think honestly, I get so surprised when anything even works.Like, it’s really sad. But when I try and test these things out,even if it works for just a single select flow, I would be so impressed. Like if there’s an Add to Cart process that kind of works just for keyboard users, even if it didn’t work for screen reader users, I’d be like, wow, they like made an attempt,because so often, it’s just like not accessible at all for any users. When it works, even in part, I get impressed. But I know that’s like a terrible bar and I shouldn’t be impressed.</p><p>Liz: All right, next one, what do you think about using an animation that moves a number up from your button to where your shopping cart total is indicated on your page so it’s like counting up?</p><p>Nic: I think I’ve seen an example of what you’re talking about. I would say, you know, you can try it out, I would definitely make sure to respect the prefers-reduced-motion query, it’s a query that lets you know if your user has turned off animations on their operating system
or their browser, and by using that media query, you can make sure that you’re not showing the animation to users who definitely don’t wanna see that animation. So try it out, remember to reset filters, reduce motion, and then sort of test with disabled users if you can.</p><p>Liz: A couple more questions here. Okay, for a modal dialog, would you dismiss the dialog when the users focus exits?</p><p>Nic: So for a modal dialog, the focus should be trapped within the dialog. So basically, if you’re hitting Tab, you should kind of like cycle through all the tab-able elements in it, and then the browser UI, and then when you’re tabbing back into the page, it should just reset on the first element in that modal. For a non-modal dialog, it definitely depends on sort of the presentation of it, but it’s pretty standard that tabbing out of the non-modal dialog, you might close that modal automatically. So you can consider doing that for non-modal dialogs.</p><p>Liz: Great. Thank you. I think this might be the last question here. Should animated scrolls between two points on a page be avoided? It provides continuity between points, but could also be overwhelming, especially for zoomed users.</p><p>Nic: Yeah, I think browsers have recently given us a bunch of tools to kind of smoothly scroll between things and I think as we see that more, we do hear people with vertigo and motion sensitivity, saying that it’s not a great pattern for them. So this is definitely a situation where I would make sure to use the prefers-reduced-motion query to make sure that we’re respecting that preference, at least, and sort of consider how large that shift is gonna be. If your page is like an infinite scroll, scrolling all the products, I would probably not use that effect for anyone, but if it’s maybe like a shorter distance of the page, you might wanna use itwith a prefers-reduced-motion query on it.</p><p>Liz: Great. Looks like we have just one more question here. Is it good practice to focus a dialogs Close button first when it opens?</p><p>Nic: Yeah, so I think this can really be a point of concern for a lot of people. I think different people will say different things. Some users think it’s best to focus the entire modal and if you have a aria-labelledby on your modal, it’ll kind of like read the heading first, or what it’s labeled by first and then we’ll kind of just read all the content in the modal and the user can choose to interact with it however, whenever they want to. Some people might focus directly on that heading elementand some people might just move focus to the first focusable element. I think it could be that you’re focusing directly on that Close button instead of the modal container just ‘cause you’re adding items to your cart, you hit Add to Cart and then the next thing you hear is just like, close modal. It’s not entirely clear what has just happened. But this is definitely something that are testing on your site and whether or not your modal, like your modal design you can change what that first focusable element is.</p><p>Liz: Got it. Great. Okay, we’re gonna squeeze in one more question here. With such a varied range of disabilities, does it make sense to make flows for each and have different views and features for each?</p><p>Nic: I think that kind of like gets us into the territory of having like a different site for disabled people. And that can be a really othering experience, You can consider providing different ways of checkout, like, for example, some e-commerce sites you have individual Product pages, but they also have like, a collections list where you canadd items to cart from there or like different ways of adding to cart and perhaps in a way, you can provide different methods for the same outcome and not in a like this is for ZoomText users kind of way but if you have those options, it will. If there’s a blocking aspect on one pattern, maybe it won’t be as terrible on another pattern.</p><p>Liz: Great. Thank you so much, Nic. And with that I think we are about at time, I do wanna thank Nic for a great session. We really appreciate it. And thank you all for attending this session and for joining us at axe-con. We have quite a few more sessions the remainder of the day,so we hope y’all have a great time. Thank you.</p><p>Nic: Thanks Liz.</p><p>Liz: Bye.</p></section>]]></content>
        <published>2023-09-05T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Bulletproofing your Content Management System (CMS)]]></title>
        <id>https://www.nicchan.me/blog/bulletproofing-your-cms/</id>
        <link href="https://www.nicchan.me/blog/bulletproofing-your-cms/"/>
        <updated>2023-06-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Help your future self by making sure your CMS can take anything clients throw at it.]]></summary>
        <content type="html"><![CDATA[<p>How many times have you been in the discovery process with a client when they say something like “And of course, we’ll need to be able to edit the content on our own.” If you’re anything like me, you probably hurriedly wrote “CMS” down in your notebook and circled it three times. You might ask the occasional follow-up question such as “Will this need to support multiple languages?” to inform the decision of which CMS you’ll choose, but other than that, the CMS piece of a site build is regarded as fairly straightforward. You pick the one that does everything you need it to do, and you build it out based on the content needs of your site. At handoff, you might do a workshop and/or prepare some documentation on how to manage content in the future, and hopefully you’ve done a good enough job to not get an email every time that client needs to change a line of text.</p>
<p>Lately, I’ve come to realize that I need to do better when estimating how long this kind of work will take. Across all my clients, I’ve noticed varying levels of how much post-launch CMS support a client needs. When I was trying to think of explanations for why some handoffs are smoother than others, I noticed certain commonalities; I realized that it all came down to the fundamental question of who is going to be editing the content in the future.</p>
<p>The smoothest hand-offs are always to those clients who will either have technical folks managing content in the future, or a designated permanent content person/team with previous content management experience. Technical teams tend to be aware of inherent limitations with content management, and in the event that something does go wrong, they may be able to fix it unaided. A professional content manager may need a little help as they learn the ropes, but after a while, they typically catch on to any unique quirks in the system and are able to train any new folks that come after them.</p>
<p>In my past experience, the most difficult CMS hand-offs have been to organizations where the content is managed by a rotating group of volunteers or interns. This may be their first experience working with any kind of content management, and the temporary nature of the position means that it’s difficult to create any kind of lasting knowledge. If an issue comes up once, you bet it’ll probably turn up again in a few months once there is a new person in that position. My experience in handing over sites in these conditions has taught me that I need to actually consider who will be maintaining the site’s content, and not just treat them as an afterthought. A website isn’t a one-size-fits-all solution, so why would a CMS be?</p>
<section><h2 id="practices-to-consider">Practices to consider</h2><p>Below is a non-exhaustive list of things I came up with when considering the editing experience. If you have any other tips, I’d love to hear from you!</p><section><h3 id="text-content">Text Content</h3><ul>
<li>Limit options appropriately. For example, instead of providing a free text field for hashtags, maybe start with a list of options that can be managed globally within the CMS. This prevents people from treating the field like social media and #making #everything #a #hashtag, thus preserving the usability of any tag archives.</li>
<li>Consider removing smaller heading levels. For most kinds of blogs, 2-3 heading levels is more than enough. I tend to remove the h1 option as it’s reserved for the page title, as well as the lower heading levels as editors just tend to use them for styling text. Random heading levels can mess with accessibility and SEO, especially when your content editor treats h6 and bold text interchangeably!</li>
<li>Consider stripping out empty paragraph and line break tags. Sometimes folks will ask me why the spacing is inconsistent between pages, and it’s almost always due to 14 extra line breaks inserted when copying content from a word processor to the CMS.</li>
</ul></section><section><h3 id="images">Images</h3><ul>
<li>Make sure an image of any dimensions can be uploaded and it will work on the site. You might tell your editors to upload a square image with specific dimensions for your staff page’s circle headshots, but someday someone will upload a landscape image, and without your intervention, some poor staff member’s head will be squished for all eternity. Cropping and resizing within the CMS is a must for certain audiences.</li>
<li>If you are ever gonna have text over an image, you need to consider ways in which to ensure that the text will remain visible. It may seem obvious that white text on a photograph with a white background won’t be legible, but it’s happened too many times for me to not implement a failsafe by adding some kind of overlay over the image.</li>
<li>Ideally, you want to process your images twice, once on the back-end to make sure that people uploading 48MB images won’t blow your hosting budget, and once on the front-end for all of that responsive image goodness and any cropping necessary.</li>
</ul></section><section><h3 id="errors">Errors</h3><ul>
<li>Write error messages, even for things that seem self evident to you. For example, one project I worked on had a URL field. I would regularly find that editors would leave off the https:// prefix or add a random ‘<a href="http://www.%E2%80%99">www.’</a> instead (because all websites start with www!) Providing explainer text, a way to validate the URL inline AND good error messages are essential for making sure these issues don’t slip into production.</li>
<li>Make sure any messaging is written in non technical language. ‘Text does not match validation regex’ may make sense to developers, but won’t help your editors. Take the time to write error messages that match your specific needs.</li>
<li>Lock everything that needs to be locked down. Does your CMS allow editors to tweak things they probably shouldn’t, such as the canonical URL of the site? Removing access to things like global site settings and plugins when appropriate can prevent problems down the line. <a href="https://amxmln.com/">Amadeus Maximillian</a> suggests using roles wherever possible to create permission based systems for different types of editor.</li>
<li>Have the site fail gracefully in the event of broken content. One of my favorite things about modern deployment solutions is that most deployment solutions make it hard to break your site. If you’re building a static site, you can lock the public version to the version that worked most recently, which gives you more time to troubleshoot any issues that come up.</li>
</ul></section></section>
<section><h2 id="conclusion">Conclusion</h2><p>Some of of the tips I described fall under good usability and cognitive accessibility. In an ideal world, it would be desirable to make sure every project you work on is robust as possible, but budgets and deadlines often pose a real challenge. It is a sad reality that projects and organizations that need this work the most often have the least amount of resources, as it’s not uncommon for things like community groups or non-profit organizations to be entirely staffed with volunteers. The pay for this kind of project may be heavily discounted, or non-existent. When working on a project for free/a discounted rate, I used find myself wondering if I could cut a few corners on the CMS side in order to make things more manageable, but taking this shortcut always came back to bite me in the form of future stress. Keeping the future editor’s capabilities in mind can make this kind of work more sustainable for you and the organization.</p></section>]]></content>
        <published>2023-06-15T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[CSS Specificity for Beginners]]></title>
        <id>https://www.nicchan.me/blog/css-specificity-for-beginners/</id>
        <link href="https://www.nicchan.me/blog/css-specificity-for-beginners/"/>
        <updated>2022-10-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[An introduction to CSS specificity in non-technical terms]]></summary>
        <content type="html"><![CDATA[<p>CSS is a styling language that allows us to apply styles to our website. Although the browser includes some styles by default, we as web authors often choose to override those browser styles. On top of that, end-users can also choose to override the author styles with their own custom styles. With all these conflicting sources of styles, how does the browser determine what style should be applied? It comes down to two primary factors, the <strong>cascade</strong>, AKA what order your styles are in, and <strong>specificity</strong>, AKA how precise the styles are. In this article, we’ll be focusing on the idea of CSS specificity.</p>
<pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">.selector</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> red</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">; </span><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">/* This is a declaration */</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p>A CSS rule consists of a selector and a declaration. The selector is a way of determining what elements the following declaration is applied to. Imagine that you are a teacher, with an auditorium full of students in front of you. A selector is a way of addressing particular students so that you can give instructions (AKA declarations) to them. The general rule is, the more specific your instruction, the higher its specificity. In CSS, more specific selectors win out over less specific selectors.</p>
<section><h2 id="element-selectors">Element Selectors</h2><p>Element selectors are a way of targeting elements based on what the element is. In our school auditorium metaphor, this might be equivalent to addressing all students from a particular grade. If you say “Fifth-grade students, stand up”, you are addressing students based on what they are. An example in CSS might look like this snippet below, where we are grabbing all our anchor links based on the fact that they are anchor links. This represents the lowest level of CSS specificity.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">a</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> blue</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre></section>
<section><h2 id="attribute-selectors">Attribute Selectors</h2><p>Attribute selectors are a way of grouping elements together based on different shared attributes. For example, in our school auditorium, we might want to address students across different grade levels based on some attribute they share. In this situation we might say something like “Students with long hair, stand up,” to address all students with long hair. We could apply our previous knowledge and combine element and attribute selectors into a more specific instruction, such as “Fourth-grade students with long hair, fifth-grade students with long hair, sixth-grade students with long hair, stand up,” but this level of specificity isn’t necessary, and is more verbose than our original instruction.</p><p>An attribute selector in CSS is enclosed in square brackets and looks like this:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">[</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">lang</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;en&quot;</span><span style="color:#A75293;--shiki-dark:#CB96CC">]</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> blue</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>Attribute selectors fall under the second lowest tier of specificity.</p></section>
<section><h2 id="class-selectors">Class Selectors</h2><p>You might think of a class name as a name that is assigned to an element. As individuals, we probably like to think that our given names are unique, but in practice that is rarely the case (I should know, I’m almost never the only Nic/Nick on a team)! Depending on the name, it might be that your name only appears once in a group, but it may be used to address multiple students at the same time, eg. “Students named John, stand up” is likely to result in multiple students standing up.</p><p>We can write our class names in a way to target one or more elements, depending on the wider context of our site and what we as CSS authors might need. A class name like “footer” may only be needed once per page, but a class name like “primary-button” may appear many times. The class selector begins with a period and is followed by the class name. Class selectors fall under the second lowest tier of specificity along with attribute selectors.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">.navigation</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> blue</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre></section>
<section><h2 id="id-selectors">Id Selectors</h2><p>So what if we want to grab a single student? The only truly unique identifier for an element is an id (technically, you can apply the same id to multiple elements and your page would still technically ‘work’, but this can have a negative impact on the accessibility and usability of your page!) An id can be thought of as a unique identifier for a student, such as a social security number or a passport number. Theoretically, there should only be one student that has a given id, so if we want to be sure that we’re addressing a specific student, using their ID number is a more foolproof way to target them rather than saying something like “John Smith, stand up.” An id selector begins with a hash, and is followed by the id. Id selectors fall under the second highest tier of specificity.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#1F7D89;--shiki-dark:#B8BAFF">#navigation</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> blue</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre></section>
<section><h2 id="inline-styles">Inline Styles</h2><p>Inline styles aren’t technically a selector in CSS, but they represent the highest tier of specificity. We might think of inline styles as students who have headphones on during the school assembly. Regardless of what instructions are given to them, they have their own set of styles that they are listening to. A room full of students with headphones on is much harder to wrangle than a room full of students listening attentively, so it is in your interest as a CSS author to minimize inline styles. Inline styles are directly applied to an element in the HTML.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">div</span><span style="color:#A75293;--shiki-dark:#CB96CC"> style=&quot;color:red;&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:#A75293;--shiki-dark:#CB96CC">I&#39;m red!&lt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">/</span><span style="color:#4075BA;--shiki-dark:#B993FF">div</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"></span></code></pre></section>
<section><h2 id="combinator-selectors">Combinator Selectors</h2><p>Combinator selectors are a way of describing elements in relation to another. In our student metaphor, this might be akin to saying something like “Students who sit behind the first row of students”, or “Amanda, the younger sibling of Sally from fifth-grade.” Though both are ways of addressing students in relation to one another, one is more specific than the other. Combinators are powerful and can unlock a ton of useful functionality without having to come up with granular class names, but be warned as combinators add up the specificity of all selectors in that particular combination. Chaining too many combinators should be avoided.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">p</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> +</span><span style="color:#4075BA;--shiki-dark:#B993FF"> p</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	margin-top:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 1</span><span style="color:#4075BA;--shiki-dark:#B993FF">rem</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre></section>
<section><h2 id="important"><code>!important</code></h2><p><code>!important</code> provides a way for us to override all of these selectors that we have previously mentioned. We might think of <code>!important</code> as an announcement blaring over the school’s public address system, it takes precedence over what is being announced in the auditorium, regardless of how specific the selector is. “All students involved in lunchtime incident, please come to the principal’s office” and “Student number 92374, please come to the principal’s office” are equally capable of overriding our previous rules. Like inline styles, <code>!important</code> is not a selector, it is applied to a declaration directly.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">a</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	color:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> red</span><span style="color:#A75293;--shiki-dark:#CB96CC"> !important</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre></section>
<section><h2 id="what-does-this-teach-us-about-writing-good-css">What does this teach us about writing good CSS?</h2><p>It’s fairly common for newer developers to be overly specific when writing CSS. I often see developers combining and chaining together long selectors that may correspond to the HTML structure of their page. A selector like <code>#navigation &gt; ul &gt; li.current &gt; .button</code> will get you the results that you want, but it can be more difficult to understand. Even more problematic, a selector like that can be hard to extend, as you keep having to find new ways to make subsequent selectors even more specific than your previous selector. The selector is much like stating “Students in fifth-grade, of which the one named John that sits behind Sally with blue eyes.” It’s a lot to keep track of! You’ll have an easier time with CSS if you take some time to really think about what you are trying to target and how specific that selector needs to be.</p></section>]]></content>
        <published>2022-10-17T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Modding Stardew Valley for accessibility]]></title>
        <id>https://www.nicchan.me/blog/modding-stardew-valley-for-accessibility/</id>
        <link href="https://www.nicchan.me/blog/modding-stardew-valley-for-accessibility/"/>
        <updated>2022-04-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[What can looking at Stardew Valley mods teach us about how to make video games more accessible?]]></summary>
        <content type="html"><![CDATA[<p>Stardew Valley is a farming simulator game that was released in 2016 by a solo developer, ConcernedApe. It was an instant classic, and countless people—from diehard gamers to those picking up their very first video game—have fallen in love with its unique charm. As a game, Stardew Valley is both wide and deep: its open ended nature means you can do as much or as little as you want and still feel a sense of progression, and its retro style pixel art graphics make it playable on a wide variety of platforms.</p>
<p>Despite all the hype surrounding the game, I didn’t expect to fall down the Stardew Valley rabbit hole myself. After a period of too much work I was left with a repetitive strain injury (RSI). I stayed away from the game because I assumed the Stardew Valley experience, like most other games, was now locked out to me. Due to my RSI, I couldn’t understand the appeal of a game that seemed to involvebe doing repetitive actions over and over again. I finally picked up the game in 2021, and I was so glad I did! I was late to the party, but I now count myself amongst the millions of loyal fans that Stardew Valley has around the world.</p>
<p>As I became acquainted with the game, I soon found myself exploring the world of unofficial Stardew Valley mods, patches that players can apply to the base game in order to change some part of the experience. I’ve previously written at length about how how <a href="https://www.nicchan.me/blog/what-tumblr-taught-me-about-accessibility/">disabled Tumblr users</a> have developed ways to make the platform more accessible, and was curious to see if the Stardew Valley community was similar.</p>
<div class="post-callout"><h3 class="post-callout__title">Disclaimer</h3><p class="post-callout__text"><p>I am not a full-time assistive technology user, and can play Stardew Valley
without much modification with the aid of my niche equipment setup. You
might enjoy the following video reviews of the base Stardew Valley
experience for a more exhaustive overview.</p></p><ul><li><p><a href="https://www.youtube.com/watch?v=zhARjRoiGEI">Onehandmostly</a> 
walks through his experience with Stardew Valley as a one-handed gamer. He
covers the existing accessibility settings extensively.</p></li><li><p><a href="https://www.youtube.com/watch?v=2gTITzQqiqQ">Darrel Bowles</a>, a
completely blind gamer, praises Stardew’s use of subtle non-verbal audio
cues. He plays Stardew Valley in multiplayer mode with a partner who
helped to build pathing so that he may navigate the farm via sound, much
like a blind person might use tactile paving in public spaces.</p></li></ul></div>
<section><h2 id="how-accessible-is-the-core-stardew-valley-experience">How accessible is the core Stardew Valley experience?</h2><p>Those of you who are web developers might already be familiar with the <a href="http://gameaccessibilityguidelines.com/full-list/">Web Content Accessibility Guidelines</a> (WCAG), a series of documents on how to make the web more usable for people with disabilities. For video games, there’s a similar effort known as the <a href="http://gameaccessibilityguidelines.com/full-list/">Game Accessibility Guidelines</a>, which I’ve referenced for this article. While websites and video games have major differences, they both often are interactive digital experiences, and there is some overlap between accessibility best practices for both mediums.</p><p>Despite being the product of a sole developer, Stardew Valley offers quite a few accessibility features out of the box. The level of customization for the overall user interface and controls is more granular than most other games, even those with larger budgets and teams behind them.</p><img alt="A small sample of Stardew Valley settings. Players can toggle the menu background, lock the toolbar, adjust the zoom level, show zoom buttons and disable flash effects." loading="lazy" decoding="async" sizes="(min-width: 846px) 846px, 100vw" style="object-fit:cover;max-width:846px;max-height:581px;aspect-ratio:1.4561101549053357;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_440,c_lfill,f_auto/v1650274788/stardew-options 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_515,c_lfill,f_auto/v1650274788/stardew-options 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_569,c_lfill,f_auto/v1650274788/stardew-options 828w,
https://res.cloudinary.com/nicchan/image/upload/w_846,h_581,c_lfill,f_auto/v1650274788/stardew-options 846w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_659,c_lfill,f_auto/v1650274788/stardew-options 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_742,c_lfill,f_auto/v1650274788/stardew-options 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_879,c_lfill,f_auto/v1650274788/stardew-options 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1146,c_lfill,f_auto/v1650274788/stardew-options 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1692,h_1162,c_lfill,f_auto/v1650274788/stardew-options 1692w" src="https://res.cloudinary.com/nicchan/image/upload/w_846,h_581,c_lfill,f_auto/v1650274788/stardew-options"><p>There are options to remap nearly every keybinding, and many mouse interactions can also be achieved via the keyboard. This is a welcome feature for those who have difficulty using a fine-tuned pointer device such as a mouse, or those who suffer from strain (like me!) using conventional mice. You can scale the user interface and the background independently up to 150% and 200% zoom, which is great for users with low vision or those who may need larger hitboxes in order to successfully complete an interaction. There is also an option to use the default hardware cursor, which is helpful for those who have customized their cursor to their needs (typically by increasing the size or changing the color).</p><p>In terms of sensory needs, Stardew Valley allows you to disable flashing lights during the occasional thunderstorm. Players have the ability to toggle certain sounds individually, allowing people to create their own custom sound experience based on their individual cognitive abilities, sensitivities and triggers. For example, blind gamers like Darrel Bowles may appreciate audio cues that would let them know that their action has been completed successfully, but gamers with certain phobias or triggers may appreciate the option to turn off particular sounds.</p><p>In terms of settings and configuration, Stardew Valley goes beyond most large-budget games, offering a wide variety of options that would allow you to configure the basic gaming experience. However, there aren’t that many options to customize the actual game itself to make it more accessible, and this is where mods step in.</p></section>
<section><h2 id="the-wonderful-world-of-stardew-valley-mods">The wonderful world of Stardew Valley mods</h2><p>The vast majority of Stardew Valley mods are available through the <a href="https://www.nexusmods.com/stardewvalley/mods/2400">Stardew Modding API (AKA SMAPI)</a>. SMAPI is an installable piece of software that allows users to load mods by placing them in a particular folder on the filesystem. Once SMAPI is installed, mods can be downloaded from a variety of places such as Nexus Mods, which hosts thousands of Stardew Valley mods alone. While some mods are merely cosmetic or offer additional content, a quick glance at <a href="https://www.nexusmods.com/stardewvalley/mods/">the most popular Stardew Valley mods on Nexus Mods</a> shows us that many others target specific accessibility needs.</p><div class="post-callout"><h3 class="post-callout__title">Another Disclaimer:</h3><p class="post-callout__text"><p>I have linked mods here for illustrative purposes only; these are not meant
as endorsements. I do not guarantee that any of the below mods will work
with newer versions of Stardew Valley, or at all. Rather, I link to them to
demonstrate that people do want—and deserve—accessible gaming experiences.
Additionally, please note that sites like Nexus Mods tend to have some
fairly significant accessibility issues themselves.</p></p></div><section><h3 id="colors">Colors</h3><p>One of the things that made me personally wary of playing Stardew Valley was its bright color palette. While I’m sure plenty of players gravitate towards the default bright and cheerful graphics, many people experience eye fatigue or migraines when looking at a bright color palette. Luckily, there’s a whole host of mods out there that offer alternative color palettes for the game maps, in every flavor imaginable. Options include, but are not limited to, the following:</p><ul>
<li>
<p><a href="https://www.nexusmods.com/stardewvalley/mods/2995">A Wittily Named Recolor</a> makes the whole palette significantly more muted</p>
</li>
<li>
<p><a href="https://www.nexusmods.com/stardewvalley/mods/1869">Starblue Valley</a> makes everything a little more blue, an option for those who experience eye strain with too much yellow</p>
</li>
<li>
<p><a href="https://www.nexusmods.com/stardewvalley/mods/1213">Natural Recolor</a> makes the color palette more natural looking</p>
</li>
<li>
<p><a href="https://www.nexusmods.com/stardewvalley/mods/6367">Vibrant Pastoral</a>, despite its name, reduces the saturation on all outdoor maps</p>
</li>
</ul><figure><img alt="Side by side comparison of what the farm looks like with the original Stardew Valley colors compared to colors from 'A Wittily Named Recolor.' The recolored farm is significantly less saturated and with higher contrast between the different kinds of foliage." loading="lazy" decoding="async" sizes="(min-width: 1282px) 1282px, 100vw" style="object-fit:cover;max-width:1282px;max-height:520px;aspect-ratio:2.4653846153846155;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_260,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_304,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_336,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_389,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_438,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_519,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1282,h_520,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 1282w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_677,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_779,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_831,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1038,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_2564,h_1040,c_lfill,f_auto/v1650274797/stardew-recolor-comparison 2564w" src="https://res.cloudinary.com/nicchan/image/upload/w_1282,h_520,c_lfill,f_auto/v1650274797/stardew-recolor-comparison"><figcaption><p>Original Stardew Valley colors vs. A Wittily Named Recolor</p></figcaption></figure><p>There are also color-related mods that explicitly target accessibility needs. <a href="https://www.nexusmods.com/stardewvalley/mods/4056">Dark User Interface</a> offers a (you guessed it!) dark mode for the UI, and can be used in combination with a map recolor. For those who like light mode but struggle with the default bright yellow-orange interface, there’s a beautiful, on-brand <a href="https://www.nexusmods.com/stardewvalley/mods/4697">white and brown</a> alternative.</p><p>A fairly common complaint from Stardew Valley players on various forums is it can be difficult to tell the difference between different object states, especially when the difference is highlighted primarily through color (Different object states can signal important information to players, such as whether an object in the game can be interacted with.) Mods such as <a href="https://www.nexusmods.com/stardewvalley/mods/6833">Highlight Empty Jars</a> address this by adding a more noticeable speech bubble on top of empty jars, or changing the empty jars to a brighter red color. Similarly, colorblind players can often struggle to tell crops apart or have issues identifying when things are finished growing, as color in the base game is often used to indicate ripeness. The <a href="https://www.nexusmods.com/stardewvalley/mods/10380">SDV Colorblind Mod</a> offers a helpful alternative for those with red/green colorblindness.</p></section><section><h3 id="cognitive-accessibility">Cognitive accessibility</h3><p>Playing Stardew Valley requires a certain degree of memorization. There are items you have to hunt down, seasonal crops and NPCs that have a specific weekly schedule. For gamers without cognitive issues, learning these things could be regarded as part of the game’s overall learning curve. It mirrors your growth as a rookie farmer, and your knowledge about the world grows as the seasons pass.</p><p>However, for those with cognitive and learning disabilities, it can be difficult to ever reach that level of familiarity. Having to repeatedly pause a game in order to go double check something on the wiki is not my personal idea of a great time.
<a href="https://www.nexusmods.com/stardewvalley/mods/541">Lookup Anything</a> basically offers an enhanced wiki experience within the game. It even provides contextual information, such as your current friendship level with NPCs and whether or not you’ve crafted an item before. <a href="https://www.nexusmods.com/stardewvalley/mods/1150">Similarly, Ui Info Suite</a> enhances the level of information present in the UI, making hidden game mechanics more visible and improving the information found in the default tooltips so that players don’t need to rely so much on memorization.</p><img alt="The NPC Map icons mod changes the map to display a characters portrait in the location they are currently in. There is a tooltip over the characters that displays the names 'Jas, Vincent." loading="lazy" decoding="async" sizes="(min-width: 1200px) 1200px, 100vw" style="object-fit:cover;max-width:1200px;max-height:720px;aspect-ratio:1.6666666666666667;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_384,c_lfill,f_auto/v1650274788/npc-map-locations 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_450,c_lfill,f_auto/v1650274788/npc-map-locations 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_497,c_lfill,f_auto/v1650274788/npc-map-locations 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_576,c_lfill,f_auto/v1650274788/npc-map-locations 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_648,c_lfill,f_auto/v1650274788/npc-map-locations 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1200,h_720,c_lfill,f_auto/v1650274788/npc-map-locations 1200w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_768,c_lfill,f_auto/v1650274788/npc-map-locations 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1001,c_lfill,f_auto/v1650274788/npc-map-locations 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1152,c_lfill,f_auto/v1650274788/npc-map-locations 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1229,c_lfill,f_auto/v1650274788/npc-map-locations 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2400,h_1440,c_lfill,f_auto/v1650274788/npc-map-locations 2400w" src="https://res.cloudinary.com/nicchan/image/upload/w_1200,h_720,c_lfill,f_auto/v1650274788/npc-map-locations"><p>By default, Stardew Valley’s map is static and displays only a graphic image of the world map. <a href="https://www.nexusmods.com/stardewvalley/mods/239">NPC Map Locations</a> adds the different non-playable characters (NPCs) to the map, with locations updating in real time as they move around. It even adds a tooltip that will let you know when the NPC will leave their current location, so that you know whether or not you have time to meet them there before they leave.</p><p>Mods that help with cognitive accessibility are some of the most popular Stardew Valley mods of all time, with download counts numbering in the millions. As Nexus Mods keeps track of unique downloads and total downloads separately, we notice something interesting. With a mod like NPC Map Locations, the unique downloads number is around 1.3 million, where the total download number is around 3.3 million, nearly three times as much. This suggests that many gamers consider these mods essential enough that they continue to return for updates to ensure compatibility. Even a single purpose mod such as <a href="https://www.nexusmods.com/stardewvalley/mods/1179">Range Display</a> (which lets you see how wide an area your sprinklers and scarecrows affect) has download numbers in the hundreds of thousands. Options to reduce the cognitive load on players are vital for cognitive accessibility and appreciated by a large number of gamers.</p></section><section><h3 id="reducing-repetition">Reducing repetition</h3><p>Another highly popular category of mods revolves around reducing repetitive actions in this game. A big part of the core gameplay loop involves repetitive actions. To plant a crop, the player needs to hoe the ground, place the seeds, and water the seeds every day, until the plant grows. Over time, a player is able to upgrade their tools to make these tasks much less repetitive, but players with conditions such as RSI or carpal tunnel syndrome may find this initial hurdle too difficult to overcome. To most players, an extra click is an inconvenience at worst, but to some, an extra click can make the game unplayable.</p><img alt="A Stardew Valley farmer driving a tractor across the ground" loading="lazy" decoding="async" sizes="(min-width: 479px) 479px, 100vw" style="object-fit:cover;max-width:479px;max-height:252px;aspect-ratio:1.9007936507936507;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_479,h_252,c_lfill,f_auto/v1650274788/tractor 479w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_337,c_lfill,f_auto/v1650274788/tractor 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_395,c_lfill,f_auto/v1650274788/tractor 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_436,c_lfill,f_auto/v1650274788/tractor 828w,
https://res.cloudinary.com/nicchan/image/upload/w_958,h_504,c_lfill,f_auto/v1650274788/tractor 958w" src="https://res.cloudinary.com/nicchan/image/upload/w_479,h_252,c_lfill,f_auto/v1650274788/tractor"><p>Many mods allow users to bypass more repetitive aspects of the game, an effective way to minimize strain. The <a href="https://www.nexusmods.com/stardewvalley/mods/1401">Tractor Mod</a> is a popular configurable mod that can drastically cut down the number of repetitive actions, with the added bonus of not breaking player immersion. A mod like this could be used in multiplayer mode to allow a disabled gamer to play on equal footing with abled players.</p><p>While early-game gameplay tends to revolve around growing crops for some initial cash, more advanced gameplay involves turning your produce into different kinds of processed goods. The <a href="https://www.nexusmods.com/stardewvalley/mods/1063">Automate</a> mod gives the player the option to remove repetitive tasks by putting a storage chest next to the machine the player wants to automate, saving the player the effort of checking on the machine. In an open-ended game like Stardew Valley where players have the choice between dozens of different activities such as farming, fishing, mining, decorating, or even romancing your favorite NPCs, mods to reduce repetition give players the control to play the game exactly how they’d like to play it.</p></section><section><h3 id="fine-motor-skills--timing">Fine motor skills &amp; timing</h3><p>Stardew Valley’s gameplay has a few elements that are considered time sensitive. Every seven seconds that pass in real life equate to 10 minutes in the Stardew Valley world. Most players will find that they are initially limited more by their in-game avatar’s “energy” than by the actual passage of time, but players who use assistive technology may need a little more time to complete tasks, or may not be able to complete certain kinds of tasks at all.</p><p>The <a href="https://www.nexusmods.com/stardewvalley/mods/169">Timespeed</a> mod does what its name implies: it gives the player full control over the passage of time in the game. You can slow down or pause the passage of time completely, and all of this can be tinkered with on the fly using various hotkeys, or based on personalized configuration for different locations.</p><p>Most actions in the game are not time sensitive, in the sense that the timing of your clicks and keypresses doesn’t affect your success at the actual mechanic. There are two main exceptions—combat and fishing—where your ability to kill a monster or catch a fish is dependent on activating a control at a certain pace. There are many reasons why disabled players may not be able to achieve this, whether because a player has a condition that limits their ability to time their actions, or because the assistive technology they are using (an example might be a <a href="http://www.orin.com/access/sip_puff/">sip and puff switch</a>) doesn’t allow for precise timing. Mods like <a href="https://www.nexusmods.com/stardewvalley/mods/3623">Fishing Made Easy</a>, <a href="https://www.nexusmods.com/stardewvalley/mods/2697">Skip Fishing Minigame</a> and <a href="https://www.nexusmods.com/stardewvalley/mods/4227">Combat Made Easy</a> give the player granular control over different factors that make these aspects of the game challenging, allowing people to tailor their experience to their specific access needs.</p><p>At this point, you might be thinking, “But Nic, these mods have general appeal outside of accessibility needs. How do we know that these mods are actually benefiting disabled people?” Disabled people are loud and clear about how these mods solve various accessibility needs! Reddit user <a href="https://www.reddit.com/r/StardewValley/comments/mfypfn/a_love_letter_to_the_modding_community_from_a/">baibeach91 describes how mods allowed them to play the game</a> after brain cancer left them with neurological difficulties, explicitly mentioning Fishing Made Easy as one of the mods that helps them. The creator of the mod responds to their post in the replies below:</p><blockquote>
<p>It makes me beyond happy to hear the mod I created has helped make the game easier for you and others that are physically unable to play the game as it is.</p>
<p>A little backstory!</p>
<p>I actually created Fishing Made Easy Suite in early 2019 only because I have a friend that has a form of Parkinson’s disease and was unable to catch the tougher fish.</p>
</blockquote><p>Mods like these are a part of the <a href="https://ssir.org/articles/entry/the_curb_cut_effect">curb-cut effect</a>, where a feature intended for disabled people also provides benefits to a larger group than who the feature was intended for. The mod was originally created for a friend with Parkinson’s, but many other people are able to enjoy the game to a greater extent because the mod exists. In many cases, accessibility features have the incidental benefit of making the game better for a large number of people.</p><p>That being said, much has been written about the curb-cut effect and the dangers of centering non-disabled people in discussions about accessibility—too much to put in this article. I’d like to specifically note that disabled folks are worthy of being considered, regardless of whether or not their accommodations benefit the larger population.</p></section><section><h3 id="text-to-speech--speech-recognition">Text-to-speech &amp; speech recognition</h3><p>This final section covers a class of mods that are less likely to fall under the curb-cut effect: those that are specifically for a disabled audience. These mods seek to either integrate with existing assistive technologies such as screen readers, or to provide a Stardew Valley-specific version of an assistive technology. Although these mods may have lower download numbers, many have a far greater impact on people’s ability to play Stardew Valley. These mods are often the only thing allowing the disabled users who download them to play the game, which would otherwise be completely inaccessible to them.</p><img alt="Pelican TTS settings let you configure different voices for each character." loading="lazy" decoding="async" sizes="(min-width: 1838px) 1838px, 100vw" style="object-fit:cover;max-width:1838px;max-height:1159px;aspect-ratio:1.5858498705780846;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_404,c_lfill,f_auto/v1650274789/pelican-tts 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_473,c_lfill,f_auto/v1650274789/pelican-tts 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_522,c_lfill,f_auto/v1650274789/pelican-tts 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_605,c_lfill,f_auto/v1650274789/pelican-tts 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_681,c_lfill,f_auto/v1650274789/pelican-tts 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_807,c_lfill,f_auto/v1650274789/pelican-tts 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1052,c_lfill,f_auto/v1650274789/pelican-tts 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1838,h_1159,c_lfill,f_auto/v1650274789/pelican-tts 1838w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1211,c_lfill,f_auto/v1650274789/pelican-tts 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1291,c_lfill,f_auto/v1650274789/pelican-tts 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1614,c_lfill,f_auto/v1650274789/pelican-tts 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_2018,c_lfill,f_auto/v1650274789/pelican-tts 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3676,h_2318,c_lfill,f_auto/v1650274789/pelican-tts 3676w" src="https://res.cloudinary.com/nicchan/image/upload/w_1838,h_1159,c_lfill,f_auto/v1650274789/pelican-tts"><p>First up, we have <a href="https://www.nexusmods.com/stardewvalley/mods/1079">Pelican TTS</a>. TTS stands for Text-to-Speech, a popular method for blind and low-vision people to interact with text interfaces. <a href="https://youtu.be/Lo9UhAMoBJs?t=124">This video shows off the gist of the Pelican TTS mod</a>, though many features have since been added that allow players to customize voices for each NPC.</p><p>In a similar vein, <a href="https://github.com/evfredericksen/StardewSpeak">StardewSpeak</a> is a mod that allows for people to play Stardew Valley solely with their voice. The mod creator has put a lot of thought into how to make the player’s experience as streamlined as possible, adding in a bunch of <a href="https://www.youtube.com/watch?v=XYL2eXWWi8U">helpful commands</a> that make this a viable way to play the game. For example, instead of forcing the player to count the number of tiles to reach the destination, the player can just say ‘Go to farm’, and the character will automatically find an appropriate path to the farm, taking all the twists and turns required.</p><p>Last but not least, we have the <a href="https://stardew-access.github.io/">Stardew Access</a> mod, which seeks to make Stardew Valley fully playable with just the keyboard (no mouse!), and adds support for popular screen readers such as JAWS and NVDA. The creator of this mod, Shoaib Khan, kindly agreed to speak with me for this article.</p><p>Shoaib is a university student who works on mods in his free time and moderates a Discord Server called Playability to gather feedback and collect bug reports. He got into modding when LogicProXGaming, a blind Minecraft streamer he followed, expressed a need for a mod that would tell him his current health on a keypress. Shoaib rose to the challenge and eventually began developing Stardew Access as well. When asked about the reception of Stardew Access, Shoaib mentioned that he never thought so many people would be interested in a mod like this, and that he’s grateful for all the feedback that allows him to improve the mod.</p><p>This is where I believe that arguments against accommodations making games “too easy” or “for lazy people” fall flat. Imagine if you had to code an entire interface or submit a pull request just so you could play a game! Making games accessible is a tremendous effort that is unfairly borne by mostly disabled people themselves and by people like Shoaib. What a different world it would be if game developers considered accessibility from the start, and it wasn’t something that players had to hack in themselves.</p></section></section>
<section><h2 id="closing-notes">Closing notes</h2><p>Many people feel strongly that allowing for users to change the difficulty of the game somehow compromises the experience or the “developer’s vision”, to which I can only shrug. Being able to meet people where they are is a fundamental aspect of accessibility. Opening the door and allowing people to enjoy a game that they wouldn’t otherwise have been able to play is a good thing. I can’t believe this still needs to be said, but based on the comment section of any given article on gaming accessibility, disabled people continue to have to fight for this basic level of acceptance in gaming spaces.</p><p>User-created mods are not a replacement for first-party/developer support, for many reasons. In the case of Stardew Valley, mods only work on open platforms, not on closed platforms such as the Nintendo Switch or iOS, and many other games don’t allow for the use of mods at all. What I want folks to take away is this: the fact that people would go out of their way to create tools to make the game accessible to themselves and others highlights the importance of creating accessible gaming experiences in the first place. I am only a visitor to the big wide world of gaming accessibility, and I hope that people will continue to support and follow the efforts of disabled gamers and their advocates.</p></section>]]></content>
        <published>2022-04-18T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[My white whale: A use case for will-change]]></title>
        <id>https://www.nicchan.me/blog/a-use-case-for-will-change/</id>
        <link href="https://www.nicchan.me/blog/a-use-case-for-will-change/"/>
        <updated>2022-02-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[what the heck does will-change even do anyway]]></summary>
        <content type="html"><![CDATA[<p>I’ve been a professional web developer for seven years now, which has given me a little bit of time to explore the big wide world of CSS. I might not reach for rarer CSS properties such as <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode">writing-mode</a> or the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns/Basic_Concepts_of_Multicol">CSS multicol properties</a> every day, but they are a beloved part of my toolkit that serve their purpose well.</p>
<p>In all these years, one property I haven’t been able to find a use case for is <code>will-change</code>. To give a bit of an introduction to what <code>will-change</code> is, Sara Soueidan wrote an <a href="https://dev.opera.com/articles/css-will-change-property/">excellent explainer</a> which is well worth the read.</p>
<section><h2 id="a-very-quick-explainer">A very quick explainer</h2><p>In short, if we imagine the browser as a large art or design program, using <code>will-change</code> promotes that element to its own ‘composite layer.’ That layer can be hardware accelerated; that is, be rendered using the more powerful GPU (Graphics Processing Unit) instead of the default CPU (Central Processing Unit). Knowing that the element will change over time lets the browser isolate that element, and should improve rendering performance. <code>Will-change</code> does come with the caveat that it should be used sparingly, only on elements that really need it. Offloading too many things to the GPU can slow down the page, the opposite of what we want.</p></section>
<section><h2 id="the-epic-journey">The epic journey</h2><p>With all that in mind, the <code>will-change</code> property landed in major browsers in August 2015, and I’ve been on the lookout for when to use it ever since. It might seem self-evident to apply it to commonly animated properties such as transform or opacity, but the <a href="https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/">browser already classifies them as composite properties</a>, thus, they are known as the few properties that you can already expect decent animation performance from. So, heeding the advice of the great developers who came before me, I was cautious and waited for the right opportunity to come along.</p><p>I hoped that <code>will-change</code> would solve the <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=521364">Chromium bug</a> where vertically centering a element using <code>transform: translateY(-50%)</code> would result in a blurry element if the calculated height was an odd number of pixels, but our friend <code>will-change</code> let me down there, it had no effect whatsoever. (I typically use Flexbox to avoid this issue now, as it sadly, still exists, seven years later).</p><p>Over time we even began to see articles critiquing <code>will-change</code> appear. <a href="https://greensock.com/will-change/">GreenSock, a popular JavaScript animation library, published a piece</a> noting how the implementation of <code>will-change</code> caused animations that had worked perfectly in the past to appear blurry, and the recommended solution (setting <code>will-change: auto</code>, effectively, a reset) seemed contrary to the proposed behavior of <code>will-change</code>.</p><p>In my mind, it was around this time where <code>will-change</code> became something to be wary of. For the most part, I wasn’t really writing animation code that was very complex, and could rely on well-tested libraries such as GreenSock or documented techniques such as <a href="https://css-tricks.com/animating-layouts-with-the-flip-technique/">FLIP</a> for silky smooth animations. Browsers were only getting better at rendering performance, whenever I popped open Chrome’s <a href="https://developers.google.com/web/fundamentals/performance/rendering/simplify-paint-complexity-and-reduce-paint-areas">paint flashing tool</a> to ensure that stuff was on its own layer when it needed to be, things were typically looking okay out of the box. I’d reach for it sometimes to try and fix the occasional rendering bug (usually as a cudgel after <code>translateZ(0)</code> and <code>backface-visibility: hidden</code> failed) but it certainly never was the silver bullet I hoped it would be.</p></section>
<section><h2 id="get-to-the-point-already">Get to the point already</h2><p>But that all changed this week. I woke up one morning to find that a client had noted that the link animation was looking a little rough around the edges in Safari. Being a Windows gal, I fired up the good ol’ BrowserStack to take a look, and saw this. (Forgive the screencap quality, it is a recording from Browserstack after all!)</p><img src="https://res.cloudinary.com/nicchan/image/upload/v1643852472/linktext.gif" alt="Animation of a link being hovered, a purple background extends from left to right, but as the mouse is moved away, the animation reverses from right to left imperfectly, leaving a little purple block on the right side of the link" width="195" height="56" loading="lazy"><p>My first reaction was surprise. Wasn’t scale supposed to be one of the safe properties? Nonetheless, I reached deep into my toolbox, for the cursed artifact that I had never reached before. I may have even held my breath a little as I typed <code>will-change: transform</code> and waited for my browser to reload the page.</p><img src="https://res.cloudinary.com/nicchan/image/upload/v1643852472/linktext-2.gif" alt="Animation of a link being hovered, a purple background extends from left to right, and recedes as expected when the link is no longer being hovered" width="182" height="56" loading="lazy"><p>It worked! I dusted off my hands and continued on with my day, excited to have finally found my white whale, a use for the will-change property.</p></section>
<section><h2 id="the-mystery-continues">The mystery continues</h2><p>But wait, there’s more! I know I can’t leave you all without the juicy details. What went wrong, and why was will-change able to help? In this particular codebase, we had a link, and a pseudo element is animating from a horizontal scale of 0 to 1 when the link is hovered (snippet below lightly modified for brevity). I tried <a href="https://codepen.io/nchan0154/pen/abVNYNo">dropping everything into a Codepen</a> to see if I could reproduce it, but interestingly enough, it works fine without will-change in the default Codepen view, and shows up only in the debug view, the only view on Codepen without their wrapping code (which I cannot share).</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">.link::before</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	content:</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	display:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> block</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	position:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> absolute</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	transform:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> scaleX</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">0</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	transform-origin:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> left</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> center</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1F7D89;--shiki-dark:#83CDFF">.link:hover::before</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	transform:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> scaleX</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">1</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>In a pre-pandemic life, I might have tried harder to get the bottom of this mystery, but alas, it was everything I had in me to type up this blog post, which I’ve uncharacteristically hammered out in a single sitting. Perhaps I will never know why <code>will-change</code> was able to save me in this case. I’d be extremely eager to hear if other folks have had experience with the <code>will-change</code> property smoothing any rendering issues, my inbox is open!</p><p>PS: Happy new year of the tiger. May 2022 be kinder to us all.</p></section>
<section><h2 id="further-examples-last-updated-february-27-2022">Further examples (Last updated February 27, 2022)</h2><p>Since publishing this article, I’ve found some other folks who have had luck with will-change resolving rendering issues. Thanks to the folks below who allowed me to share their discoveries here.</p><p>Alex Riviere noticed that when using Chrome 98 with a Macbook Pro, the last cloned item in <a href="https://codepen.io/fimion/pen/jOamJaE?editors=0010">this infinite scrolling section</a> would cause the last element not to render. Adding <code>will-change: transform</code> fixes this.</p><p>Brian Cross noticed a recent issue with Safari 15 where GSAP transform animations that had worked correctly in previous versions were now <a href="https://codepen.io/BrianCross/pen/wvqZPEq">leaving behind artifacts</a>. <a href="https://greensock.com/forums/topic/30156-safari-v15-rendering-bug/">The official GSAP team’s response</a> was to add <code>will-change: transform</code> to the problematic element.</p><p>Justin Veiga came across <a href="https://codepen.io/jvwrx/pen/KKZNLeZ/07caad5021a72b787ea3e8567b9d7d30">an issue with Chrome on MacOS</a>, where a photo enlarging animation done with <code>transform: scale()</code> would display with noticeable jank. Adding <code>will-change: transform</code> resolves this.</p></section>]]></content>
        <published>2022-02-03T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Complete Guide to Accessibility Tooling]]></title>
        <id>https://www.nicchan.me/blog/a-complete-guide-to-accessibility-tooling/</id>
        <link href="https://www.nicchan.me/blog/a-complete-guide-to-accessibility-tooling/"/>
        <updated>2021-10-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[From code snippets to testing suites, this article rounds up a bunch of accessibility tooling]]></summary>
        <content type="html"><![CDATA[<p>I’m honored to have published an article on Smashing Magazine! As a little editors note, I’m really excited to have opportunities to write long form content and publish them in places where they may have more reach. This blog will still be updated, but content here is likely to have more personal opinions and musings on web development, or dive into niche topics rather than be fully instructional. Thank you for sticking with me on this journey!</p>
<p><a href="https://www.smashingmagazine.com/2021/06/complete-guide-accessibility-tooling/">Read “A Complete Guide to Accessibility Tooling” at Smashing Magazine</a></p>
<section><h2 id="translations">Translations</h2><ul>
<li><a href="https://habr.com/ru/company/htmlacademy/blog/572502/">Russian</a></li>
</ul></section>]]></content>
        <published>2021-10-24T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Tips for Focus Styles]]></title>
        <id>https://www.nicchan.me/blog/tips-for-focus-styles/</id>
        <link href="https://www.nicchan.me/blog/tips-for-focus-styles/"/>
        <updated>2021-01-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A series of notes on focus styles, an assortment of suggestions for both designers who may be looking for information on how to design focus styles, as well as tips for developers on implementing them. ]]></summary>
        <content type="html"><![CDATA[<p>Last updated on March 28, 2024</p>
<p>Below are a series of notes on focus styles: an assortment of suggestions for both designers who may be looking for information on how to design focus styles, as well as tips for developers on implementing them. This article assumes you have a basic level understanding of what focus indicators are, and why they are important, and that you just want to level up your understanding. If you want a great overview of what a focus indicator is and why it is important, you might want to check out <a href="https://www.deque.com/blog/give-site-focus-tips-designing-usable-focus-indicators/">this excellent article by Caitlin Geier over at Deque</a> first!</p>
<p>Unfortunately, <a href="https://adrianroselli.com/2017/02/avoid-default-browser-focus-styles.html">default browser focus styles may not be all that accessible</a>. In Firefox, the focus indicator is a 1 pixel wide dotted outline. In Safari, it shows up as a light blue ring. On sites where the brand color is a similar shade of blue, or even on a site where the background color is plain white, neither of these styles meet the <a href="https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html">required contrast ratio of 3:1 for non text elements</a>.</p>
<p><a href="https://blog.chromium.org/2020/03/updates-to-form-controls-and-focus.html">Chrome pushed an update to their focus styles</a> so that it displays both a solid dark color and a solid white ring. Although much improved, there are still certain cases where it may be difficult to perceive; for example, a black button on a white background would just appear as slightly wider.</p>
<p>As iffy as these styles may be for accessibility, you are technically not reponsible for the inaccessibilty of these styles. Your site can be fully compliant with the WCAG 2.1 (Web Content Accessibility Guidelines) yet have totally invisible focus styles, provided they are the ones provided by the browser. We really can do a lot better than that, and I would argue that anyone who cares about accessibility should go take that extra step rather than relying on inaccessible browser defaults. In any case, there are quite a few gotchas that often trip people up when designing and developing focus styles. Let’s dive into some common issues with custom focus styles.</p>
<section><h2 id="the-best-focus-styles-are-additive">The best focus styles are additive</h2><p>When I was just starting out, I often received designs with no interactive states at all! In many cases, it was on me to come up with hover and focus states. This led to me applying the same styling for all these states. While this is certainly better than having no focus states at all, the best focus states need to work in conjunction with other states. This is especially critical for things like checkboxes, where oftentimes a designer might design a hover or focus state that only works when the input is unchecked, but is incompatible with the checked/selected state.</p><p>By treating focus states as additive, you gain another benefit — on slower internet connections and devices, users may use the focus state as an indication that yes, the control has actually been pressed. On sites that are slow with high Time To Interactive metrics, I often find myself confused about whether or not a site is usable yet, and having an interface with carefully considered states may alleviate some of this confusion.</p></section>
<section><h2 id="rely-on-affordances">Rely on affordances</h2><p>An ‘affordance’ is a feature or property of a design that suggests how it is supposed to be used. In web design, radio buttons and checkboxes are both UI patterns that allow a user to select options. Checkboxes allow for multiple items to be selected, and radio buttons offer a single choice amongst the options. While their exact appearance differs across browsers, default browser radio buttons are always round and checkboxes are square. Similarly, although focus styles differ between browsers, all of them are some form of outline, so it’s best to design focus styles with this in mind.</p><p>I recently came across a case in the wild where designed focus styles that lack affordances were an issue for me. I’ve mocked up a recreation of the experience in the screenshots below.</p><figure><img alt="Zoomed in viewport that features navigation with two links, the last of which is focused, and a cut off hero section with a title." loading="lazy" decoding="async" sizes="(min-width: 1223px) 1223px, 100vw" style="object-fit:cover;max-width:1223px;max-height:789px;aspect-ratio:1.550063371356147;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_413,c_lfill,f_auto/v1611728128/focus-outline1 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_484,c_lfill,f_auto/v1611728128/focus-outline1 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_534,c_lfill,f_auto/v1611728128/focus-outline1 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_619,c_lfill,f_auto/v1611728128/focus-outline1 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_697,c_lfill,f_auto/v1611728128/focus-outline1 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1223,h_789,c_lfill,f_auto/v1611728128/focus-outline1 1223w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_826,c_lfill,f_auto/v1611728128/focus-outline1 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1076,c_lfill,f_auto/v1611728128/focus-outline1 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1239,c_lfill,f_auto/v1611728128/focus-outline1 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1321,c_lfill,f_auto/v1611728128/focus-outline1 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2446,h_1578,c_lfill,f_auto/v1611728128/focus-outline1 2446w" src="https://res.cloudinary.com/nicchan/image/upload/w_1223,h_789,c_lfill,f_auto/v1611728128/focus-outline1"><figcaption>Initial viewport</figcaption></figure><figure><img alt="Zoomed in viewport where the previously unseen button in the hero section is focused. There is no outline, but the background color of the button has changed." loading="lazy" decoding="async" sizes="(min-width: 1224px) 1224px, 100vw" style="object-fit:cover;max-width:1224px;max-height:799px;aspect-ratio:1.5319148936170213;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_418,c_lfill,f_auto/v1611728127/focus-outline2 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_490,c_lfill,f_auto/v1611728127/focus-outline2 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_541,c_lfill,f_auto/v1611728127/focus-outline2 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_627,c_lfill,f_auto/v1611728127/focus-outline2 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_705,c_lfill,f_auto/v1611728127/focus-outline2 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1224,h_799,c_lfill,f_auto/v1611728127/focus-outline2 1224w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_836,c_lfill,f_auto/v1611728127/focus-outline2 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_1089,c_lfill,f_auto/v1611728127/focus-outline2 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_1253,c_lfill,f_auto/v1611728127/focus-outline2 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_1337,c_lfill,f_auto/v1611728127/focus-outline2 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2448,h_1598,c_lfill,f_auto/v1611728127/focus-outline2 2448w" src="https://res.cloudinary.com/nicchan/image/upload/w_1224,h_799,c_lfill,f_auto/v1611728127/focus-outline2"><figcaption>After hitting tab from the last navigation link</figcaption></figure><p>This site had custom focus styles for their buttons, where the outline was removed and the background color of the button was changed. While the colors in question meet all contrast requirements, the button was not previously visible until the user scrolled, so it took me a bit of tabbing back and forth to realize what had changed. Users who rely on browser zoom may also encounter issues with this as well, as their viewport sizes may prevent them from seeing the previous unfocused state of the control. By relying on affordances and design conventions, we can ensure that the focus styles make sense out of context and that we aren’t relying on the visual difference between the unfocused and focused state.</p></section>
<section><h2 id="consider-adding-a-little-bit-of-breathing-room">Consider adding a little bit of breathing room</h2><p>Designers often request that focus styles be removed because a thick ring that butts up against the control isn’t the most visually appealing. For things like navigation links, you could consider adding padding to the link to ensure that there is some space between the outline and the content. This not only adds more breathing room to the design, but it also improves accessibility by increasing the tappable area of the control.</p><img alt="Side by side of two focused links, one with no padding and the other with some padding." loading="lazy" decoding="async" sizes="(min-width: 358px) 358px, 100vw" style="object-fit:cover;max-width:358px;max-height:109px;aspect-ratio:3.2844036697247705;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_358,h_109,c_lfill,f_auto/v1611728127/focus-outline3 358w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_195,c_lfill,f_auto/v1611728127/focus-outline3 640w,
https://res.cloudinary.com/nicchan/image/upload/w_716,h_218,c_lfill,f_auto/v1611728127/focus-outline3 716w" src="https://res.cloudinary.com/nicchan/image/upload/w_358,h_109,c_lfill,f_auto/v1611728127/focus-outline3"><p>If tap target size isn’t a concern or changing the padding isn’t feasible, you could use the <a href="https://caniuse.com/?search=outline-offset">outline-offset</a> CSS property, supported in all modern browsers. All you need to do is pass in an offset distance, such as <code>outline-offset: 0.25rem;</code>, and the outline will be drawn the specified distance away from the element. Alternatively, you could utilize multiple box shadows by having a very thick box shadow in the color you would like the outline to be, and layer a box shadow the color of the background on top of it! (I learned this trick from <a href="https://speakerdeck.com/a11ychi/if-its-interactive-it-needs-a-focus-style-with-eric-bailey?slide=48">Eric Bailey’s very cool slides on focus styles</a>.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">button</span><span style="color:#1F7D89;--shiki-dark:#83CDFF">:focus</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	box-shadow:</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0.5</span><span style="color:#4075BA;--shiki-dark:#B993FF">rem</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> white</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">,</span><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> /* background color here */</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> 0.75</span><span style="color:#4075BA;--shiki-dark:#B993FF">rem</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> blue</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">; </span><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">/* outline color here */</span></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">	/* this results in a blue outline 0.25rem wide,</span></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   offset 0.5rem from the element, as pictured previously! */</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>Another reason why you may wish to offset the focus: if you have a element against a background where the contrast ratio just barely meets the requirements, it can be impossible to find a third color for the outline that also meets contrast requirements against all adjacent colors. By adding an offset, you only have to worry about the contrast between one color, the background.</p></section>
<section><h2 id="dealing-with-overflow-hidden">Dealing with <code>overflow: hidden</code></h2><p>Sometimes, even default outlines may be unintentionally removed when the parent of the interactable control has <code>overflow: hidden</code> on it. This can happen on things like card user interfaces, where using <code>overflow: hidden</code> to clip overflowing content will also remove the outlines. You could try consider using a negative value passed to outline offset, such as <code>outline-offset: -.25rem;</code> or an inset box shadow (eg. <code>box-shadow: inset 0 0 0 0.25rem red;</code>) in order to render an outline without changing the css on the parent element.</p></section>
<section><h2 id="when-using-sticky-elements-use-scroll-padding-block-start-to-ensure-your-focused-elements-arent-covered">When using sticky elements, use <code>scroll-padding-block-start</code> to ensure your focused elements aren’t covered.</h2><p>If your design contains sticky elements, it’s possible for the sticky element to inadvertently overlap your focusable element.</p></section>
<section><h2 id="getting-an-outline-to-hug-the-element">Getting an outline to hug the element</h2><div class="post-callout"><h3 class="post-callout__title">Note</h3><p class="post-callout__text"><p>The following text about the outline not hugging the element is no longer
true as of Firefox version 88, Chrome version 94 and Safari 16.4. If you
need to support older browsers, considering follow the advice below, but
otherwise, you’re good to go!</p></p></div><p>Another reason why designers may request for the focus outline to be removed is because the default outline ignores rounded borders on elements. Thankfully, we aren’t actually limited to using the outline property for custom focus styles! This issue can be solved by using box-shadow instead of outline to account for the border radius.</p><img alt="Side by side of two focused rounded buttons, one using outline and the other with box shadow. The link with box shadow has the outline curving along the rounded corners." loading="lazy" decoding="async" sizes="(min-width: 556px) 556px, 100vw" style="object-fit:cover;max-width:556px;max-height:158px;aspect-ratio:3.518987341772152;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_556,h_158,c_lfill,f_auto/v1611728127/focus-outline4 556w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_182,c_lfill,f_auto/v1611728127/focus-outline4 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_213,c_lfill,f_auto/v1611728127/focus-outline4 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_235,c_lfill,f_auto/v1611728127/focus-outline4 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_273,c_lfill,f_auto/v1611728127/focus-outline4 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_307,c_lfill,f_auto/v1611728127/focus-outline4 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1112,h_316,c_lfill,f_auto/v1611728127/focus-outline4 1112w" src="https://res.cloudinary.com/nicchan/image/upload/w_556,h_158,c_lfill,f_auto/v1611728127/focus-outline4"></section>
<section><h2 id="its-probably-best-to-avoid-using-outline-none-even-if-you-override-focus-styles">It’s probably best to avoid using <code>outline: none</code>, even if you override focus styles</h2><p>Certain CSS properties are overriden in Windows High Contrast Mode, including borders, background colors and box shadows. A lot of custom focus styles are reliant on using these three properties and hiding the outline. In order to support high default contrast mode, <a href="https://sarahmhigley.com/writing/whcm-quick-tips/">Sarah Higley</a> advises that we don’t remove the outline completely with <code>outline: none;</code>. Instead, we can set the outline color to be transparent, so that the outline is invisible when not in high contrast mode, but overridden correctly in high contrast mode, where the transparent color will be replaced with a color set by the user. You could preserve the existing outline styles with <code>outline-color: transparent;</code>, or define a width and style with <code>outline: 0.25rem solid transparent;</code>.</p></section>
<section><h2 id="final-words">Final Words</h2><p>Focus styles are definitely something that should be considered from the beginning of the design phase. Because consistency and convention are key to designing successful focus styles, adding focus styles may not take that much more time to design and implement, but would add a lot of value in terms of making the interface useable for disabled users.</p><p>I certainly don’t think this piece is either the first or last piece of writing on the subject of focus styles (curse you all for stealing the good focus related puns!), but hopefully it gets the gears turning in your head for possible focus style options. I invite you to read up on the articles featured within this post and below, and, as always, test with users!</p></section>
<section><h2 id="further-reading">Further Reading</h2><ul>
<li><a href="https://css-tricks.com/focusing-on-focus-styles/">Focusing on Focus Styles</a> by Eric Bailey</li>
<li><a href="https://hacks.mozilla.org/2019/06/indicating-focus-to-improve-accessibility/">Indicating focus to improve accessibility</a> by Hidde de Vries</li>
<li><a href="https://developer.paciellogroup.com/blog/2018/03/focus-visible-and-backwards-compatibility/">:focus-visible and backwards compatibility</a> by Patrick H. Lauke</li>
<li><a href="https://www.deque.com/blog/accessible-focus-indicators/">Accessible Focus Indicators: Something to :focus on</a> by Aaron Pearlman</li>
</ul></section>]]></content>
        <published>2021-01-28T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Talk: UI patterns in existing web map widgets]]></title>
        <id>https://www.nicchan.me/blog/talk-ui-patterns-in-existing-web-map-widgets/</id>
        <link href="https://www.nicchan.me/blog/talk-ui-patterns-in-existing-web-map-widgets/"/>
        <updated>2020-10-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A talk I gave at the W3C/OGC Maps for the Web Workshop on map accessibility.]]></summary>
        <content type="html"><![CDATA[<div class="video" style="aspect-ratio: 16/9;"> </div>
<p><a href="https://wcag-maps.nicchan.me/">Standalone Slides</a></p>
<section><h2 id="introduction-000">Introduction (0:00)</h2><p>Hello everyone! My name is Nic Chan and I have been contributing to the Maps 4 HTML Community Group over the past year, with a particular focus on the Uses Cases and Requirements report. Today I will be presenting on the accessibility of existing map widgets and tools.</p></section>
<section><h2 id="definitions-019">Definitions (0:19)</h2><p>Before I get into the details of the analysis, I’d like to lay out some definitions that will frame this discussion.
The map tools discussed today fall into two general categories, widgets and client-side mapping APIs.
Widgets refer to an embeddable map, typically in an iframe or something similar, that requires minimal code to implement. These include things like the Google Maps embed that small business owners may use to share their store location.
Client-side Mapping APIs are JavaScript libraries that allow website developers to create more customized map viewers. They typically require the developer to do more configuration and instantiation work, with the benefit of offering more control over the end result. You would typically find these as a part of web apps like storefinders or delivery services.</p><p>In this presentation, I’d like to frame accessibility as the degree to which these maps are usable by disabled people. Some contexts may use the term accessibility to encompass other things, like cost and size of these tools and how that pertains to access, but in this case we will be focusing on the usability aspect, and especially how these mapping tools measure up to our next term, WCAG.</p><p>WCAG, short for Web Content Accessibility Guidelines, is a series of guidelines developed by the W3C in order to provide a shared standard for developing accessible web content. It is important to note that the WCAG is not the be all and end all of accessibility. When it comes to things like maps, a tool could meet every single criterion, but still not be accessible. There are many outstanding questions (which will be discussed in greater detail by other presenters and panelists) about what would constitute an equivalent experience to what a map can communicate for low vision and blind users. However, it is still worthwhile to examine the existing tools with the WCAG in mind, so we can account for other disabled users beyond low vision and blind users, such as people who have motor impairments. By looking at these existing tools, we can determine any common patterns worth adopting for a native HTML map element.</p><p>Not all WCAG criterion are relevant to web maps, so I’ve pulled out some of the most applicable and interesting ones, where the map tools meet these criterion with varying degrees of success.</p></section>
<section><h2 id="tools-reviewed-240">Tools Reviewed (2:40)</h2><p>The tools reviewed today include both widgets and libraries from vendors like Google, Bing, Apple, MapBox and TomTom, as well as some open source solutions with OpenStreetMap tiles. For a full breakdown of how each tool measures up and the details of each criterion, please visit Robert Linder’s full evaluation at <a href="https://tinyurl.com/wcag-maps">tinyurl.com/wcag-maps</a>. Robert is a member of the Maps4HTML Community Group, and his contributions have been invaluable.</p><p>With that out of the way, let’s begin!</p></section>
<section><h2 id="non-text-content-313">Non Text Content (3:13)</h2><p>5/11 tools failed.</p><p>This criterion states that all non-text content has an equivalent text alternative. None of the map tools offer any meaningful text alternative to the map content by default, so on that front, all of the maps would fail, unless context is provided by the site developer. As mentioned earlier, what this alternative content could look like is a very complicated question that depends on the content of the map, and warrants larger discussion.</p><p>Because developers and authors may choose to provide additional context, when evaluating this criteria, we are focused on text alternatives to controls other than the map itself. In this case, this criterion also applies to things like alternative text for logo images and making sure all icon buttons are labelled descriptively for screen reader users.</p><p>As an example, maps often use the plus and minus symbol to indicate zooming in and out. As a sighted user, I can see the visual context these buttons are in, and I can also see that the map zooms in and out when I press the buttons. However, if you just hear “Plus Button”, the context is a little more unclear, it could mean adding a pin to the map, or changing the quantity of another control. We can include context without changing the visual appearance of the control by adding things like alt text to images, or an aria-label to the control. A possible solution to resolve this example might be to add an aria-label to the button.</p></section>
<section><h2 id="info--relationships-448">Info &amp; Relationships (4:48)</h2><p>11/11 tools failed.</p><p>This criterion requires that all information, including a control’s state or structure, can be determined programmatically or is otherwise available through text.
Tools often failed to label the map’s structure as a distinct piece of content and as a control. A map generated by these tools is not just a static image, it is a control in and of itself. When the map has focus, most tools will allow you to do things like pan and rotate the map using keyboard shortcuts or your mouse pointer. Thus, the map needs to be labelled so that it is identifiable as a control, not just an image. In tool authors’ defense, there isn’t a set standard or consensus for how to approach this yet, as ‘map’ is not yet a valid component role, so there may be uncertainty on how to appropriately label this map container.
The other most common reason for failure is the lack of attention paid to control state. For example, all of these web maps allow users to zoom in and out to an extent. Once you’ve hit the zoom limit, the appropriate control should be disabled, but very few examples communicate this programmatically, and most only adjust the styles. Furthermore, screen reader users will often have no idea of the current zoom level. Bing stands out amongst the examples as it announces the current zoom level in addition to the buttons state, so a user of an audio interface will always know what the current status is.</p></section>
<section><h2 id="contrast-621">Contrast (6:21)</h2><p>9/11 tools failed.</p><p>To pass this criterion, all text should pass a minimum contrast ratio of 4.5 for regular sized text. The contrast ratio refers to the contrast between the text color and the background color. Because many widgets do not allow for color customization, having accessible defaults would go a very long way in terms of ensuring map accessibility. This contrast criterion can apply to both the map controls and the actual map content.
Some maps had text buttons that did not meet the contrast ratio. Fixing this is as simple as changing the text color on the buttons.
Most maps had contrast issues within the map content, particularly with the labels for bodies of water and minor roads, such as in this example here. This screenshot shows the ocean filled in with a light blue color, but the text on top of it that says ‘North Atlantic Ocean’ is a medium blue, and it never gets any darker or larger no matter how much you zoom in. Maps are in a tricky position as color hierarchy is very important for establishing focal points, and there is a need to deprioritize certain map features to avoid making the map too noisy. When a map is zoomed out, it is understandable if some minor labels are below the average font size, or have lacking color contrast. However, when a map is zoomed into an area, a good map may have these minor labels take visual priority and be displayed larger and with more contrast. Some maps included a thin white outline around each label to increase the text contrast, a technique that is commonly used in closed captions for TV.</p></section>
<section><h2 id="keyboard-802">Keyboard (8:02)</h2><p>7/11 tools failed.</p><p>Every functionality that is available to mouse or touch users must also be available for keyboard only users. This includes all interactable controls, as well as functions activated by pointer gestures, which most maps do have.
Most failures in this instance are due to the use of non-native controls. When a semantic element like a HTML button is used, all the behaviors that are expected of the control are handled automatically by the browser. It is very common for developers to add a click handler to a div element and ignore the other required behavior. Some custom buttons are not able to be accessed by hitting the tab key and cannot be activated because they cannot be accessed at all, whereas others can be focused, but do not have any means of activation through the keyboard.
A user with a pointer device can pan across the map by dragging the map interface. Some maps lack a way to do this with the keyboard, either because the map container is not focusable, or because they lack the keyboard shortcuts necessary.</p></section>
<section><h2 id="character-key-shortcuts-909">Character Key Shortcuts (9:09)</h2><p>1/11 tools failed.</p><p>This criterion states that if a keyboard shortcut is implemented using character keys, then at least one of the following is true:
A mechanism is available to turn the shortcut off; or
A mechanism is available to remap the shortcut to use modifier keys such as shift, control, alt, or
The keyboard shortcut is only active when that component has focus.
This criterion exists to prevent conflicts with assistive technology shortcuts and web interface shortcuts. The NVDA screen reader uses arrow keys to navigate text, and it may conflict with the use of arrow keys to pan and scroll the map. None of the web maps we analyzed used techniques #1 or #2. The web maps that have shortcuts and passed this criterion have opted for technique 3, only allowing keyboard shortcuts when the map container has focus.</p></section>
<section><h2 id="focus-order-1006">Focus Order (10:06)</h2><p>2/11 tools failed.</p><p>To pass this criterion, elements must receive focus in an order that matches the content reading order. In English and other left-to-right languages, we have an expectation that elements will be interactable from left to right, top to bottom, in the same order we read text.</p><p>Here is a diagram which displays the focus order of the Google Maps embed. The numbers indicate which element receives focus in what order. The first focusable element is the map container, which makes sense as the focus order should go from outward to inwards. Next is the Terms of Use button, which is in the bottom right hand corner. After that, comes the View Larger map button, which is in the opposite corner, in the top left. We return to both zoom control buttons in the bottom right, and finally, the unlabelled toggle satellite imagery button in the bottom left corner. In this case, the developers used styles to position the controls arbitrarily. Without proper focus order, the experience of tabbing through the map component is quite jarring and unnecessarily disorienting to bounce from corner to corner.</p></section>
<section><h2 id="focus-visible-1116">Focus Visible (11:16)</h2><p>7/11 tools failed.</p><p>This criterion is related to the previous one. The focus ring is an indicator that shows which interactive element currently has focus, an example of which is shown here. We can see the zoom out button currently has focus due to the thick black ring that Chrome applies. It is present by default in all browsers, but is common for developers to remove this indicator without replacing it with their own styles. In combination with the previous criterion, this criterion is serious enough to render a map component unusable for sighted keyboard-only users, because there is no way for them to determine what element is currently selected.</p><p>In some cases, the focus isn’t persistently visible, which is due to the addition of scripts that remove the outline once the element is focused. This criterion is an example of the default behavior being accessible, and the developers have overridden this default behavior to make an ultimately inaccessible design choice</p></section>
<section><h2 id="language-of-parts-1221">Language of Parts (12:21)</h2><p>9/11 tools failed.</p><p>The requirement to pass this criterion is fairly simple, all it requires is that the content’s language is included in the markup. This is important for many accessibility reasons, such as making sure screen readers read words with the correct intonation, and can be useful for features like on-the-fly language translation. Despite the fact this criterion is simple to achieve, many vendors fail to implement this. Passing this criterion is as simple as adding a single lang attribute to the containing element, and lang attributes to text in other languages.</p><div class="post-callout"><h3 class="post-callout__title">Motion Warning</h3><p class="post-callout__text"><p>The next slide has a gif which demonstrates an unexpected scrolling
appearance. Viewers with vestibular issues may wish to look away for a
second.</p></p></div></section>
<section><h2 id="on-input-1311">On Input (13:11)</h2><p>2/11 tools failed.</p><p>This criterion states that changing any setting in a user interface component should not trigger any changes of context unless the user has been warned about it. In layman’s terms, if a control is used, there shouldn’t be any unexpected side effects like changing the viewport size. Here we have a gif where the up and down arrow keys are being used to pan the map, but unfortunately the viewport scrolls up and down as well. This can be quite disorienting, so it’s important to test for side effects like this.</p></section>
<section><h2 id="conclusion-1346">Conclusion (13:46)</h2><p>Building accessible web maps is no easy task. The base level of accessibility for existing web map solutions is quite poor overall, and no one widget or library stands out as being far superior to the rest. Criteria where almost every tool fails are often due to the lack of standardization, and this is where having a native HTML map element could really set the bar for what map widgets should strive to be.</p><p>Regardless, there are quite a few quick and easy wins to be had. I hope any developers who work with these map libraries can consider implementing some of the low-hanging fruit I’ve brought up here.</p><p>Thank you! If you have any questions about the material covered, feel free to reach out to me at <a href="mailto:nic@nicchan.me">nic@nicchan.me</a>.</p></section>]]></content>
        <published>2020-10-09T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Usability Testing Popular Shopify Themes]]></title>
        <id>https://www.nicchan.me/blog/usability-testing-popular-shopify-themes/</id>
        <link href="https://www.nicchan.me/blog/usability-testing-popular-shopify-themes/"/>
        <updated>2020-01-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A deep dive into the accessibility of the Shopify theme landscape.]]></summary>
        <content type="html"><![CDATA[<p>In recent years, there has been a <a href="https://www.essentialaccessibility.com/blog/web-accessibility-lawsuits/">sharp increase in the number of lawsuits filed in response to inaccessible websites.</a> As a developer who works primarily with Shopify, I have seen an increase in questions from merchants related to accessibility, some of them from merchants who were facing lawsuits themselves.</p>
<p>As Shopify is popular with small business owners who can’t afford a custom-built store, I began to wonder about the state of Shopify themes. What sort of options were out there for a merchant looking to purchase an existing theme from the theme store, and what could I safely recommend to merchants so they could have an accessible storefront from the beginning? I decided I would conduct some <strong>basic</strong> accessibility testing of all the themes on the homepage of the theme store to get an overview of what the theme landscape looked like.</p>
<section><h2 id="methodology">Methodology</h2><p>For this article, I chose to test the themes for a specific task using two testing methods. First, I would try to add a product to my cart and reach checkout with my monitor obscured using only the NVDA screen reader on Chrome, and then I conducted the same test with the display unobscured but with the keyboard only, no mouse, trackpad or other pointer devices. This could be described as a simplified version of a task-oriented walkthrough.</p><div class="post-callout"><h3 class="post-callout__title"><p>Disclaimer</p></h3><p class="post-callout__text"><p>Proper accessibility testing takes a long time. To be able to fully evaluate a website’s accessibility, it needs to be tested with actual users on a variety of browsers and assistive technology combinations, ideally against the <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1.</a> I only tested these themes on my own, for a single task, so take my findings with a grain of salt!</p></p></div><p>The list of themes consists of every single theme on the <a href="https://themes.shopify.com/">Shopify theme store</a> home page, pulled from some time in April. 32 themes were tested in total, and testing was completed sometime in May, so bear in mind that some themes may have been updated since then, especially with increased discussions about the Domino’s Supreme Court case.</p><p>Where a theme had more than one color preset, I opted to test the first one. Most themes have 3-4 presets and the difference between them usually consists of different configurations of fonts and colors. While I certainly think that the choice of fonts and colors can be very revealing in terms of demonstrating a developer’s commitment towards accessibility, almost all themes allow the merchant to change the site’s fonts, font sizes and color palettes; thus merchants are able to improve these kinds of inaccessible theme defaults. As a result, I did not conduct any automated testing for things like color contrast or font sizes on these demo stores, and I only focused on whether or not I could complete the task at hand.</p><p>If at any point, I lost track of focus for more than a few tab stops, I would consider the keyboard test a fail. Your users will not have enough patience to debug your website’s problems, even if I personally continued trying in order to determine further issues with the theme.</p></section>
<section><h2 id="testing-steps">Testing Steps</h2><ol>
<li>Land on the homepage</li>
<li>Using the main navigation, try to navigate to a collection page. If the navigation contains any submenus, the collection should be selected from a submenu. Expandable elements are often coded incorrectly and do not consider anything beyond the mouse/trackpad experience.</li>
<li>From the collection page (essentially a page where multiple products are listed ), try to navigate to a product that is not the first product on the page.</li>
<li>If a product variant (size, color, etc) was available, try to select something other than the default variant. This is often where theme developers will try to implement custom form controls, which are prone to accessibility failures.</li>
<li>Add the item to the cart. While some themes contain ‘Buy it now’ buttons that immediately take you to checkout, a user may want to purchase more than one item, thus we should test the cart process.</li>
<li>Navigate to the cart (this may be a cart component on the same page or a separate page depending on the theme)</li>
<li>Review the order and press checkout. The test ends here, as the Shopify checkout is standardized and is rigorously tested for accessibility by Shopify.</li>
</ol></section>
<section><h2 id="common-points-of-failure">Common Points of Failure</h2><p>When asked why they do not bother to make accessible websites, developers often complain that it is too difficult to make things accessible because of the many niche bugs that occur with particular screen reader and browser combinations. In my experience, this couldn’t be further from the truth. Of all the failures I encountered, not one was due to an edge case scenario with my browser/screen reader combination, and the vast majority of errors came from ‘low-hanging fruit’. These kinds of errors are not difficult to fix. Semantic HTML and appropriate use of CSS can resolve most of these issues without complicated hacks or dependencies. Below is a summary of the most common reason why themes failed, with some resources that can help you learn how to resolve these common issues.</p><section><h3 id="no-focus-styles">No Focus Styles</h3><p>The vast majority of navigation failures for the keyboard only test were due to the fact I lost track of where the keyboard focus was immediately. The solution to this is as simple as <strong>not</strong> adding <code>*:focus { outline: none; }</code> to your code in order to display the default focus outline, but a nicely designed one can often be an easier sell in terms of clients wanting to retain a certain brand aesthetic, and be more visible than default browser options.</p></section><section><h3 id="inaccessible-dropdown-menus">Inaccessible Dropdown Menus</h3><p>Many e-commerce sites contain menus that are displayed when the parent link is hovered. Not only do many themes neglect to show the menu on keyboard focus, they also neglect to consider the usability of this pattern for tablet users. Developers should consider whether or not a <a href="https://w3c.github.io/aria-practices/examples/disclosure/disclosure-navigation.html">disclosure widget style navigation</a> is more appropriate in this situation. If other stakeholders insist on showing the menu on hover as opposed to click, consider using the <a href="https://www.scottohara.me/blog/2017/05/14/focus-within.html">:focus-within</a> selector in order to allow keyboard-only users to at least be able to navigate the menus. (Disclaimer: if you have more than a few links in your dropdowns, definitely consider the disclosure widget pattern, as it may constitute a failure of <a href="https://www.w3.org/WAI/WCAG21/Understanding/bypass-blocks.html">2.4.1 Bypass Blocks.</a></p></section><section><h3 id="not-hiding-off-screen-content-properly">Not Hiding Off-screen Content Properly</h3><p>A lot of themes rely on content that may be hidden initially to the user, and displayed upon interaction, such as dropdown menus, quickshop modals, and etc. These UI patterns are frequently animated in from their initial hidden state, with developers utilizing properties like opacity and transforms. Although changing these properties may hide content from a sighted user, it is important to also also ensure that this hidden content is also hidden for screen reader users and keyboard only users. <a href="https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html">This is a great summary of various approaches</a>, but the gist is that <code>visibility: hidden</code> respects CSS transitions, and can be used in conjunction with the animateable CSS properties in order to hide content for all users.</p><p>During my testing there were many times where my keyboard focus got stuck in an off-screen hamburger menu, and I was forced to give up.</p></section><section><h3 id="unlabelled-icons-and-interactable-elements">Unlabelled Icons and Interactable Elements</h3><p>Icons in interfaces are becoming increasingly popular, but unless they are clearly labelled for screen reader users, they can be extremely confusing to navigate. Many times, icon buttons came in groups (eg. social media links), so I was left trying to make sense of what “Clickable Clickable Clickable” meant. If you are implementing icon buttons or links, you can <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">label them for screen reader users</a> without changing the design, but bear in mind that <a href="https://twitter.com/mattcutts/status/1099901408965525504">icons are likely not as clear as you think they are.</a></p></section><section><h3 id="aria-mysteries">ARIA mysteries</h3><p>There were several themes where the well-meaning developer had attempted to use ARIA roles to supplement their markup. Unfortunately, ARIA is often not well-understood by developers, and incorrect use of ARIA can lead to mismatched user expectations. One theme had a mega menu where every link was marked up as a <code>role=&quot;menuitem&quot;</code> within several levels of <code>role=&quot;menu&quot;</code>s, resulting in every link being announced as ‘menu item submenu menu menu item submenu link [LINK NAME]’. This is extremely verbose, and an <a href="https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html">incorrect use of the ‘menu’ and ‘menuitem’ roles.</a> If you aren’t 100% sure what ARIA is doing for your markup, it’s best to leave it out completely rather than botching what would have been an accessible experience. <a href="https://www.24a11y.com/2019/pouring-aria-into-the-html-element-specs/">The HTML spec</a> has recently been updated to include a new ‘Accessibility Considerations’ section that can help you determine whether or not a certain role is applicable in your situation.</p></section><section><h3 id="inaccessible-custom-form-controls">Inaccessible Custom Form Controls</h3><p>Many merchants sell products that have multiple ‘variants’. Different options like sizes or colors are presented as form options that a customer can toggle between. Developers often try to implement custom controls for these form options in order to maintain complete control over the way these form options look, but these attempts can cause accessibility issues in several ways. Below is a list of some of the ways in which these form controls failed:</p><ul>
<li>Options were not keyboard accessible due to use of unsemantic elements, rendering a user completely unable to select any options at all</li>
<li>Custom form controls (most commonly, color swatches and image thumbnails) did not have any textual label, so screen reader users had no idea what they were selecting</li>
<li>Custom <code>&lt;select&gt;</code> elements that resembled modal dialogs did not move focus appropriately or have keyboard interactions</li>
</ul><p>By implementing custom controls, developers lose a ton of value offered by using proper form controls. Because the form state and submission process are dealt within proprietary JavaScript, instead of within the form itself, developers not only create additional work for themselves on the accessibility front, they also make it extremely difficult to introduce third party code (such as from apps or external developers) that can extend the functionality of a theme. This results in a theme that is more difficult to maintain, less flexible for merchants and a poor experience for disabled users.</p><p>Styling form controls have come a long way; a lot more is possible now, with strong consistency across browsers. Instead of potentially botching your own attempt at a custom form control, consider referencing examples of <a href="https://scottaohara.github.io/a11y_styled_form_controls/">styled form components</a> and incorporating them into your work. For Shopify product variants in particular, a lot of common UI patterns like color swatches can be represented by <a href="https://scottaohara.github.io/a11y_styled_form_controls/src/radio-button/">radio buttons</a> because of their ability to select a single option among a group.</p></section><section><h3 id="bad-focus-management-and-dead-end-ajax">Bad Focus Management and Dead-end AJAX</h3><p>AJAX cart interactions are extremely common in e-commerce these days, as they allow the customer to complete actions without having to wait for the page to reload. Unfortunately, far too many sites do nothing to manage the keyboard states. In the rare instance where I was able to reach the Add to Cart button, I would often hit the button excitedly only to find that nothing at all would appear to happen. What an anticlimactic experience! Sighted users would be able to see either a notification window or an AJAX cart modal/drawer pop up, but if the focus is not moved, screen readers experience no indication that anything has happened.</p><p>AJAX interactions aren’t the only place where we encounter issues like these. Many modern user interfaces tend to include complex interaction patterns such as modals, but many implementations will just toggle the visibility of the modal and fail to follow any of the guidelines for <a href="https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-7">keyboard interaction</a>. If they’re lucky, a keyboard only or screen reader user may eventually be able to tab their way into the target component, but you would be lucky to hold their patience for that long.</p></section></section>
<section><h2 id="results-tables">Results Tables</h2><p>Horizontal headers represent the developer of the theme, and vertical headers represent where in the testing process the first accessibility failure took place. The later on in the process, the “better” a theme is, but given that the process of purchasing an item is integral to an e-commerce store, anything below a “pass” should be unacceptable.</p><dl><dt>Navigational failures</dt><dd><p>Refers to when I could not even navigate to a different page due to missing styles or inaccessible menu components.</p></dd><dt>Product failures</dt><dd><p>Refers to when I could not navigate to a product from the collection page or could not select a product variant.</p></dd><dt>Add to cart failures</dt><dd><p>Refers to when I was not redirected to the cart after adding the product to the cart, or any checkout-preventing errors within the cart.</p></dd></dl><section><h3 id="keyboard-results">Keyboard results</h3><table><thead><th></th><th scope="col">Shopify</th><th scope="col">Third-Party</th></thead><tr><th scope="row">Navigation Failure</th><td>0</td><td>11 (42.3%)</td></tr><tr><th scope="row">Product Failure</th><td>0</td><td>4 (15.4%)</td></tr><tr><th scope="row">Add To Cart Failure</th><td>1 (16.7%)</td><td>6 (23.1%)</td></tr><tr class="row--success"><th scope="row">Pass</th><td>5 (83.3%)</td><td>5 (19.2%)</td></tr></table></section><section><h3 id="screen-reader-results">Screen Reader results</h3><table><thead><th></th><th scope="col">Shopify</th><th scope="col">Third-Party</th></thead><tr><th scope="row">Navigation Failure</th><td>0</td><td>9 (34.6%)</td></tr><tr><th scope="row">Product Failure</th><td>0</td><td>2 (7.7%)</td></tr><tr><th scope="row">Add To Cart Failure</th><td>3 (50%)</td><td>11 (42.3%)</td></tr><tr class="row--success"><th scope="row">Pass</th><td>3 (50%)</td><td>4 (15.3%)</td></tr></table></section></section>
<section><h2 id="analysis">Analysis</h2><p>I didn’t want to critique individual theme developers with this exercise, but I did notice that themes built by Shopify performed noticeably better than than third-party themes. While many larger third-party developers had frequently asked questions or pages on their websites about accessibility, these pages often had expressed superficial commitment, and placed the burden on disabled people to report all errors, without making any effort to make these pages themselves accessible. You can see what a predicament this places disabled people in, and it allows accessibility to be deprioritized by false statistics (ie. “We haven’t had any complaints about this!”)</p><p>On the other hand, Shopify’s accessibility team is small but mighty. <a href="https://twitter.com/svinkle">Scott Vinkle</a> and <a href="https://twitter.com/devonpersing">Devon Persing</a> are both wonderful people who are committed to advocating for accessibility beyond their roles at Shopify. The codebases at Shopify are an order of magnitude larger than those by the average theme developer, and the fact that Shopify still came ahead in these tests is a testament to both their hard work and Shopify’s increasing commitment to accessibility.</p><p>It’s vital for Shopify to continue to model best practices. When reviewing the source code for the themes, I observed that many theme developers would often copy the markup and JavaScript for complex components from a Shopify theme to use as a functional starting point for their themes. Because of this, Shopify’s theme accessibility efforts not only benefit stores that use Shopify’s own themes, but also, the themes built by developers who seek to emulate Shopify’s work.</p><p>Theme developers should not regard Shopify’s relative successes in accessibility as a case of ‘let’s bring on a single person to do all of the accessibility for us!” It should never be the burden of a single individual to ensure accessibility. Company-wide buy-in is necessary not only because it takes only one small error to render a site unusable, but more importantly, the people who do work that falls under the umbrella of ‘Diversity and Inclusion’ should not be placed in a position where they are seen as constantly rejecting and critiquing the work of other people in the company. When we move beyond this idea of just ‘compliance’ and integrate accessibility holistically into the entire process of building things, we might finally be able to move the field forward.</p><p>Finally, let’s revisit the initial question. What sort of options are out there for a merchant who is looking for an accessible theme? Scott tells me that so far, <a href="https://themes.shopify.com/themes/debut/styles/default">Debut</a>, the theme that all stores start with, has undergone rigorous user testing and is updated to meet WCAG 2.1. Other themes by Shopify will undergo the same process eventually, but I’ve already noticed improvements in the time it’s taken me to write this article. Sometime in the future, <a href="https://www.shopify.com/accessibility/plan">baseline accessibility requirements</a> may be imposed on all themes and apps within the ecosystem, so it’s wise to start thinking about accessibility now. Until this day comes, the options are fairly limited if a merchant wants to pick a WCAG 2.1 compliant theme - either use Debut out of the box, have a developer experienced with accessibility customize it to your needs, or opt for a custom theme. I hope that more developers in this space will see the need for options, and rise up to the challenge.</p></section>]]></content>
        <published>2020-01-20T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[What Tumblr Taught Me About Accessibility]]></title>
        <id>https://www.nicchan.me/blog/what-tumblr-taught-me-about-accessibility/</id>
        <link href="https://www.nicchan.me/blog/what-tumblr-taught-me-about-accessibility/"/>
        <updated>2019-08-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Things I've learned about accessibility from my many years on Tumblr.]]></summary>
        <content type="html"><![CDATA[<p>As someone who was a teenager during its peak, Tumblr has had an undeniable influence on my life. Like many people my age, my first exposure to the concepts of ‘accessibility’ and ‘ableism’ was through Tumblr. In sharp contrast to the web accessibility community, where we often focus on technical details, meeting clear criterion and legal compliance, Tumblr’s disability community focused on more human facets of accessibility by practicing accessibility in a variety of creative ways. Even when presented with access barriers created by the inaccessibility of Tumblr as a platform, Tumblr as a community has found unique ways to support disabled users. As developers, we need to learn from our mistakes by finding out where our users compensate for our deficiencies, and learn from how disabled communities support themselves.</p>
<section><h2 id="on-disability">On ‘Disability’</h2><p>When I was a child, my understanding of disability was very binary and limited. You were either disabled or not disabled; my younger self might imagine someone in a wheelchair, or someone with a prosthetic limb. My understanding changed entirely when I met Maddy Ruvolo, a disability activist whom I had the pleasure of meeting in college. Maddy was the co-founder and president of the school’s Disability, Illness, and Difference Alliance, and she wrote her senior thesis on chronically ill youth and the role that Tumblr played as a model of disability community.</p><p>Because I am the kind of big nerd who enjoys reading her friends’ theses, it was through her and the many subsequent hours I spent browsing Tumblr that I began to reevaluate my limited, abled conception of disability. It may be tempting to try to categorize experiences of disability into three distinct categories; [1] what we might call more visibly disabled people, [2] chronically ill people, and [3] mentally ill people. Over time, I came to understand that while there may be significant differences between these three experiences, there is also significant overlap among their access needs and struggles. <strong>The boundaries between these three categories are so blurry that we cannot afford to make generalizations about what we perceive these groups to be</strong>.</p></section>
<section><h2 id="what-is-tumblr">What is Tumblr?</h2><p>Tumblr, a microblogging platform popular amongst young people, was always a place where niche communities could flourish, and discussion of topics such as race, sexuality and gender was frequent and widely shared. Among these communities were those that discussed various aspects of disability, particularly chronic illness and mental illness.</p><p>Unfortunately, Tumblr’s own terrible web accessibility has excluded a very large portion of the disability community, primarily by being completely unusable to those who use assistive technologies such as screen readers and keyboard only users. As a result, the discussion surrounding disability on Tumblr has been shaped by the absence of disabled people who were more likely to be experts in the technical facets of web accessibility. While I think that Tumblr (and all platforms) should commit to complying with the Web Content Accessibility Guidelines (WCAG), there are still valuable lessons to be learned by examining the idea of accessibility from different, non-technical perspectives.</p></section>
<section><h2 id="tumblr-and-third-party-apps">Tumblr and Third Party Apps</h2><p>Often maligned as ‘the blue hellsite’ by its own user base, Tumblr as a platform has always had a hostile relationship with the majority of its users. Tumblr users reading this post may remember the time they removed the ability for users to publicly reply to each other’s posts. <a href="https://support.tumblr.com/post/131951272032/pardon-our-dust-hey-you-know-the-reply-feature">Tumblr’s vague official announcement post</a> did not go over well, and users were left to speculate whether the new instant message/direct message (DM) feature was really going to replace the ability to <em>reply</em>. Imagine if Twitter removed the ability to respond to tweets and insisted that DMs were a replacement! Although Tumblr did eventually bring replies back, this was just one incident in a long series of disappointing management decisions from Tumblr. Most recently, Tumblr’s mass purging of ‘adult content’ has de-platformed many queer creators, regardless of whether or not their content actually contained ‘adult content’, further contributing to the toxic idea that queer people are inherently inappropriate for children.</p><p>Long-time Tumblr users may also recall using Tumblr-specific extensions in order to improve their browsing experience. <a href="https://bjornstar.com/tagged/tumblr+savior/page/6">Tumblr Savior</a> was among these extensions, and for many users, it lived up to its name and made Tumblr into a usable experience. Originally released in 2008, Tumblr Savior functioned similarly to Twitter’s muted words, though it was even more powerful because it searched the full contents of the HTML when deciding whether or not to hide a post. This muting mechanism was helpful as instead of relying on the poster to tag posts with specific tags, Tumblr Savior would hide anything that mentioned the offending topic within the body of the post.</p><p>Most abled users would use this blocking feature to hide things like TV show spoilers or mentions of a fandom they didn’t particularly care for, but many disabled people considered Tumblr Savior a necessary tool in order to use the platform at all. For example, people with motion sensitivities or conditions like epilepsy could choose to universally block all gifs in order to avoid potential triggers. Similarly, people who experience trauma/Post Traumatic Stress Disorder (PTSD) or certain phobias could avoid topics which may induce panic attacks and other negative responses.</p><p>Muting mechanisms and preference controls are vital tools for accessibility. Platforms that host content, whether they be social media networks, news outlets or something else entirely, have an obligation to allow users to control their own experiences. Whether it be someone hoping to browse Instagram without triggering their eating disorder, someone trying to keep up with their relatives without being subject to a seizure-inducing gif, someone avoiding the details of a recent celebrity suicide for their own mental health or someone with a serious phobia of balloons, we all have a right to control what content we consume.</p><div class="post-callout"><p><p>Platforms do not have to take it upon themselves to block or censor any
content that may negatively affect users, but providing a way for users to
opt out of content is absolutely an accessibility issue, especially when
<a href="https://www.npr.org/sections/codeswitch/2018/01/14/577664626/making-the-case-that-discrimination-is-bad-for-your-health">marginalized
communities</a>,
who experience <a href="https://www.washingtonpost.com/health/trumps-presidency-may-be-making-latinos-sick/2019/07/19/4e89b9f0-a97f-11e9-9214-246e594de5d5_story.html">trauma and anxiety at higher
rates</a>,
continue to be the targets of hate crimes and political attacks, both online
and offline.</p></p></div><p>Third-party Tumblr apps once provided a number of other features that allow disabled users to overcome Tumblr’s shortcomings. Missing E and XKit (both of the original apps are now defunct, though <a href="https://github.com/new-xkit/XKit">XKit lives on with a new team behind it, they’re always looking for contributors</a>) were two of the most popular more full-featured extensions. In addition to providing muting and advanced blocking implementations for better content filtering and harassment prevention, these apps contained features such as one click reblogging and auto-tagging. One-click reblogging allowed for users to share a post to their own blog very quickly, and auto-tagging would allow users to apply appropriate tags to a post with minimal hassle. In her thesis, Maddy argued that because users with chronic illnesses often experience fatigue issues, features like one-click reblogging enabled people who may be too exhausted to leave the house to engage in some form of social interaction, however small, while still accommodating for their followers’ triggers by automatically tagging posts when necessary. As abled people, we often regard features like this as a convenience for most users, but they can be necessary for disabled users to experience the app at all.</p><figure><img alt="Popup that begins with the text: Hello! We've noticed you're using a browser hack, Missing-E, that can cause serious problems for you and for Tumblr. It contains a long scrollable list of the potential 'problems' that Missing-E creates." loading="lazy" decoding="async" sizes="(min-width: 716px) 716px, 100vw" style="object-fit:cover;max-width:716px;max-height:675px;aspect-ratio:1.0607407407407408;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_603,c_lfill,f_auto/v1610102431/tumblr-missing-e 640w,
https://res.cloudinary.com/nicchan/image/upload/w_716,h_675,c_lfill,f_auto/v1610102431/tumblr-missing-e 716w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_707,c_lfill,f_auto/v1610102431/tumblr-missing-e 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_781,c_lfill,f_auto/v1610102431/tumblr-missing-e 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_905,c_lfill,f_auto/v1610102431/tumblr-missing-e 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1018,c_lfill,f_auto/v1610102431/tumblr-missing-e 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_1207,c_lfill,f_auto/v1610102431/tumblr-missing-e 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1432,h_1350,c_lfill,f_auto/v1610102431/tumblr-missing-e 1432w" src="https://res.cloudinary.com/nicchan/image/upload/w_716,h_675,c_lfill,f_auto/v1610102431/tumblr-missing-e"><figcaption>Screenshot of the pop-up presented to Missing-E users</figcaption></figure><p>You might think that Tumblr would look favorably upon the developers who spent countless unpaid hours working to make their platform more usable, but instead, Tumblr actively waged war with third-party app developers. In 2011, users with the Missing E extension installed were greeted with a pop-up that warned against using a ‘browser hack’, which Tumblr claimed could lead to disastrous consequences such as data loss. Tumblr ultimately banned Missing E, despite <a href="https://observer.com/2011/09/missing-e-developer-keeps-trying-to-work-with-tumblr-but-tumblr-keeps-trying-to-shut-him-down/">the developer’s attempts to work within the guidelines that Tumblr had set</a>.</p><p>In 2017, half a decade later, Tumblr finally released a post filtering feature. In comparison to some of the third-party extensions, Tumblr’s solution only enabled users to filter by tags. Relying on users to tag things consistently is inferior to full text search (imagine if Twitter’s muted words only worked for hashtags). The more comprehensive Chrome extension <a href="https://chrome.google.com/webstore/detail/tumblr-savior/oefddkjnflmjbclpnnoegglmmdfkidip">Tumblr Savior</a> still has over 150,000 installs today despite the fact it no longer works consistently and was last updated <em>over a year ago</em>.</p></section>
<section><h2 id="the-unsemantic-web">The Unsemantic Web</h2><p>Generally speaking, platforms like Tumblr are often hostile to third-party developers, and by proxy, disabled users. This can manifest in ways that are overt, as with Tumblr’s active attempts to block third party extensions, or in more subtle ways, such as when a platform makes engineering decisions in order to achieve some goal like improving developer convenience or thwarting adblockers, but with negative consequences for accessibility.</p><figure><img alt="Facebook's awful awful code. You do not want me to transcribe it." loading="lazy" decoding="async" sizes="(min-width: 612px) 612px, 100vw" style="object-fit:cover;max-width:612px;max-height:909px;aspect-ratio:0.6732673267326733;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_612,h_909,c_lfill,f_auto/v1610102431/tumblr-facebook 612w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_951,c_lfill,f_auto/v1610102431/tumblr-facebook 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_1114,c_lfill,f_auto/v1610102431/tumblr-facebook 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_1230,c_lfill,f_auto/v1610102431/tumblr-facebook 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_1426,c_lfill,f_auto/v1610102431/tumblr-facebook 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_1604,c_lfill,f_auto/v1610102431/tumblr-facebook 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1224,h_1818,c_lfill,f_auto/v1610102431/tumblr-facebook 1224w" src="https://res.cloudinary.com/nicchan/image/upload/w_612,h_909,c_lfill,f_auto/v1610102431/tumblr-facebook"><figcaption><p>Screenshot of the code used for Facebook’s ‘Sponsored’ link</p></figcaption></figure><p>Facebook’s ‘Sponsored’ link is a particularly egregious example of this. The sponsored link is a label on a post to indicate the content is sponsored. In order to display the word ‘Sponsored’ without directly displaying the word in its entirety, for fear of allowing detection by ad blockers, Facebook added dozens of extraneous letters in nested <code>&lt;span&gt;</code> tags in hopes of making it difficult for ad blockers to parse the text. Until recently, the redundant code also made it difficult for assistive technologies to parse the text, resulting in some screen readers reading out ‘S-P-O…’ etc. as individual letters. After several months, Facebook appears to have resolved this (at least for the NVDA screen reader on Chrome), a minor victory.</p><p>Keeping things simple and semantic is key to writing good code. ‘Semantic’ in a web development context generally refers to using the right kind of HTML element for the job. Using the proper elements in the right context provides affordances for things like search engines and assistive technology, allowing them to convey meaning to the end user. In Facebook’s case, their 50+ line monstrosity can be replaced with a single tag, <code>&lt;span&gt;Sponsored&lt;/span&gt;</code>. Overengineered solutions can make things inaccessible to users and dramatically increase the size of the page, slowing down the website and using up more data.</p><p>In addition to writing bloated, unsemantic code in order to thwart adblockers, Facebook’s code architecture decisions made it difficult for third-party developers to create accessibility fixes. For example, Facebook uses a CSS-in-JS solution that generates class names. CSS is a styling language that changes the appearance of elements on the page. We typically attach these styles through class names, so writing something like <code>.box { background-color: red }</code> would make all elements with the class name ‘box’ have a red background.</p><p>Third-party developers often rely on consistent class names in order to create extensions that last beyond a single update, but Facebook has prioritized their engineering needs over their end-users. Creating a scalable, easy-to-use naming system is a difficult task, as applications like Facebook have several thousand unique components that must be named in order to prevent styles from unintentionally affecting other parts of the page. Many CSS-in-JS solutions, including the one Facebook has chosen, take care of the naming process for you, and thus instead of having names like ‘box’, we get non-descriptive names like ‘q_q25znkklu’, as found in the screenshot above. While it is not necessary for class names to be meaningfully named, generated class names are often regenerated when changes are made. Any third party extensions that rely on those class names will have to be updated to accommodate the change, even if the actual styles themselves do not need to be updated. The most popular <a href="https://userstyles.org/styles/browse/facebook">themes for Facebook</a> are updated several times a week in order to keep up with Facebook’s changes. Maintaining a theme for one of these applications is a thankless and time-consuming task, and many developers are forced to give up.</p><p>Although changing one’s theme to feature Game of Thrones characters is likely not done to improve accessibility, many user-developed custom stylesheets are designed for accessibility purposes. Dark themes can reduce eye fatigue and headaches for people who have to look at their screens for a long period of time, and can be especially helpful for those with light sensitivities and or those who experience migraines. Fonts can be changed to better support readers who require larger fonts or fonts with better contrast (such as for <a href="https://chrome.google.com/webstore/detail/dyslexia-extension/jgkgailgjdefcllgbadjkjneobkfoopb">dyslexic users</a>), and user interfaces can have distracting (animated or otherwise) elements removed in order to help people with ADHD or other sensory disorders better focus on the task at hand.</p><p>As web developers, our first impulse might be to try and distill all these experiences into an actionable list. As tempting as it is to treat accessibility as a checklist, it does not work that way. As humans, we are complex and our access needs may conflict with one another. For example, I personally prefer dark themes and have a screen color temperature adjuster on at all times in order to manage the eye fatigue and headaches that often happen when you spend all of your time staring at a screen. However, some people experience glare, blurriness or streakiness when looking at light text against dark backgrounds, and dark themes would therefore be unusable for them. Even in the rare case that a platform provides both dark and light theme options, the size of a platform like Tumblr guarantees that some people will have conflicting access needs. People with multiple disabilities often have to make sacrifices by choosing between features that may support them in one area, but worsen another.</p><p>I don’t believe that developers for large platforms like Facebook and Tumblr are sitting in a room discussing how to make their sites as inaccessible as possible. They face attacks from a variety of bad actors, and their ever-changing, complicated codebases may be part of an active attempt to thwart these attacks. However, we cannot let our desire to fend off potentially malicious technology override the needs of actual users.</p><p>When we make websites difficult for bots and scrapers to use, we have the unintentional side-effect of making website difficult for assistive technologies to use. Tools such as screen readers and magnification software are widely classified as assistive technology, but custom extensions and stylesheets should also fall under that category. <strong>If you can’t build something that accommodates the needs of users with disabilities, then the very least you can do is to build it in a transparent way that allows for them and their advocates to fix it on their own</strong>. When a platform is engineered for clicks and engagement, we prioritize profits over the people we are trying to serve, ultimately leading to unethical design practices. In short, the web is not a compile target and we should not treat it like one. Our users are not our enemies, and it should not be acceptable to exclude people with disabilities for profit or developer convenience.</p></section>
<section><h2 id="practicing-accessibility">Practicing Accessibility</h2><p>To recap, Tumblr as a platform has blatantly ignored the requests of it’s disabled users, and was hostile to third-party developers that made it’s platform usable. While Tumblr as a platform has very much served as a lesson for what not to do, Tumblr’s disability community has developed unique strategies in order to cope with the inaccessibility of the platform, modelling what we might now call ‘inclusive design’ before the term was coined.</p><p>For those who might not know, individual Tumblr blogs live at their own URL, and can be styled using different themes, much like a MySpace page or a Wordpress blog. So while the Tumblr dashboard and application that bloggers use to <em>make</em> sites may not be compatible with most assistive technologies, individual blog owners can make <em>their rendered sites</em> somewhat more accessible through choosing more accessible themes. (I quantify ‘somewhat’ here as Tumblr has made many baffling decisions that make full compliance with the <a href="https://www.w3.org/TR/WCAG21/">WCAG</a> almost impossible).</p><p>Tumblr’s theme store offers a wide variety of themes, ranging from paid premium themes made by professionals and agencies to themes made by hobbyists who learned how to code by copy-and-pasting snippets from each other. In terms of accessibility, there is almost no correlation between the cost of the theme and how accessible it is. Sure, you have many themes built by hobbyists that are using 12px white fonts on pastel pink backgrounds, but it is painfully common for <a href="https://webaim.org/projects/million/">websites built by professionals to not be accessible at all</a>. It is much easier for a simple theme built by a passionate hobbyist to be built accessibly by virtue of not adding the bloat and fanciness we are used to seeing in web projects these days.</p><p>Within the Tumblr theme building community, which is largely populated by non-professionals, certain practices have been created for accessibility purposes. For example, many themes offer the ability to display a spoon meter (derived from <a href="https://en.wikipedia.org/wiki/Spoon_theory">Spoon theory</a>, a popular disability metaphor) or other variants such as stylized health meters and battery indicators. In a world where social media places immense pressure on us to be constantly available and performing our best selves, there is power to be found in being vulnerable and disengaging from the demands that social media places on our time. Declaring ‘I am low on spoons’ and having that be respected and accommodated by one’s followers is a profound action, and the ability to set boundaries is necessary in order to allow disabled people to participate in online communities.</p><p>Tumblr theme developers have often repurposed certain platform features in order to improve accessibility. An interesting example of this is the <code>{TagsAsClasses}</code> functionality. The <code>{TagsAsClasses}</code> feature outputs tags on a post as CSS classes that can be referenced for styling purposes. Although this feature was likely created for more commercial purposes, users have utilized this tag for a variety of access needs. Users can add support for multilingual blogs by tagging posts with right-to-left languages (such as Arabic) appropriately. It can also be used to allow users to accommodate for their followers’ needs by tagging things for common phobias or triggers such as #motion warning, #no captions, #spiders, #gore, and hiding that content or placing it under a ‘Read More’ cut.</p><p>Several themes offer a wide variety of settings in offer to support as many different kinds of reading experiences as possible. Amateur theme developers share snippets for light and dark mode toggles, bold/italic style removers and font family switchers (to toggle between fonts that users may find more readable, as people have different preferences). While their implementations aren’t always perfect, unpaid amateurs, many of whom are students, are putting in massive amounts of effort to improve accessibility. We, as professional developers, should take pause, as we often fail to do put in even minimal effort. We absolutely need to do better.</p><p>One final practice that I would like to mention is the idea of ‘Community accessibility’ as it is practiced on Tumblr. In this case, ‘community accessibility’ refers to the practice of individuals contributing their own time and ability to improve accessibility when the original creator is unable to. For example, someone with fatigue issues may need to create a long post but be unable to type. They can record a video of themselves and tag the post #accessibility request, in the hopes that one of their followers will add transcripts or captions to the post, which can then be shared with the video.</p><p>Another common practice is to add image descriptions to images, as Tumblr has never had proper alt text support. Tumblr’s reblogging system facilitates this kind of interaction in ways that other platforms do not, as another user’s image descriptions can be grouped with the original inaccessible image. <strong>Community accessibility improves the overall degree to which disabled users may participate in the community, without placing the burden on those disabled users themselves</strong>.</p><p>What would it look like if we encouraged and facilitated community accessibility when building our own platforms? Media-sharing platforms could allow users to submit accessibility requests for captions or alt text, or allow them to be added in later. (I can’t count the number of times that I’ve forgotten to add alt text to a tweet.) Platforms that facilitate meetups or review services could allow users to enter accessibility information, so that people who use wheelchairs won’t have to suffer the humiliation of turning up at an event only to find it completely inaccessible to them. At the very least, platforms could facilitate requests for accessibility information anonymously, as disabled users shouldn’t have to open themselves up to harassment from defensive event planners. The potential for improvement is limitless and we can do better at every step of the process, from idea conception to development.</p><p>In terms of tangible things that we can start to do, I have a few suggestions. For those of us in tech, we should reflect on the state of our projects, as <a href="https://www.seriouseats.com/2019/07/cooking-websites-and-accessibility-for-disabled-readers.html">SeriousEats has done in this thoughtful public blog post</a>. For social media users, it may mean thinking about the ways we interact with social media (Tumblr or otherwise!), and committing to <a href="https://www.a11ywithlindsey.com/blog/7-ways-tweets-more-accessible/">improving accessibility in the content we create</a>. No matter what level of knowledge we have, above all we need to listen to and center the conversations that disabled people are having.</p><p>Building accessible platforms does not start when we begin designing user interfaces (UIs); we need to be thinking about this much earlier on in the process. Caring for the collective physical and mental health of our communities is integral to practicing disability justice. We cannot build platforms that are technically accessible but facilitate the harassment and ongoing traumatization of marginalized communities and then call our jobs done. The power of the internet is transformative, and when we make spaces inclusive, we change lives for the better.</p></section>]]></content>
        <published>2019-08-15T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Features I Want for Shopify]]></title>
        <id>https://www.nicchan.me/blog/features-i-want-for-shopify/</id>
        <link href="https://www.nicchan.me/blog/features-i-want-for-shopify/"/>
        <updated>2019-05-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The features I think Shopify needs the most to improve developer experience.]]></summary>
        <content type="html"><![CDATA[<p>In my role as a developer for <a href="https://www.altanddot.com/">Alt and Dot</a>, I work on both building custom themes from scratch as well as improving existing stores. As an agency, we often have long term relationships with merchants, maintaining and scaling their site as they grow. This list of Shopify features that I find myself wishing for on a regular basis is informed by that experience of working with many merchants over a long period of time, and the shared pain points we have observed as a Shopify-focused agency.</p>
<section><h2 id="multiple-section-instances">Multiple section instances</h2><p>For those who might be unfamiliar, the Theme Customizer is basically Shopify’s answer to a page builder. In the actual admin, most pages just present you with a <abbr title="Rich Text Editor">RTE</abbr> field and a few other settings, but through the customizer, developers can define ‘sections’ with their own fields or repeatable blocks to allow merchants to customize complex content and layout on their pages.</p><p>Don’t get me wrong, I adore sections and the addition of them has been the single greatest addition to Shopify since I started working with the platform. The downside is that for now, only homepage sections can be added and reordered. Other pages must have their sections predefined and placed in order within the template code. Even more frustrating is that while sections on the homepage are unique, sections on other pages are shared. So while you may be able to have multiple instances of say, a hero image component on the homepage, if you wanted to have hero components on a ‘Contact Us’ and ‘About Page’ with different content, you would need to define separate sections for this. There are different techniques to work with this limitation, but all involve duplication of the schema, and it requires developer intervention for the creation of new customized pages, even if it’s only to copy and paste sections to create new instances. Allowing the full use of sections on all pages would open up a ton of new interesting possibilities for designs without having to do anything hacky like processing the <abbr title="Rich Text Editor">RTE</abbr> output or adding shortcodes.</p></section>
<section><h2 id="additional-section-features">Additional section features</h2><p>In addition to rolling out sections for all pages, it would be fantastic to be able to define some additional settings for sections, like what pages they can be added to. A list of product features might not make sense on non product pages, but it would still be nice to have it as a section so it can be reordered on the product page. It would also be useful to be able to define whether or not a section’s content is ‘global’ or not. For example, you may have a list of products currently on sale on some pages in your store. In this case, it wouldn’t make sense to have it be a part of your layout files, but you might want the content of that section to be the same any time it is used, and if it is updated on one page, it would be updated on all pages that it is present on.</p></section>
<section><h2 id="redesign-the-image-picker">Redesign the Image Picker</h2><p>While I’m on the topic of the the customizer, I think the image picker could use a redesign. I understand the desire to show the content being edited next to the images, but whenever I need to reuse an older image, the small image panel isn’t much help. A search bar would go a long way in helping to track down existing images, and it would be even better if there was an option to look through past images in an expandable window, as seems a bit ridiculous to me that on my large external monitor, I can only see 9 thumbnails at a time. I often see editors re-upload the same image to save themselves the trouble of trying to find an older image, which means customers don’t get the performance benefits of things like caching.</p><img alt="Shopify's Image picker UI, there is a tiny sidebar for images and the page UI takes the rest of the ginormous amount of space." loading="lazy" decoding="async" sizes="(min-width: 1920px) 1920px, 100vw" style="object-fit:cover;max-width:1920px;max-height:937px;aspect-ratio:2.049092849519744;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_312,c_lfill,f_auto/v1610102431/shopify-customizer 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_366,c_lfill,f_auto/v1610102431/shopify-customizer 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_404,c_lfill,f_auto/v1610102431/shopify-customizer 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_469,c_lfill,f_auto/v1610102431/shopify-customizer 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_527,c_lfill,f_auto/v1610102431/shopify-customizer 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_625,c_lfill,f_auto/v1610102431/shopify-customizer 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_814,c_lfill,f_auto/v1610102431/shopify-customizer 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_937,c_lfill,f_auto/v1610102431/shopify-customizer 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_937,c_lfill,f_auto/v1610102431/shopify-customizer 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_999,c_lfill,f_auto/v1610102431/shopify-customizer 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2560,h_1249,c_lfill,f_auto/v1610102431/shopify-customizer 2560w,
https://res.cloudinary.com/nicchan/image/upload/w_3200,h_1562,c_lfill,f_auto/v1610102431/shopify-customizer 3200w,
https://res.cloudinary.com/nicchan/image/upload/w_3840,h_1874,c_lfill,f_auto/v1610102431/shopify-customizer 3840w" src="https://res.cloudinary.com/nicchan/image/upload/w_1920,h_937,c_lfill,f_auto/v1610102431/shopify-customizer"></section>
<section><h2 id="svg-uploads">SVG Uploads</h2><p>My team have heard me say this enough, but Shopify, it’s 2019, please give us SVG upload support in the image picker! Vector-based illustrations and icons are a huge trend that is not going away any time soon, and it would be nice to allow people to upload crispy logos.</p></section>
<section><h2 id="option-to-force-alt-text">Option to force alt text</h2><p>One final thing I would like for the image picker is the ability to force an image to have alt text added before it can be saved. I look forward to a day where we can leave image carousels in the past, but until we reach a point where clients stop putting critical information exclusively in images, we need processes to ensure that we are not leaving screen reader users out of the equation. Right now, adding alt text is very much optional and it can be easily forgotten by a site editor.</p></section>
<section><h2 id="first-party-metafields-support">First Party metafields support</h2><p>Ask any Wordpress developer what their favorite plugins are, and you’ll find that Advanced Custom Fields is near the top of the list. ACF is a fantastic plugin that lets a developer add fields directly to the admin panel, allowing editors to edit complicated page layouts without fiddling with HTML or page builder plugins. Shopify’s answer to this is ‘metafields’ — nearly all Shopify objects let you assign metafields to them. Metafields are often used by apps to extend Shopify’s features, but they can also be used to populate unique content for specific products, pages, etc. For example, if you wanted to add an instructional video to your product pages, you could define a metafield for the embed code, and pull it in on the product templates.</p><p>The downside with Shopify is that there is no way to pull in metafields directly into the admin pages, you must use an external app. Because of the necessary authentication steps required for an external app, editing metafields can be a time consuming and annoying process. Some apps only let you edit the metafields that were created within that same app, which ties you to that particular app, but apps that show all metafields can show information that is unnecessary for the editor, like configuration objects for other app functions. Having the ability to define metafields that can be shown directly within the admin panel with a corresponding field type would significantly improve the editing experience for merchants.</p></section>
<section><h2 id="improved-git-workflow">Improved Git workflow</h2><p>One thing I enjoy about Shopify is the way they approach configuration. A store’s settings (everything from what colors are used to the content for the sections) is stored in a single <code>config/settings_data.json</code> file, which makes migration and backups easier as you don’t need to keep track of a local and remote database. It’s treated as any other file, which means that you can edit locally and have ThemeKit sync up your changes remotely, or pull down remote changes from the live store whenever needed. However, when it comes to keeping track of this file in Git and managing it within the context of an active store, we’ve hit several barriers that make this a lot more unwieldy than we’d like.</p><p>Much of Shopify’s theme tooling has been built around the use case of building a theme that would be used by many stores or sold in the theme store. In cases like this, the developers can easily manage the theme. They do not need to be concerned with a specific store’s configuration, and they can treat their repository as a single source of truth and push updates to the theme store.</p><p>In the case where a developer is actively maintaining an existing store, managing version control becomes a lot more difficult. Most developers will have a seperate theme that is forked from the currently live theme where they will be implementing new features. To track this in Git, you might have the master branch correspond to the currently live theme, and new feature branches being created to correspond to themes that are not currently live but can be previewed through the admin. While a developer is working on a new feature, the merchant may install and configure a bunch of new apps that directly modify the theme code, and rearrange the homepage sections to reflect a new promotion. The developer needs to be actively pulling all these new changes into Git, or they might be lost when the feature branches are merged. Even worse, a developer may actively be trying to stay on top of these changes, but still potentially lose client content when it comes to merging due to the way JSON works.</p><p>Below is a abridged snippet of code that demonstrates how Shopify stores data for content blocks within the <code>config/settings_data.json</code> file. The numbers represent longer IDs that have been cut short for demonstration purposes, and each block represents a slide in a carousel.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;1&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">  &quot;type&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;carousel&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">  &quot;blocks&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">    &quot;1&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#FFFFFF;--shiki-dark:#FFFFFF">      ...</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">    },</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">    &quot;2&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#FFFFFF;--shiki-dark:#FFFFFF">      ...</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">    }</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">  },</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">  &quot;block_order&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#5F4C65;--shiki-dark:#B993FF">    &quot;1&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#5F4C65;--shiki-dark:#B993FF">    &quot;2&quot;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">  ],</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>As you can see, they are using a JavaScript Object for the block settings, and a separate <code>block_order</code> Array to determine the what order the blocks come in. We recently had a case where the client added a slide to the homepage while we were working on a separate feature branch. When it came time to merge feature branch with the master branch, the JavaScript object with the blocks was reordered as there is no guaranteed order on key/value pairs. As a result, the merge ended up creating an invalid configuration object with two duplicate slides and we ended up ‘losing’ a slide.</p><p>Most developers (and Shopify themselves) utilize the approach of completely ignoring the <code>config/settings_data.json</code> file in Git to avoid these hassles. This approach works if you are not servicing any particular store in general and are just developing themes, or if you are managing a store which lacks complex custom features and does not need to be updated often. But for large active stores, particularly Shopify Plus stores, better support for things like version control and workflow tooling are necessary to prevent potentially costly mistakes in the form of broken deployments and data loss. I’ve often encountered stores where developers are working directly on the live theme with no version control whatsoever, which can lead to disastrous consequences for the merchant. Improving the developer experience would allow developers to provide better service to merchants, and cultivate a healthier developer ecosystem around the Shopify platform in general.</p></section>
<section><h2 id="highlight-accessible-themes-and-apps">Highlight accessible themes and apps</h2><p>Shopify has made great strides in improving accessibility in recent years, and I am very glad that they have a team devoted to working on improving the accessibility of their platform and their official themes. Currently, Debut and the Shopify Checkout paired together make for an experience that has been well tested by their accessibility team, so if it is a hard requirement that your store complies with the WCAG 2.1 guidelines, the Debut theme is a great place to start. Shopify also has a <a href="https://shopify.github.io/liquid-code-examples/">library of liquid snippets</a> that serve as a great reference for anyone looking to implement common e-commerce patterns in a semantic and accessible way.</p><p>Shopify’s commitment to writing accessible code is great, but for now, third-party themes and apps remain a completely different ball game. The Shopify team have expressed that they are working on a set of guidelines for themes and apps to adhere to, and while I am very excited for this possibility, there’s not much out there right now to help well-meaning merchants find an accessible theme. Apps can be especially problematic, as a fairly accessible experience can be rendered completely inaccessible by something like a poorly-written email capture modal that offers no way to close it through keyboard access alone.</p><p>During the transition period for between now and whenever the accessibility guidelines are rolled out, it would be great to see functionality that would highlight more accessible themes and plugins within their respective stores, such as a special tag or something along those lines. Adding something like this would encourage theme developers to make accessibility a priority now, rather than waiting till when the guidelines come out and doing the bare minimum to meet them. Many merchants these days are more concerned about accessibility (even if only to avoid a facing a lawsuit), but right now, they lack the ability to make decisions about it without the guidance of an experienced technical person.</p></section>
<section><h2 id="closing-remarks">Closing Remarks</h2><p>I hope that what you take from this article isn’t ‘Shopify is a crappy platform that needs a ton of fixes to be usable’. On the contrary, I think Shopify is a great platform that has continued to implement features to improve both the developer and merchant experience. I would like nothing more than to have solutions to all these problems announced at this year’s Shopify Unite conference so as developers, we can better meet the needs of not only our clients, but their customers as well.</p><p>If you’re also a Shopify dev, I’d love to hear from you! What features would you like Shopify to implement?</p></section>]]></content>
        <published>2019-05-08T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Auditing My Own Site For Accessibility]]></title>
        <id>https://www.nicchan.me/blog/auditing-my-own-site-for-accessibility/</id>
        <link href="https://www.nicchan.me/blog/auditing-my-own-site-for-accessibility/"/>
        <updated>2019-04-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How I retrofitted my portfolio to be more accessible.]]></summary>
        <content type="html"><![CDATA[<p>I launched the first version of this site well over 3 years ago, and it was one of my first fully completed code projects. Like many new self taught developers, I had built it as a demonstration of my skills to land clients and jobs, and to that end it has served me well. While I would occasionally add fixes to improve the user experience on older browsers, the basic design hasn’t changed much over the years, and it essentially remains the same site that I launched back in 2015.</p>
<p>Over the years, as I learned more about web accessibility, and heard directly from disabled users giving feedback on my accessible Tumblr Theme, I began having the sneaking suspicion that my own site was probably not up to par. In 2018, I spent a lot of time focused on not only learning about accessible web practices, but also actually implementing accessibility improvements on client work, and now I finally feel ready to confront the skeletons in my own closet by tackling my own site.</p>
<p>Admittedly, I feared this day not because I felt I lacked the knowledge to implement certain accessibility changes, but because I felt that admitting any previous lack of knowledge would be perceived as a sign of weakness in a highly competitive industry. I toyed with the idea of wiping away all my past mistakes with a completely new design so I could pretend that I had always known the best way to build sites, but eventually I decided against this because 1. I still kind of like the design for this site and 2. I think it’s more of an accurate representation to the way we build things on the web. Sure, we may not get it right the first time, or even the next few times, but the beauty of the web is that you get to go back and improve things over time. Everything is an ongoing work in progress, and we should embrace particular fact as a strength of the medium, not as a weakness. And on that note, feel free to continue reading to follow along on my journey to improving my own site.</p>
<section><h2 id="general">General</h2><p>Right off the bat, I knew the color scheme was probably going to fail. While the body text, a deep navy, would definitely pass, I had some places where I knew the color contrast was too low, and my project pages had small transparent white text against a brand color that may or may not have been chosen with contrast in mind. Using <a href="http://www.aremycoloursaccessible.com/">this fabulous tool, aremycolorsaccessible.com</a>, I was able to input colors from my color scheme, and play with the toggles to find accessible alternatives and combinations. This tool is great because it not only tells you if your chosen colors are accessible, it also lets you go and tweak the color scheme directly on the same page, so you don’t have to go back and forth between your graphics software and the browser.</p><img alt="aremycolorsaccessible.com screenshot showing a failed color scheme" loading="lazy" decoding="async" sizes="(min-width: 448px) 448px, 100vw" style="object-fit:cover;max-width:448px;max-height:558px;aspect-ratio:0.8028673835125448;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_448,h_558,c_lfill,f_auto/v1709966445/a11y-2 448w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_797,c_lfill,f_auto/v1709966445/a11y-2 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_934,c_lfill,f_auto/v1709966445/a11y-2 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_1031,c_lfill,f_auto/v1709966445/a11y-2 828w,
https://res.cloudinary.com/nicchan/image/upload/w_896,h_1116,c_lfill,f_auto/v1709966445/a11y-2 896w" src="https://res.cloudinary.com/nicchan/image/upload/w_448,h_558,c_lfill,f_auto/v1709966445/a11y-2"><p><strong>Take Home Message</strong>: Color schemes and contrast play a significant role in determining whether or not a site is accessible. Tooling can be extremely helpful for determining appropriate color schemes.</p></section>
<section><h2 id="navigation">Navigation</h2><p>One easy accessibility win is adding a ‘Skip to Main Content’ link. This is a link which targets the main element on the page, allowing keyboard users to easily bypass tabbing through the whole navigation on each page. Most modern screen readers are smart enough to allow you to skip navigation elements if they are properly marked up, but this is still a nice thing to have for sighted keyboard users.</p><p>I ended up giving a solid background to my navigation on all pages (as opposed to a transparent background), so that I would eliminate the possibility that I’d choose a background that provided too little contrast for my navigation links. I also made sure that <code>aria-current=&quot;page&quot;</code> was added to the currently active navigation link.
In addition, the existing hover and focus state of my navigation items needed improvement, as the current focus state relied solely on a color change. It is best not to rely exclusively on changing color in order to convey important information to the user, as certain types of color blindness can prevent people from easily detecting the change. I added an underline to the ensure that the currently highlighted nav item would not be missed.</p><figure><img alt="Before and After screenshots of the old and new navigation." loading="lazy" decoding="async" sizes="(min-width: 526px) 526px, 100vw" style="object-fit:cover;max-width:526px;max-height:136px;aspect-ratio:3.8676470588235294;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_526,h_136,c_lfill,f_auto/v1709966445/a11y-3 526w,
https://res.cloudinary.com/nicchan/image/upload/w_640,h_165,c_lfill,f_auto/v1709966445/a11y-3 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_194,c_lfill,f_auto/v1709966445/a11y-3 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_214,c_lfill,f_auto/v1709966445/a11y-3 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_248,c_lfill,f_auto/v1709966445/a11y-3 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1052,h_272,c_lfill,f_auto/v1709966445/a11y-3 1052w" src="https://res.cloudinary.com/nicchan/image/upload/w_526,h_136,c_lfill,f_auto/v1709966445/a11y-3"><figcaption><p>Before: Low opacity white on light background? What was I thinking! <br>
After: Increased contrast, consistent appearance on all pages.</p></figcaption></figure><p><strong>Take Home Message</strong>: Navigation is a crucial component to get right. Opt for visible navigation over hidden navigation if possible, and do your best to ensure that you can use the keyboard to navigate the menu by using clear focus states and appropriate elements and aria-roles when applicable.</p></section>
<section><h2 id="homepage">Homepage</h2><p>I had taken the time to ensure that the homepage, with it’s fancy scroll effect and all, still worked without JavaScript enabled. For sites with fancy animation where the elements load in on page scroll, it’s common to set the initial state of elements to be animated with CSS, and then apply the transition with JS once the user has scrolled down to that section of the page. Because these transitions often involve transitioning from 0 opacity or transitioning elements from off the page, it’s a good idea to set some kind of fallback method so that in case JS fails to load for some reason, your users can still interact with the page.</p><p>While I had progressively enhanced the whole layout, I had missed some basic semantics in the markup that would make a big difference to the experience of screen reader users. For example, look at the code snippet below:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="html"><code><span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">div</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;tags__wrapper&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;tags__tag&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Jekyll</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;tags__tag&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Forestry</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;tags__tag&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">SCSS</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">	&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;tags__tag&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Javascript</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">div</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"></span></code></pre><p>Spot the issue? We should probably list these items out as an unordered list instead. Newer developers will often reach for the markup that is easier to style instead of choosing the element that is the most semantic, and this has a tendency to hurt not only screen readers, but any kind of robot/crawler, including search engines. Using semantic elements for list elements can allow screen readers to say the the total number of elements and allow for easy navigation within the list elements, and it is worth it to take the time to do a little bit of extra styling in order to make sure that the elements are read correctly when read aloud by screen readers.</p><p>One more thing to note about the homepage - consider the markup below:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="html"><code><span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">a</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;button button--small&quot;</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> href</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;/projects/studio-ryte&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	Read Case Study →</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">a</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"></span></code></pre><p>Links that say things like ‘Read More’ or ‘Learn More’ are quite commonly seen across the web, but they can hurt scanability. Some screen readers offer the functionality of listing all links on a page. Vague link text or repeated link text can be confusing for users trying to scan for the link they want to navigate to when out of regular document context. Complicating things further, the title attribute is handled inconsistently between screen readers - some will ignore it completely and some read both the link text and the title attribute. We can provide context for screen reader users by adding screen reader text to the link, a trick I picked up from <a href="https://www.a11ywithlindsey.com/blog">Lindsey Kopacz’s excellent blog!</a> The corrected markup is as follows.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="html"><code><span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">a</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;button button--small&quot;</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> href</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;/projects/studio-ryte&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	Read </span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;screenreader&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Studio Ryte</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> Case Study →</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">a</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"></span></code></pre><p><strong>Take Home Message</strong>: While the end result may all look the same to a sighted user, proper semantic markup gives you a ton of accessibility features for free. Consider the desired experience for your users and use the appropriate element based on that, not based on what is easiest to style.</p></section>
<section><h2 id="about-page">About Page</h2><p>The about page features a resume section in addition to a contact form. Below is a sample of some of the resume markup in Liquid.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="liquid"><code><span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">h2</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__heading&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Resume</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">h2</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">h2</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;subheading&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Related Experience</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">h2</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">{% </span><span style="color:#A75293;--shiki-dark:#CB96CC">for</span><span style="color:#4075BA;--shiki-dark:#B993FF"> exp</span><span style="color:#A75293;--shiki-dark:#CB96CC"> in</span><span style="color:#A75293;--shiki-dark:#9DDACB"> page</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">experience</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__institution&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">exp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">institution</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__title&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">exp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">title</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__bullet&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">&amp;bull;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__date&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">exp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">date</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">ul</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__description&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    {% </span><span style="color:#A75293;--shiki-dark:#CB96CC">for</span><span style="color:#4075BA;--shiki-dark:#B993FF"> desc</span><span style="color:#A75293;--shiki-dark:#CB96CC"> in</span><span style="color:#4075BA;--shiki-dark:#B993FF"> exp</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">description</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">      &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">li</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">desc</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">li</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">li</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    {% </span><span style="color:#A75293;--shiki-dark:#CB96CC">endfor</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">ul</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">{% </span><span style="color:#A75293;--shiki-dark:#CB96CC">endfor</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"></span></code></pre><p>Right off the bat, we can see that I used two H2 tags in a row. ‘Related Experience’ is most definitely supposed to be a heading under ‘Resume’, and the two have separate classes with different styling to distinguish them visually, so the only cause I can think of is that I must have been copy and pasting from other parts of the project without really thinking about what heading level was appropriate in the context of the destination. 🤦</p><p>Screen readers allow users to navigate by heading levels, so having a clear document structure is necessary. With the markup above, it would appear that ‘Resume’ is a completely empty section to a screen reader user! We can fix this up by making ‘Related Experience’ an h3, and further improve readability by putting the position title, instution and date in a h4 to mark it as a heading before the list element below it.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="liquid"><code><span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">h2</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__heading&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Resume</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">h2</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">h3</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;subheading&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">Related Experience</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">h3</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">{% </span><span style="color:#A75293;--shiki-dark:#CB96CC">for</span><span style="color:#4075BA;--shiki-dark:#B993FF"> exp</span><span style="color:#A75293;--shiki-dark:#CB96CC"> in</span><span style="color:#A75293;--shiki-dark:#9DDACB"> page</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">experience</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">h4</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__point&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__institution&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">exp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">institution</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__title&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">exp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">title</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__bullet&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">&amp;bull;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__date&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">exp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">date</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">span</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">h4</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">ul</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> class</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">=</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;resume__description&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    {% </span><span style="color:#A75293;--shiki-dark:#CB96CC">for</span><span style="color:#4075BA;--shiki-dark:#B993FF"> desc</span><span style="color:#A75293;--shiki-dark:#CB96CC"> in</span><span style="color:#4075BA;--shiki-dark:#B993FF"> exp</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">description</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">      &lt;</span><span style="color:#4075BA;--shiki-dark:#B993FF">li</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{{ </span><span style="color:#4075BA;--shiki-dark:#B993FF">desc</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">li</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> }}</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">li</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">    {% </span><span style="color:#A75293;--shiki-dark:#CB96CC">endfor</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">  &lt;/</span><span style="color:#4075BA;--shiki-dark:#B993FF">ul</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">&gt;</span></span>
<span class="line"><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">{% </span><span style="color:#A75293;--shiki-dark:#CB96CC">endfor</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4"> %}</span></span>
<span class="line"></span></code></pre><p><strong>Take Home Message</strong>: Proper use of headings is essential for screen readers. Always evaluate what heading level is appropriate in the context of the resulting page. Be especially careful when using frameworks/component libraries that may abstract over the generated markup, it’s easy to reach for the element that matches the design and not realize that you are using an incorrect heading level.</p></section>
<section><h2 id="illustrations">Illustrations</h2><p>The illustration section of my portfolio site is fairly simple in theory. Because the order of illustrations doesn’t matter in this case, I have chosen to use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns/Using_multi-column_layouts">CSS Multi-Column layout</a> to create a Masonry style effect without loading any additional JS. Upon clicking an image, the image will open a modal that shows the full image and the title.</p><p>The modal was powered by <a href="https://www.lokeshdhakar.com/projects/lightbox2/">Lokesh Dhakar’s Lightbox 2</a> script, originally written over 8 years ago. The script used JavaScript to handle things that were difficult to do with CSS during that time period, like controlling the sizing and positioning of the image, and was ridiculously simple to set up. However, I realized the script failed on some basic accessibility issues, such as lacking alt text support, and failing to trap focus within the modal. An accessible modal should not only transfer focus to the modal when opened, it should also hide all other content to screen readers, and it needs to keep focus within itself until the element is closed. Otherwise, you can encounter an unpleasant situation where you can tab out of the modal, but the modal remains open, obscuring a keyboard user’s current position on the page. I did some research by looking through <a href="https://github.com/lokesh/lightbox2/issues/138">the project’s Github issues</a>, but it seemed like accessibility fixes weren’t a priority for this version of the project, and would be better handled in version 3.</p><p>I decided that instead of submitting a PR to a project that wasn’t actively being worked on anymore, I would find a new tool that would help me replicate the lightbox functionality. Additionally, I could kill two birds with one stone and take steps to remove my site’s dependency on jQuery. Honestly, finding component libraries that are explicitly focused on accessibility and aren’t tied to some other JS framework, whether it be jQuery, React, or something else, can be quite difficult. I find myself returning to <a href="https://inclusive-components.design/">Heydon Pickering’s Inclusive Components</a> and <a href="https://github.com/scottaohara/accessible_components">Scott O’Hara’s Accessible Components</a> time and time again whenever I need to find good examples of common UI patterns.</p><p>In this situation, I ended up using the <a href="https://github.com/edenspiekermann/a11y-dialog">a11y-dialog library</a>. I’ve used this library successfully on several client projects, and it exposes events that I need to dynamically generate the necessary markup. I like this library in particular because it comes with no styling - not a perk for most people, but this is great for projects when you need to match a client’s designs and you don’t want to spend hours overriding the highly specific CSS that often comes with these libraries! It uses the native <code>&lt;dialog&gt;</code> when the browser supports that element, and makes it work on older browsers that don’t support <code>&lt;dialog&gt;</code> just yet.</p><p>I won’t go into too much detail about my implementation, but there are a few things that are worth noting. I kept the modal trigger element as links to the larger versions of the images instead of a button, I like this as a form of progressive enhancement - if for some reason the JS fails, you can still see a larger version of the image by clicking on the link. I used flexbox on the contents of the dialog window, so that the title could be <strong>before</strong> the image in the source order, allowing the image to fall under the heading (a trick I picked up from <a href="https://inclusive-components.design/cards/">Heydon’s article about cards!</a>). I then used the order property to visually move the title below the image, as is more conventional when displaying art, so both sighted and screen reader users will experience a structure they are more familiar with. Using flexbox and the <code>:not(:empty)</code> selector allowed me to easily conditionally resize the image to occupy the full height if there wasn’t a title (naming things is hard, as we all know). You can <a href="https://github.com/nchan0154/nchan0154.github.io/commit/42a26afd17dd736a46e25b04dc72f0d45e56cc91">visit this commit on Github</a> to check out the code for the dialog.</p><div class="post-callout"><h3 class="post-callout__title">Edit</h3><p class="post-callout__text"><a href="https://www.scottohara.me/blog/2019/03/05/open-dialog.html"><p>Scott O’Hara points out that the dialog element isn’t quite functioning as
intended</p></a> <p>in certain popular browser and screen reader combinations, and that it’s
best to avoid the <code>&lt;dialog&gt;</code> element completely. In my
code, I have replaced it with a <code>&lt;div&gt;</code> (horrified gasps
echo around the room) with the role of dialog. In cases like this where the
native support isn’t that great, an appropriate usage of ARIA labels can
help. Modal dialogs are a particular difficult component to build, and the
incomplete/quirky browser support only makes it more difficult. While I’m
not using modals for a critical element of my site, if you are using them
for mission critical UI, consider testing with as many screen reader and
browser combinations as possible as they all have their own unique quirks.</p></p></div><p><strong>Take Home Message</strong>: A single inaccessible dependency can render a site impossible to use for screen readers. Audit any UI libraries thoroughly before incorporating them into your project!</p></section>
<section><h2 id="in-closing">In Closing</h2><p>I hope this post has been somewhat helpful! Lately I have seen a greater push for web accessibility, and I wanted to contribute something that would provide a little more detail to those who already understand why accessibility is important, but are unsure how to begin implementing it. I’d like to close with some resources which have been incredibly valuable to me in my learning process:</p><ul>
<li><a href="https://inclusive-components.design/">Inclusive Components by Heydon Pickering</a></li>
<li><a href="https://www.a11ywithlindsey.com/blog">A11y with Lindsey</a></li>
<li><a href="https://www.scottohara.me/code/">Everything by Scott O’Hara</a></li>
<li><a href="http://adrianroselli.com/tag/accessibility">The writings of Adrian Roselli</a></li>
<li><a href="https://www.smashingmagazine.com/2019/02/accessibility-webinar/">Video of Léonie Watson using various websites with a screen reader</a></li>
</ul></section>]]></content>
        <published>2019-04-12T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Adventures in HTML5 Canvas]]></title>
        <id>https://www.nicchan.me/blog/adventures-in-html5-canvas/</id>
        <link href="https://www.nicchan.me/blog/adventures-in-html5-canvas/"/>
        <updated>2017-05-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Musings on struggling with HTML5 Canvas generative art.]]></summary>
        <content type="html"><![CDATA[<p>Recently, I was tasked with replacing a background video, created in Apple Motion, with an HTML5 canvas animation. Having never worked with canvas before, I was suprised at how fundamentally simple the canvas API was, though I can’t say it was ‘easy’ to work with neccessarily. Many hours of blood, sweat and tears later, I was able to produce something very close to the original video.</p>
<p data-height="600" data-theme-id="0" data-slug-hash="OmjvZJ" data-default-tab="result" data-user="nchan0154" data-embed-version="2" class="codepen"><p>See the Pen 
<a href="http://codepen.io/nchan0154/pen/OmjvZJ/">Animated Dividers</a> by Nic
Chan (<a href="http://codepen.io/nchan0154">@nchan0154</a>) on 
<a href="http://codepen.io">CodePen</a>.</p></p>

<section><h2 id="canvas-basics">Canvas Basics</h2><p>The canvas API is fairly primitive in that once you draw a shape, you can’t really go back and edit it. This is the exact opposite of CSS or Javascript animation frameworks like GSAP, where you can reach in and grab a particular element with the targeted use of selectors. Because of this, the typical animation flow goes something like this:</p><ol>
<li>Clear the canvas</li>
<li>Draw the canvas elements</li>
<li>Modify the elements that are to be animated</li>
<li>Grab the next frame, typically with requestAnimationFrame</li>
</ol><p>It’s pretty amazing to think that with every frame of this animation, we’re drawing the same lines over again, eh? <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations">MDN</a> was an invaluable source for me when I was trying to understand this process.</p></section>
<section><h2 id="on-algorithmic-perfection">On Algorithmic Perfection</h2><p>I spent a lot of time trying to come up with the perfect formulas for calculating things like opacity, speed, and scaling. If we were modelling a real life situation, getting an accurate algorithm would be top priority, but since we are merely using these functions to generate a visual representation, there is a lot more freedom! Getting something that just ‘feels right’ is perfectly acceptable, and in some cases, preferred, over trying to manually calculate that perfect bezier curve.</p></section>
<section><h2 id="on-persistence">On Persistence</h2><img alt="A series of very jagged lines on a white canvas. They look very messy." loading="lazy" decoding="async" sizes="(min-width: 1129px) 1129px, 100vw" style="object-fit:cover;max-width:1129px;max-height:476px;aspect-ratio:2.3718487394957983;width:100%" srcset="https://res.cloudinary.com/nicchan/image/upload/w_640,h_270,c_lfill,f_auto/v1709706012/canvas-lines 640w,
https://res.cloudinary.com/nicchan/image/upload/w_750,h_316,c_lfill,f_auto/v1709706012/canvas-lines 750w,
https://res.cloudinary.com/nicchan/image/upload/w_828,h_349,c_lfill,f_auto/v1709706012/canvas-lines 828w,
https://res.cloudinary.com/nicchan/image/upload/w_960,h_405,c_lfill,f_auto/v1709706012/canvas-lines 960w,
https://res.cloudinary.com/nicchan/image/upload/w_1080,h_455,c_lfill,f_auto/v1709706012/canvas-lines 1080w,
https://res.cloudinary.com/nicchan/image/upload/w_1129,h_476,c_lfill,f_auto/v1709706012/canvas-lines 1129w,
https://res.cloudinary.com/nicchan/image/upload/w_1280,h_540,c_lfill,f_auto/v1709706012/canvas-lines 1280w,
https://res.cloudinary.com/nicchan/image/upload/w_1668,h_703,c_lfill,f_auto/v1709706012/canvas-lines 1668w,
https://res.cloudinary.com/nicchan/image/upload/w_1920,h_809,c_lfill,f_auto/v1709706012/canvas-lines 1920w,
https://res.cloudinary.com/nicchan/image/upload/w_2048,h_863,c_lfill,f_auto/v1709706012/canvas-lines 2048w,
https://res.cloudinary.com/nicchan/image/upload/w_2258,h_952,c_lfill,f_auto/v1709706012/canvas-lines 2258w" src="https://res.cloudinary.com/nicchan/image/upload/w_1129,h_476,c_lfill,f_auto/v1709706012/canvas-lines"><p>I admit I’m a little ashamed to post this, but above is my first attempt at trying to generate a pattern of network lines with Javascript. It wasn’t even in the same ballpark as what we needed, and I used exactly none of the code in the final product. Regardless, it’s an important reminder that experimentation is necessary - you will probably fail your first few attempts when attempting a new thing, and that’s perfectly normal.</p></section>]]></content>
        <published>2017-05-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Dual booting Ubuntu with Windows 10 on a Dell XPS 15 (9560)]]></title>
        <id>https://www.nicchan.me/blog/dual-booting-ubuntu-with-windows-10-on-a-dell-xps-15/</id>
        <link href="https://www.nicchan.me/blog/dual-booting-ubuntu-with-windows-10-on-a-dell-xps-15/"/>
        <updated>2017-02-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A tutorial on how to run both Linux and Windows on a Dell XPS.]]></summary>
        <content type="html"><![CDATA[<p>I just received a brand new Dell XPS in the mail, whoo! In order to have a slightly less painful experience developing native apps, I decided to dual boot Windows 10 and Ubuntu. Sounds pretty easy right? Wrong! There were a lot of hangups that nobody told me about, and I had to figure it all out by frantically googling while explosions were happening. This one is for all of you out there who want to avoid that headache.</p>
<p>Note, I am not smart enough to have figured any of this out on my own, and I make no promises that this will work perfectly on your machine. It’s just what worked for my machine. All sources are linked.</p>
<section><h2 id="preparing-a-bootable-version-of-linux">Preparing a bootable version of Linux</h2><p>Before we begin, it’s advisable to prepare a backup version of Windows, especially if you have any data on there that might be of value. Because I was using a brand new machine and I live on the edge (not the Microsoft Edge, geddit?), I chose to forgo this step.</p><p>You will need a clean USB with at least 4GB of space.
I first grabbed Ubuntu 16.04.01 from the official <a href="https://www.ubuntu.com/download/desktop">Ubuntu Website</a>. I then downloaded <a href="https://rufus.akeo.ie/">Rufus</a> as recommended in their docs to format the USB. Ubuntu has prepared an <a href="https://www.ubuntu.com/download/desktop/create-a-usb-stick-on-windows">excellent guide</a> on how to do this, but basically you are selecting your USB stick and your freshly downloaded Ubuntu installation and writing it in ISO mode.</p></section>
<section><h2 id="enabling-ahci-mode-on-your-ssd">Enabling AHCI Mode on your SSD</h2><p>Since my machine came with Windows 10 preinstalled, I needed to adjust the SSD configuration as Ubuntu wouldn’t recognize the hard drive in RAID mode.</p><p>Following <a href="https://www.tenforums.com/drivers-hardware/15006-attn-ssd-owners-enabling-ahci-mode-after-windows-10-installation.html">this discussion thread</a>, I ran the following steps:</p><ol>
<li>Search for command prompt, right click and hit <code>Run as Administrator</code></li>
<li>Type the following to force a safe mode boot <code>bcdedit /set {current} safeboot minimal</code></li>
<li>Restart your machine, and hit F12 on the Dell screen to pull up boot options</li>
<li>Select <code>Bios Setup</code></li>
<li>Expand <code>System Configuration</code> and select <code>SATA operation</code></li>
<li>Switch from <code>RAID on</code> to <code>AHCI</code></li>
<li>Save and exit to boot into Windows 10 Safe mode</li>
<li>Run command prompt as administrator again</li>
<li>Type <code>bcdedit /deletevalue {current} safeboot</code> to disable safe mode boot</li>
<li>Restart your computer again. Success!</li>
</ol></section>
<section><h2 id="creating-space-for-ubuntu">Creating space for Ubuntu</h2><p>Alright, time to shrink down our windows partition to make space for Linux!</p><ol>
<li>Search for <code>Disk Management</code> (or open Command Prompt and type <code>diskmgmt.msc</code>)</li>
<li>Identify the disk with your Windows installation. For me, it was the one that was 400GB+.</li>
<li>Right click and select <code>Shrink Volume</code></li>
<li>Shrink it by your desired amount. I originally opted for 40GB (or 40,000MB), but I found that for what I was doing with Linux (React Native and Android Emulation), I needed more, so I eventually expanded it to 70GB</li>
<li>You should see the unallocated space show up!</li>
</ol></section>
<section><h2 id="installing-ubuntu">Installing Ubuntu</h2><p>Alright, it’s show time! Plug your bootable Ubuntu USB in, restart your computer once more, hit <code>F12</code> and choose to Install Ubuntu from your USB. The installation process is fairly simple, you can follow Ubuntu’s <a href="https://www.ubuntu.com/download/desktop/install-ubuntu-desktop">Official Guide</a>. Because we are dual booting, I chose to install it alongside Windows boot manager. Now that we’ve unallocated some space for Ubuntu and switched to AHCI mode, it should recognize the unallocated space as an option and install it to that space. Whew! The hard part is over.</p></section>
<section><h2 id="fixing-the-touchpad">Fixing the Touchpad</h2><p>The first thing I noticed on my new Ubuntu install was that the trackpad didn’t have palm detection turned on, and it was making typing a nightmare. To resolve it, I installed Libinput and configured it with the following steps:</p><ol>
<li>Open Terminal, type <code>sudo apt-get install xserver-xorg-input-libinput</code></li>
<li>Edit the config file by typing <code>gedit /usr/share/X11/xorg.conf.d/90-libinput.conf</code>(or vi if you prefer vim). It may be located in a different location depending on your Ubuntu installation.</li>
<li>Scroll down and modify the Touchpad input class like so:</li>
</ol><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>Section &quot;InputClass&quot;</span></span>
<span class="line"><span>    Identifier &quot;libinput touchpad catchall&quot;</span></span>
<span class="line"><span>    MatchIsTouchpad &quot;on&quot;</span></span>
<span class="line"><span>    MatchDevicePath &quot;/dev/input/event*&quot;</span></span>
<span class="line"><span>    Driver &quot;libinput&quot;</span></span>
<span class="line"><span>    Option &quot;Tapping&quot; &quot;True&quot;</span></span>
<span class="line"><span>    Option &quot;PalmDetection&quot; &quot;True&quot;</span></span>
<span class="line"><span>    Option &quot;TappingDragLock&quot; &quot;True&quot;</span></span>
<span class="line"><span>EndSection</span></span>
<span class="line"><span></span></span></code></pre><p>Phew! Now we can finally type without wanting to pull our own hair out.</p></section>
<section><h2 id="fixing-the-time-zone-issue">Fixing the Time Zone Issue</h2><p>What? We’re still not done? :( There’s one more thing we need to fix before we log back into Windows again. There tends to be conflicts with how the time is stored when running Windows and Linux on the same machine. Luckily, it’s an easy fix if you want to run Linux on local time as opposed to UTC. On our version of Ubuntu, simply open up the terminal and type <code>timedatectl set-local-rtc 1</code>. <a href="https://help.ubuntu.com/community/UbuntuTime#Multiple_Boot_Systems_Time_Conflicts">The Ubuntu Website</a> offers alternative solutions if you prefer.</p><p>Now we’re finally done. Happy developing!</p></section>]]></content>
        <published>2017-02-06T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Animated CSS Dividers]]></title>
        <id>https://www.nicchan.me/blog/animated-css-divider/</id>
        <link href="https://www.nicchan.me/blog/animated-css-divider/"/>
        <updated>2016-10-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Using CSS to create simple gradient animations.]]></summary>
        <content type="html"><![CDATA[<p>Having grown up in the age of blinkies, I have a special fondness for animated elements. However, there is undoubtedly a certain level of restraint required when implementing animation today, too much and the page feels overwhelming very quickly. I’m particularly fond of this simple divider animation that I implemented for the Hexxel landing page.</p>
<section><h2 id="the-details">The Details</h2><p>Since we are unable to transition between gradients using CSS, some trickery was required. For the animated bar part, I set the background to a linear gradient between 3 colors, then sizing up the background to 200% in width, but 100% in height. To achieve the effect of a gradient transitioning, I transformed the background position back and forth.</p><p>The little nodes on the end of the divider are nothing special, simple pseudo-elements, but they certainly add to the network theme of the site, don’t you think?</p><p data-height="300" data-theme-id="0" data-slug-hash="PGJErg" data-default-tab="css,result" data-user="nchan0154" data-embed-version="2" class="codepen">See the Pen <a href="http://codepen.io/nchan0154/pen/PGJErg/">Animated Dividers</a> by Nic Chan (<a href="http://codepen.io/nchan0154">@nchan0154</a>) on <a href="http://codepen.io">CodePen</a>.</p></section>]]></content>
        <published>2016-10-02T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ramping up with Shopify]]></title>
        <id>https://www.nicchan.me/blog/ramping-up-with-shopify/</id>
        <link href="https://www.nicchan.me/blog/ramping-up-with-shopify/"/>
        <updated>2016-04-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A simple gulp workflow for Shopify.]]></summary>
        <content type="html"><![CDATA[<p>Just recently, I had the opportunity to build my first Shopify theme, and I had to learn to get productive quickly. Here are a few of the things that those who are coming from other platforms might want to take note of.</p>
<section><h2 id="setting-up-a-development-environment">Setting up a development environment</h2><p>Because Shopify doesn’t let you download their software, what I recommend doing is signing up for a <a href="https://www.shopify.com/partners">Partner Account</a>, which lets you create unlimited test stores (as well as pick up a percentage of profits if you refer customers to the platform). Rather than manually upload your theme files to your test store every time, you can either use the <a href="https://github.com/Shopify/shopify_theme">Shopify Theme Gem</a> or the <a href="https://apps.shopify.com/desktop-theme-editor">Mac Only Desktop Theme Editor</a> to sync all your theme code directly to the test store. I chose the gem - the set up instructions included in the repository were very straightforward and it didn’t take long before I was up and running.</p></section>
<section><h2 id="picking-a-starter-theme">Picking a starter theme</h2><p>Once your <code>theme watch</code> is up and running, you can choose to go with an existing Shopify starter theme to help speed up your development process. <a href="http://shopify.github.io/skeleton-theme/">Skeleton</a> is the more minimal of Shopify’s two official themes, with the bare minimum amount of boilerplate to get you started. <a href="http://shopify.github.io/Timber/">Timber</a> is the one I ended up using, it includes a helper class based grid system, an AJAX based interactive cart and responsive menus. If you’d prefer to roll with the more popular Foundation or Bootstrap, it might be wiser to go with the skeleton theme.</p><p>A shopify theme has the following folders:</p><ul>
<li>assets</li>
<li>config</li>
<li>layout</li>
<li>locales</li>
<li>snippets</li>
<li>templates</li>
</ul><p>If you poke around the assets folder, you might notice that all the styling is done in one giant .scss.liquid file, which Shopify compiles on its own servers. If you’re used to a modern, modularized CSS workflow, the ban against <code>@import</code> will seem like a nightmare. Running your own SCSS processor and uploading the compiled CSS is an option, but doing so won’t let you use liquid variables (aka Shopify settings you might want to include like colors and font sizes) within your SCSS. What I ended up doing was running a gulp task that watched a newly created scss folder. Upon detecting changes to the contents of that folder, it would use <code>gulp-cssimport</code> to string my stylesheets together into one giant .scss.liquid file that it then dumped into the assets directory. Below is my Gulpfile.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="js"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">var</span><span style="color:#4075BA;--shiki-dark:#B993FF"> gulp</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> =</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> require</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;gulp&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">var</span><span style="color:#4075BA;--shiki-dark:#B993FF"> cssimport</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> =</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF"> require</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;gulp-cssimport&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">var</span><span style="color:#4075BA;--shiki-dark:#B993FF"> globalConfig</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> =</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	src</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">:</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;scss&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, </span><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// The directory where all your SCSS is</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// Process CSS</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">gulp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">task</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;styles&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, </span><span style="color:#A75293;--shiki-dark:#CB96CC">function</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> () {</span></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">	return</span><span style="color:#4075BA;--shiki-dark:#B993FF"> gulp</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">src</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">globalConfig</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">src</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> +</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;/style.scss.liquid&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">)</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">pipe</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">cssimport</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">())</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">pipe</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">gulp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">dest</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;assets/&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">));</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// Watch files</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">gulp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">task</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;watch&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, </span><span style="color:#A75293;--shiki-dark:#CB96CC">function</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4"> () {</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	gulp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">watch</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#4075BA;--shiki-dark:#B993FF">globalConfig</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#4075BA;--shiki-dark:#B993FF">src</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> +</span><span style="color:#5F4C65;--shiki-dark:#B993FF"> &quot;/**/*.*&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, </span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">[</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;styles&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">]</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A6AA4;font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// Default task</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">gulp</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">.</span><span style="color:#1F7D89;--shiki-dark:#B8BAFF">task</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;default&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">, </span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">[</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;watch&quot;</span><span style="color:#4F4D7E;--shiki-dark:#D4D4D4">]</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"></span></code></pre><p>And my style.scss.liquid looked something like this:</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="scss"><code><span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@import</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> url</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&#39;_variables.scss.liquid&#39;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@import</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> url</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&#39;libs/_timber.scss.liquid&#39;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A75293;--shiki-dark:#CB96CC">@import</span><span style="color:#1F7D89;--shiki-dark:#83CDFF"> url</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">(</span><span style="color:#5F4C65;--shiki-dark:#B993FF">&#39;components/_header.scss.liquid&#39;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">);</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">...</span></span>
<span class="line"></span></code></pre><p>This way, you get to structure your scss however you are used to doing it. Crisis averted!</p></section>
<section><h2 id="creating-your-own-settings">Creating your own settings</h2><p>Those of you coming from Wordpress will be pleased to know that adding new settings to Shopify is as easy as changing the <code>config/settings_schema.json</code> file. Below is a snippet from mine.</p><pre class="astro-code astro-code-themes blog light blog dark" style="background-color:#fafafa;--shiki-dark-bg:#17162b;color:var(--color-violet);--shiki-dark:#d4d4d4;overflow-x:auto" tabindex="0" data-language="json"><code><span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">{</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	&quot;name&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;Colors&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">	&quot;settings&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: [</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		{</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;type&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;header&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;content&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;TNB Theme Colors&quot;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		},</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		{</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;type&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;color&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;id&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;tnb_color_bg&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;label&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;Background Color&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;default&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;#222222&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">,</span></span>
<span class="line"><span style="color:#4075BA;--shiki-dark:#B993FF">			&quot;info&quot;</span><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">: </span><span style="color:#5F4C65;--shiki-dark:#B993FF">&quot;Background color&quot;</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">		}</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">	]</span></span>
<span class="line"><span style="color:var(--color-violet);--shiki-dark:#D4D4D4">}</span></span>
<span class="line"></span></code></pre><p>This setting can be accessed in your .scss.liquid files just as easily, like so <code>{% raw %}{{settings.tnb_color_bg}}{% endraw %}</code> .</p></section>
<section><h2 id="final-observations">Final observations</h2><p>While I found developing for Shopify to be a breath of fresh air compared to working with Wordpress (figuring out how to handle theme settings + SCSS in Wordpress is just plain shitty), I have a few caveats. Shopify doesn’t let you customize the checkout.liquid template (the template that handles the checkout process) unless you are a Shopify Plus customer. It also doesn’t make it particularly easy to filter by product variants, which is the standard way to assign things like colors and sizes to a product. Those who might want complete control over the entire user experience or offer complicated product filtering without resorting to workarounds like manually managing tags should seek out a more robust platform, but if ease to use is a priority, I definitely wholeheartedly recommend Shopify.</p></section>]]></content>
        <published>2016-04-07T00:00:00.000Z</published>
    </entry>
</feed>