Choice-based authors, please double-space the menu

yep, it really is just a method of browsing that simply can’t be explained or taught, it’s way too different than how websites work to the eye. a whole lot of the stuff we assume is “just HTML” might well be a bit more in the audiobook version :slight_smile:

and i found a lot of that type of feature mostly through avoiding RSI. i tend to notice a lot of semantic issues just by not wanting to stretch to reach the mouse each time i want to move.

1 Like

Just thought I’d add this as a follow up since I did some more research. I have a feeling that Mother already knew this, but this is new to me.

Adding role='link' tabindex='0' isn’t enough for true keyboard accessibility. You can tab through links using tab and shift+tab, but you still need to click on them using the mouse, which kind of defeats the purpose.

To fix this, you have to use some javascript to add events to the links once the page renders. This will add the ability to click a link using space or enter. (The preventDefault() part prevents the page from scrolling when space is pressed).

document.addEventListener("keydown", function(event) {
	if (event.code === "Space" || event.code === "Enter") {
		var el = document.activeElement; 
		if (el.tagName !== "A" && el.getAttribute("role") !== "link") return;
	else if (event.code === "Escape") {
		// close dialog window

This code only has to be run once (it’ll take care of any newly added links) and should probably be placed in the windows.onload or $(document).ready sections (depending on whether you’re using jquery).

This will click a link when enter or space is pressed while the clickable element is tab focused. This will work automatically for any element that is either an anchor or has the “link” role, so it’ll also work on this: <span role="link" tabindex="0">test</span>. Additionally, you can throw some code in there to close any dialog windows you have when you press the escape key.

As an added bonus: if you have links that change color or something when you hover over them, you get retain that functionality with tabbing by using focus.

a:hover, a:focus {
    color: orange;

Edit: Discovered a more optimal way of doing it, and apparently event.keyCode is deprecated so using event.key instead.


I suggest instead not messing with event handlers. Give your links a fake href, e.g. href="javascript:void(0)" and they’ll be accessible out of the box.

1 Like

or making them buttons, which is really what they are, because they’re not actually causing a navigation, nor do they do so in notional terms. i personally think that most usages of anchors in hypertext fiction should be buttons styled to look like links, given the semantic ambiguity, and also the need for some workaround to make them work like buttons. there’s a reason buttons work more like what’s expected here than href-less anchors.

1 Like

I built my game using React and it kept throwing warnings when I gave anchor tags hrefs like that. I’m not sure what the concern was but I ended up just making them either styled buttons or spans with all the proper roles and event handlers set.

well, it’s because javascript:void(0) is unsafe in JSX. react doesn’t want to let you do anything that would execute code inside JSX from string sources. there’s a whole lot that the browser will let you do, or do for you, that react won’t do. same goes for syntax errors in JSX. a browser will try to do its best to reconstruct that if it’s HTML, but react will mostly explode. it’s also more semantic to give them a tab index than an href defined as undefined. this and other reasons are why there aren’t any links in accelerate, there’s just buttons.

(accelerate uses typescript and parses JSX from ink text, so it’s very relevant here.)

1 Like

I also got warnings for undefined hrefs or href=’#’. I got the sense it just really didn’t want me to use anchors unless they actually navigated to a new page. Which I guess makes sense, that’s what their expected behavior is.

1 Like

right, i should say this isn’t entirely clear from W3C standards, because the way hypertext games work doesn’t really match either “en bloc” applications like flash/unity, nor do they really act like a website with “pages” as atemporal locations and mostly visible links. they’re almost like a teletype or feed, but all the text is important. but in general, i think both points here are true:

  • don’t use event handlers (or any JS, for that matter) when you can handle it in HTML and CSS instead
  • buttons with tab indices of 0 are probably both more semantic and less surprising for users and devs alike than quasi-“fake” anchors which don’t actually navigate

But if they’re buttons instead of links, will screen readers get confused or noisy?

1 Like

my guess would be they’d actually be less noisy, or at least better fitting reader navigation, but my personal incapacities here are migraines and RSI, so i tend to use tab navigation much more than a screen reader. i think it’s part of the broader work of making sure a screen reader receives the proper semantics and hints needed that navigating by audio produces a mapping which is substantially similar to the visual layout of the page.

i think the bigger problem with screen readers isn’t so much the software but the expectation. when one “clicks” a link on a webpage with a screen reader, the predominant reaction of nearly every web page is to load an entirely new page and reset the focus position to the top of the document. that’s not at all what hypertext games do.

in our case, we were careful to add the necessary roles and hints to ensure that the “noise” is ignored by screen readers. some of that involved the group role, some the presentation role. a lot of the work gets done just by ensuring your container elements aren’t seen as semantic regions for navigation.

buttons fit this better semantically because users understand that buttons can do a range of things outside page navigation, including adding new content to the page.

1 Like

My orange text here is styled as role=button


I tested it with a screenreader. It reads “Amir’s BUTTON heavy head rests on your lap.” I think that makes sense though? The reader would want to know a button is there.


you can also change how this works, just so you know. it’s not something that should be overused, but if you wanted, you could use aria-roledescription to change what that says instead of BUTTON.

Interesting. But yeah, I could see that getting abused.

The one thing I haven’t figured out how to do yet is reset the “head” of the screenreader. When my content area changes to new text, I’d want it to start from there I guess.

1 Like

Im’ma have to buckle down and learn CSS at some point. In AXMA when you put a link alone on the line it displays as a button, which is wonderful. If there’s other text on the line, it’s an inline link as just a highlighted word.

Unfortunately the links in the menu are initially dictated by the theme setting and just display as highlighted words. I can access the StoryStyle passage and probably put more vertical padding between links somehow, but I every time I open up the CSS I tiptoe in like it’s that party in Eyes Wide Shut and I shouldn’t be there.



Making the links into <button> tags has a huge gotcha: there’s no way to make buttons display: inline, even if you use !important. If you mark them display: inline they’ll actually be display: inline-block.

The only way to make an inline button is to give a non-<button> element a role=button. But then, if you want it to behave like a button, you either have to mess around with keyboard handlers, or use some other element that behaves like a button. I recommend an <a> tag with some kind of href and role=button.

i don’t actually consider this a problem at all, for the exact reason outlined above. there’s simply not a good way to stick interaction elements into the middle of prose sentences and have it work well for screen readers, not without basically destroying the utility of the role description. it also produces navigational landmarks at random within sentences. neither of those get any better by having a link which doesn’t actually navigate in the middle of the sentence, imho. but i can certainly see how this would be a problem if one were to try to make a “cycling button” instead of a “cycling link,” or wanted to make an inline-link-dense twine-like.

given the apparent objective or recent topic here is accessible choice IF, i’d go further and to say that one probably shouldn’t have any inline interactable content as a core part of their choice design. it produces a lot more UX problems than it solves in UI problems:

  • visual contrast issues between interaction elements, text, and backdrops become severe enough for choices to be unreadable or invisible;
  • forgetting to add role=button produces confusion in voice output and expectation;
  • all novice or VI players expend non-trivial effort determining where all the choices are, how they relate to the context of the document, and how “choiceful” or agency-driven they are.

i consider all that more work than having a single container where the choices live, but i don’t imagine we feel all that differently there. it helps in our case that we surveyed a lot of the comments on public discussion of popular twine fiction, and one complaint that always popped up was “it’s hard for me to tell when i’m choosing, looking, moving, etc.”

I agree; this is (in part) why ChoiceScript puts all user-interactive options at the end of the screen.

1 Like

yep! mostly just wanted to make it super easy for anyone who does want to make that sort of fiction, which is totally fine – almost no one ever makes their time or money back in audience terms by futzing with ARIA, and accessibility should therefore be seen as a scope and budgetary concern as well as ethical inclusivity – to ignore what i’m saying as entirely personal preference. i think @brwarner did plenty well in figuring out how to deal with the UX hazards here, and i’m sure plenty of other entrants this year did as well.

to an extent (based on what you asked earlier, @brwarner), i’m not sure there’s much in the way of best practice for resetting the screen reader caret. part of this is because moving the caret is something which rarely happens in websites – neither marquees nor feeds, e.g. your twitter page, focus on new content when it’s generated, and when it is, it’s generally in reverse chronological order scrolling from below the interaction element. hypertext, even in the “simpler” context dan and i make use of, works the opposite way: the most consequential choices are usually the last text in the document, and clicking the link/button/semantically decorated span/etc. doesn’t just give more content, it gives more page. that’s very different than your average business website, where even if content isn’t visible on page load, the controls to reveal it, like toggles, are visible.

another problem here is that, between the lack of availability of a large audience for playing and testing hypertext games with screen readers, and the interrelated fact that it’s hard to make money catering to a small and poorly-understood audience, we still don’t really know what people would want or expect! should they be given a simple way to navigate back to the top? should it be automatically reset? should there be a non-visible, but audible output saying e.g. “you’re at the top of the new content”? i’m not really sure, and part of the reason i pushed so hard to launch our game as an extremely overlarge web page, rather than a desktop executable, is to generate possible responses about what would be preferred there.

1 Like

Resetting the page is common in single-page applications, which what a Twine game basically is.

I never found good resources on focusing the screen-reader. Here’s what seems to work for us:

  • Find the element you’d like to focus on, typically the first <p> in a section or something like that.
  • In CSS, set it to outline: none
  • Set its tabindex="-1"
  • Call .focus() on it, then immediately call. .blur(). That’s supposed to be enough, but it usually isn’t.
  • requestAnimationFrame. In the subsquent frame, call .focus()
  • rAF again to call .blur(), remove the tabindex, and remove outline: none.

Here’s the code we use in ChoiceScript:

var focusFirst = function() {
  var text = document.getElementById("text");
  if (text.firstElementChild) {
    var focusable = text.firstElementChild;
    if (/^img$/i.test(focusable.tagName) && focusable.complete === false) {
      focusable.addEventListener("load", focusFirst);
    focusable.setAttribute("tabindex", "-1");
    requestAnimationFrame(function() {
      requestAnimationFrame(function() {