Projects
-
What I Learned This Week: AI and Alt Text (Don't Do It)

For those of us who are sighted, it is easy to forget that alt text is a necessity for navigating the Internet for the millions of blind individuals who use screen readers. Not to mention, there are still, believe it or not, Internet users who do not load all images by default, whether to avoid some types of tracking or because their internet is spotty/unreliable. So if you’re reading this and ever work in the frontend, hopefully you are remembering to include alt text. (Thankfully, this blog generates its static files from Markdown, a language in which it’s easier to add alt text to images than not. But I’ll be the first to admit I could write better alt text.)
But isn’t writing alt text so much work? What if we let an AI write it for us?
Last week, my colleague gave an excellent presentation about the possibility of using AI to generate alt text. This is something that a number of websites are doing (see describeimage.ai, imageprompt.org/describe-image, aitools.inc/tools/ai-alt-text-generator, describepicture.org, and many, many others–I would assume that 99% of these are just wrapping an API call to ChatGPT).
I believe gen-AI tools are built into a number of CMSes as well. For example, here’s a blog post talking about how the edtech software Blackboard will flag images without alt text and offer to generate descriptions of the images, using Microsoft genAI tech.
However, the general consensus is that this tech is not yet ready to replace humans.
My colleague demoed a couple of images that are relevant to people who work in marketing. One of them was:
We all know what this picture is, right?An AI alt-text generator described this as “a simple black checkmark on a white background,” whereas humans would probably prefer this to have the alt text “Nike logo” or simply “Nike”.
My colleague also showed a product image of a shampoo bottle on a beach. Since we work in marketing, our clients use a lot of product images! AI described this (fake) product image as something like, “A white bottle, labeled ‘shampoo’, standing upright on a sandy beach.” Better alt text would be the name of the product, like “Ocean breeze shampoo.”
Especially if the image is used as a clickable link (say in an email where you want people to buy the shampoo). If an image is used as a link, most (all?) screen readers will use the alt text as the link, so a long, flowery description is not useful here.
AI image descriptors also miss important cultural context, as shown in this article from UNC’s digital accessibility office, showing an example where a Maori dancer performing a ceremonial haka dance is described as “a person with his tongue out.”
That said, not every website does alt text well at this time. I found this exercise from Clark University’s “Introduction to Societal Computing” (what a wonderful class title, can I audit?) in which students are asked to compare AI-generated alt text and human generated alt text from three websites: a news site, a large nonprofit, and any third site. In the example given, the professor compares alt text on the New York Times’ website, Wikipedia, and Clark University’s own site.
I’ll let you read the outcomes for yourself but the TLDR is, unsurprisingly, large websites do a good job of writing descriptive alt text that generative AI cannot outperform, but smaller websites often have useless or no alt text, in which case a generative-AI-written caption might be a slight improvement. But there’s another way to improve alt text, which is to write it!
I couldn’t find a lot of takes on this topic from screen-reader users themselves. The general consensus seems to be that some (bad) alt text is better than no alt text.
However, I was hoping to find posts such as, “My favorite website went from having good alt-text to mediocre alt-text overnight, and I suspect they’re using AI”, or “My favorite website went from having no alt-text to mediocre alt-text overnight, and I suspect they’re using AI.” The lack of such takes leads me to think that this technology isn’t widespread enough yet for people to have noticed.
If a website’s images have no alt text, some screen readers and other software can still identify images, using their own built-in software. I believe this feature is built into new versions of the screen-reader JAWS, for example. But this software isn’t necessarily any better at identifying images, and can often be worse. Users report “hallucinations” and bugs, in the forums I found.
Supporters of AI-generated alt text say that if your choice is between literally no alt text (forcing users to rely on their own software, if they have it) and alt text written by a generative AI and approved by a human being, the latter is better. I guess that’s true, but only barely.
My take? As web developers, we can do better than outsourcing image descriptions to generative AI.
Resources
-
Don't Sync State, Derive It! (With Apologies to Kent C. Dodds)
Syncing is for swimming, not for state.This is a pretty standard lesson (Kent C. Dodds talks about it a lot in his React courses and on his blog) but it’s still something that has taken me a while to internalize.
With BookGuessr, I have a bunch of state!
I have the list of all books that could possibly be part of the game, the list of books that is part of the current game, the score, the high score, the game’s status (started or ended), and probably some other stuff. This is a lot to keep track of, and despite knowing what I know, my first draft of the app looked like this:
const [allBooks,setAllBooks]= useState(initialListOfBooks); const [chosenBooks,setChosenBooks] = useState([]); const [score, setScore] = useState(0); const [gameIsActive,setGameIsActive] = useState(false); ...and so on. Easy to write, not so easy to track.
Now, if a player chooses a book, we need to:
- remove that book from the list of available books
- add that book to the list of chosen books
- check if the score needs to increase, and increase it if so
- check if the game needs to end, and end it if so
That’s four separate state variables we need to manage! But we really only need to manage one state, if we turn our
allBooksvariable into something like this:[{'title': 'The Grapes of Wrath','author':'John Steinbeck','year':1939,'correct':true},{'title': 'Middlemarch','author':'George Eliot','year':1871},{'title': 'Snow Crash','author':'Neal Stephenson','year':1992,'correct':false}...]There are probably lots of ways to slice this, but this is the structure I have decided on (for now). Now,
scoreis calculated by performingallBooks.filter(book=>book.correct).length,chosenBooksare calculated by filtering on the same condition and sorting by year,gameIsActivecan be calculated by finding if any item in the array has theincorrectkey, and so on.This turns the above code into something more like:
const [allBooksForGame, setAllBooksForGame] = useState(allBooksWithDates()); const currentBook = chooseNextBook(allBooksForGame); const score = calculateScore(allBooksForGame); const highScore = calculateHighScore(score);This is pretty clean (the implementations of the helper functions I’ll leave up to the reader), but more importantly, when
allBooksForGamechanges, everything else updates without the programmer having to do anything. -
The Making of (and Redesigning of) BookGuessr
A couple of years ago, I got into my head the idea that I wanted to make a Wikitrivia style game with novels. I love to read, there’s a lot of publicly available data about books out there, why not? I made the first prototype in a weekend, using a list of 1000 Novels You Must Read from The Guardian, and data from The Open Library, and it worked, but.
I didn’t really want to show it to anyone because I really didn’t like how it looked, or behaved, and don’t even get me started on how it worked (read: didn’t work) on mobile.
Recently, I got a bee in my bonnet about redesigning it. I’d take another weekend and just spiff up the CSS and be done.
Lies, all lies.
First I spiffed up the CSS. I’m not at all a designer, so this was harder than it sounds. But I used a few tips from Erik Kennedy and I think I made it better.
Before
AfterThen, because the mechanic of hovering over where you want to place a book is not just mobile-unfriendly but mobile-impossible, I remade the entire site using a drag-and-drop library. I chose React-DND-kit pretty much at random, and once I figured out its quirks, I can say I’m pretty happy with it, but there may be a future blog post forthcoming about all said quirks.
Then, because I was running into some annoying React off-by-one bugs related to state being set when I didn’t expect it, I ripped out all the game logic and redid that. (Future blog post coming: Don’t store state, derive it!)
This wasn’t a ton of coding work, but you know how side projects can drag on. So I’m happy to say, six months after thinking I’d just “take a weekend” to do a little cleanup, Bookguessr is finally ready for the world.
Until I get the itch to redesign it again. Which might be tomorrow.
-
I'm on a Podcast
Last month I had the honor to appear on the “Her Corporate Compass” to talk about my approach to asking questions. (previously, and previously).
You can listen to it here:
If Spotify isn’t your thing, it’s also on Apple Podcasts here.
I will be completely transparent: I’m still a little scared of the sound of my own voice, so I have not listened to this entire episode yet. However, I have listened to Elhannah’s previous episodes and so I know that she did a great job, and any problems or issues are on me, not her. :)
Thank you so much Elhannah for having me!
-
Secrets of the Git Commit Hash

I attended an online presentation recently about very specific ways git can get messed up. To be clear, git can get messed up in many ways, but this fascinating presentation, by Mike Street, was about just some of the ways we run into problems with git.
Have you ever gotten this message:
Updates were rejected because the tip of your current branch is behind its remote counterpart. If you want to integrate the remote changes, use 'git pull' before pushing again.This USUALLY means exactly what it sounds like (assuming that this message sounds like anything to you): The branch you were working on has changed on the remote, and before you push your changes to the remote, you should pull the new changes to your computer and integrate them, otherwise you might break something.
But how does git know that something has changed at the remote? Git does this by comparing commit hashes, the 40-character strings that uniquely identify a specific commit.
As I think I understand it, your git talks to the remote git and says, essentially, hey, I’ve got
develophere, and before I made my changes, it had the hashabc123.And remote git (on github or elsewhere) says, “Cool, yeah,
developwas at abc123 last time you pushed, so let’s go ahead and add your changesdef456.”But let’s say your coworker updated
developwhile you were working.Now
developpoints to a different commit hash (now callednew-work), and you get the above error.However!
This comparison can only occur because git is comparing commit hashes. The hash is made up of a bunch of information, and here I quote Mike Street’s presentation directly:
- the parent commit hash (or hashes, in the case of a merge)
- the commit message
- the commiter name and date of commit
- the author name and date of authorship (these can be different than the above)
- the file changes
- magic
(On a side note, if I understand this Stack Overflow answer correctly, creating this hash involves using the SHA-1 algorithm (at least) three times: the parent hash(es), the hash of each file that has changed, and the result of hashing the return of
git cat-file, which is what contains the ‘metadata’ about the commit. And then it’s all mushed together, I presume.)The upshot of all of this means that two commits that are functionally the same: same files changed, same changes within those files, etc., can still look different to git, because the hashes change whenever the metadata does. So the ‘updates were rejected’ error can occur even when there are no true updates.
And this is why if you do:
git commit -m "do something" git push git commit --amend #change the commit message git pushyou will get the original error message:
Updates were rejected because the tip of your current branch is behind its remote counterpart. If you want to integrate the remote changes, use 'git pull' before pushing again.The two commits are the same but git doesn’t know that!
At this point, if you are sure you are the only person working on this branch, you could do a
git push --forceto resolve the issue. But it’s better to avoid this problem in the first place, by not amending commits that have already been pushed to the remote.We will see similar issues when rebasing (because the parent commit hash, as well as the commit date, could change).
The short version of this insight is: the commit hash updates every time the commit, including the metadata, changes.
Now you know!
This presentation did not fundamentally change the way I use git: the way to avoid this problem was, and remains, “do not amend commits that have already been pushed to the remote.” But it did help me understand why this is the case. Thanks, Mike!
Resources
The presentation was part of Code and Coffee: A Virtual Coffee Conference. I believe that the individual talk will be posted shortly, but for now it is available as part of the livestream recording here, starting at about the 9 minute mark.


