Gnome Calendar: A New Era of Accessibility Achieved in 90 Days

3 months ago 2

Jul 25, 2025

Disclaimer(s)

This article is highly technical and goes into the inner workings of accessibility in GTK. In hopes to make it as clear as possible and avoiding ambiguity, you may notice a lot of redundancy, such as reusing nouns instead of using pronouns. This may set the article’s tone as emotionless and robotic.

Table of Contents
  1. Introduction
  2. Calendaring Complications
    1. Accessibility Trees Are Insufficient for Calendaring
    2. Negative Implications of Accessibility due to Maximizing Performance
  3. Improving the Existing Experience
    1. Improving Focus Rings
    2. Improving the Calendar Grid
    3. Improving the Calendar List Box
  4. Implementing Accessibility Functionality
    1. Making the Event Widget Accessible
    2. Making the Month and Year Spin Buttons Accessible
      1. Implementing GtkAccessible and GtkAccessibleRange
      2. Providing Top-Level Semantics to a Child Widget As Opposed to the Top-Level Widget Is Discouraged
  5. The Now and Future of Accessibility in GNOME Calendar
  6. Conclusion

Introduction§

There is no calendaring app that I love more than GNOME Calendar. The design is slick, it works extremely well, it is touchpad friendly, and best of all, the community around it is just full of wonderful developers, designers, and contributors worth collaborating with, especially with the recent community growth and engagement over the past few years. Georges Stavracas and Jeff Fortin Tam are some of the best maintainers I have ever worked with, especially Jeff’s underappreciated superhuman capabilities to voluntarily coordinate huge initiatives and issue trackers.

One of many Jeff’s initiatives is gnome-calendar#1036: the accessibility initiative. It is a big and detailed list of issues related to accessibility, and regularly gets updated. The upcoming release of GNOME, 49, will feature the biggest update GNOME Calendar has ever received (excluding the initial release). It will also be the accessibility update, where we managed to turn GNOME Calendar from an app that was literally unusable with a keyboard and assistive technology, to an app that is actually functional with a keyboard and screen reader in about three months.

This article will explain in details about the fundamental issues that held back accessibility in GNOME Calendar since the very beginning of its existence, the progress we have made with accessibility as well as our thought process in achieving it, and the now and future of accessibility in GNOME Calendar.

Calendaring Complications§

On a desktop or tablet form factor, GNOME Calendar has a month view and a week view, both of which are a grid comprising of cells representing a time frame. In the month view, each row is a week, and each cell is a day. In the week view, the time frame within cells varies on the zooming level.

There are mainly two reasons GNOME Calendar was inaccessible: firstly, the accessibility tree does not cover logically and structurally complicated workflows and designs such as a calendaring app; and secondly, significant negative implications of accessibility due to maximizing performance.

Accessibility Trees Are Insufficient for Calendaring§

The accessibility tree is rendered insufficient specifically for calendaring apps, namely by the flexibility and volatility of events, tailoring the entire interface and experience around that flexibility, and the visual representation of GNOME Calendar.

Firstly, the flexibility of events significantly complicates the widget tree. Events are highly flexible, because they are time-based. An event can last a couple of minutes, but it can as well last for hours, days, weeks, or even months. It can start in the middle of a day and end on the upcoming day; it can start by the end of a week and end at the beginning of the upcoming week. Essentially, there are, quite literally, an infinite amount of combinations of events.

Since events can last more than a day, cell widgets cannot hold any event widget, because otherwise event widgets would not be capable of spanning across cells. As such, event widgets are overlaid on top of cell widgets and positioned based on the coordinates of each widget. However, since cell widgets cannot hold a meaningful link with event widgets, there is no way to easily ensure there is a link between an event widget and a cell widget.

Secondly, the visual representation of GNOME Calendar is fundamentally incompatible with accessibility trees. GNOME Calendar’s month and week views are visually 2.5 dimensional: Grid layouts are structurally two-dimensional, but overlaying event widgets adds the additional layer. Conversely, accessibility trees are fundamentally two-dimensional, so GNOME Calendar’s representation cannot be sufficiently adapted into a two-dimensional logical tree.

In summary, accessibility trees are insufficient for GNOME Calendar. The flexibility of event widgets limits the capability of linking cell widgets with event widgets, so event widgets are instead overlaid on top, making the visual representation 2.5 dimensional. However, the layer added by the overlay makes it fundamentally impossible to adapt to a two-dimensional accessibility tree.

Negative Implications of Accessibility due to Maximizing Performance§

Unlike the majority of apps, GNOME Calendar’s complexity requires to maximize performance, but doing so severely impacted accessibility and rendered it unusable with a keyboard.

For context, GNOME Calendar’s layout and widgetry consist of custom widgets and complex calculations according to several factors, such as:

  • making sure performance is maintained with hundreds, if not thousands of instances of widgets;
  • the size of the window;
  • the height and width of each cell widget;
  • where to position each event widget, including when there are already multiple event widgets overlaid on a cell widget;
  • whether the event widget can fit inside a cell widget or not;
  • what went wrong in my life to work on a calendaring app written in C.

One way to minimize that problem is by creating custom widgets that are minimal and only fulfill the purpose we absolutely need. However, this comes at the cost of needing to reimplement most functionality, including most, if not all accessibility features and semantics, such as keyboard focus.

While GTK’s widgets are great for general purpose use cases and do not have any performance impact with limited instances of them, performance starts to deteriorate on weaker systems when there are hundreds, if not thousands of instances in the view, because they contain a lot of functionality that event widgets may not need.

In the case of the GtkButton widget, it has a custom multiplexer, it applies different styles for different child types, it implements the GtkActionable interface for custom actions, and more technical characteristics. Other functionality-based widgets will have more capabilities that might impact performance with hundreds of instances.

To summarize, GNOME Calendar reduces overhead by creating minimal custom widgets that fulfill a specific purpose. This unfortunately severely impacted accessibility throughout GNOME Calendar and made it unusable with a keyboard, as some core functionalities, accessibility features and semantics were never (re)implemented.

Improving the Existing Experience§

Despite being inaccessible as an app altogether, not every aspect was inaccessible in GNOME Calendar. Most areas throughout the app worked with a keyboard and/or assistive technologies, but they needed some changes to improve the experience. For this reason, this section is reserved specifically for mentioning the aspects that underwent a lot of improvements.

Improving Focus Rings§

The first major step was to improve the focus ring situation throughout GNOME Calendar. Since the majority of widgets are custom widgets, many of them require to manually apply focus rings. !563 addresses that by declaring custom CSS properties, to use as a base for focus rings. !399 tweaks the style of the reminders popover in the event editor dialog, with the addition of a focus ring.

We changed the behavior of the event notes box under the “Notes” section in the event editor dialog. Every time the user focuses on the event notes box, the focus ring appears and outlines the entire box until the user leaves focus. This was accomplished by subclassing AdwPreferencesRow to inherit its style, then applying the .focused class whenever the user focuses on the notes.

Improving the Calendar Grid§

The calendar grid on the sidebar suffered from several issues when it came to keyboard navigation, namely:

  • pressing ↹ would focus the next cell in the grid up until the last cell;
  • when out of bounds, there would be no auditory feedback;
  • on the last row, pressing ↓ would focus a blank element; and
  • pressing → in left-to-right languages, or ← in right-to-left languages, on the last column would move focus to a completely different widget.

While the calendar grid can be interacted with using a keyboard, that keyboard experience was far from desired, because it took several key presses to get to the next widget. It was also easy to unintentionally lose track of focus.

!608 addresses these issues by overriding the Gtk.Widget.focus () virtual method. Pressing ↹ or Shift+↹ skips the entire grid, and the grid is wrapped to allow focusing between the first and last columns with ← and →, while notifying the user when out of bounds.

Improving the Calendar List Box§

Note

The calendar list box holds a list of available calendars, all of which can be displayed or hidden from the week view and month view. Each row is a GtkListBoxRow that holds a GtkCheckButton.

The calendar list box had several problems in regards to keyboard navigation and the information each row provided to assistive technologies.

The user was required to press ↹ a second time to get to the next row in the list. To elaborate: pressing ↹ once focused the row; pressing it another time moved focus to the check button within the row (bad); and finally pressing the third time focused the next row.

Row widgets had no actual purpose besides toggling the check button upon activation. Similarly, the only use for a check button widget inside each row was to display the “check mark” icon if the calendar was displayed. This meant that the check button widget held all the desired semantics, such as the “checkbox” role and the “checked” state; but worst of all, it was getting focus. Essentially, the check button widget was handling responsibilities that should have been handled by the row.

Both inconveniences were addressed by !588. The check button widget was replaced with a check mark icon using GtkImage, a widget that does not grab focus. The accessible role of the row was changed to “checkbox”, and the code was adapted to handle the “checked” state.

Implementing Accessibility Functionality§

Accessibility is often absolute: there is no ‘in-between’ state; either the user can access functionality, or they cannot, which can potentially make the app completely unusable. This section goes in depth with the widgets that were not only entirely inaccessible but also rendered GNOME Calendar completely unusable with a keyboard and assistive technology.

Making the Event Widget Accessible§

Note

GcalEventWidget, the name of the event widget within GNOME Calendar, is a colored rectangular toggle button containing the summary of an event.

Activating it displays a popover that displays additional detail for that event.

GcalEventWidget subclasses GtkWidget.

The biggest problem in GNOME Calendar, which also made it completely impossible to use the app with a keyboard, was the lack of a way to focus and activate event widgets with a keyboard. Essentially, one would be able to create events, but there would be no way to access them in GNOME Calendar.

This entire saga began all thanks to a dream I had, literally, which was to make GcalEventWidget subclass GtkButton instead of GtkWidget directly. The thought process was: GtkButton already implements focus and activation with a keyboard, so inheriting it should therefore inherit focus and activation behavior.

In merge request !559, the initial implementation indeed subclassed GtkButton. However, that implementation did not go through, due to the reason outlined in § Negative Implications of Accessibility due to Maximizing Performance.

Despite that, the initial implementation instead significantly helped us figure out exactly what were missing with GcalEventWidget: specifically, setting Gtk.Widget:receives-default and Gtk.Widget:focusable properties to “True”. Gtk.Widget:receives-default makes it so the widget can be activated how ever desired, and Gtk.Widget:focusable allows it to become focusable with a keyboard. So, instead of subclassing GtkButton, we instead reimplemented GtkButton’s functionality in order to maintain performance.

While preliminary support for keyboard navigation was added into GcalEventWidget, accessible semantics for assistive technologies like screen readers were severely lacking. This was addressed by !587, which sets the role to “toggle-button, to convey that GcalEventWidget is a toggle button. The merge request also indicates that the widget has a popup for the event popover, and has the means to update the “pressed” state of the widget.

In summary, we first made GcalEventWidget accessible with a keyboard by reimplementing some of GtkButton’s functionality. Then, we later added the means to appropriately convey information to assistive technologies. This was the worst offender to have an accessible calendar app, but we finally managed to resolve it!

Making the Month and Year Spin Buttons Accessible§

Note

GcalMultiChoice is the name of the custom spin button widget used for displaying and cycling through months and/or years.

It comprises of a “decrement” button to the start, a flat toggle button in the middle that contains a label that displays the value, and an “increment” button to the end. Only the button in the middle can gain keyboard focus throughout GcalMultiChoice.

In some circumstances, GcalMultiChoice can display a popover for increased granularity.

GcalMultiChoice was not interactable with a keyboard, because:

  1. it did not react to ↑ and ↓ keys; and
  2. the “decrement” and “increment” buttons were not focusable.

For a spin button widget, the “decrement” and “increment” buttons should generally remain unfocusable, because ↑ and ↓ keys already accomplish that behavior. Furthermore, GtkSpinButton’s “increment” (+) and “decrement” (-) buttons are not focusable either, and the Date Picker Spin Button Example by the ARIA Authoring Practices Guide (APG) avoids that functionality as well.

However, since GcalMultiChoice did not react to ↑ and ↓ keys, having the “decrement” and “increment” buttons be focusable would have been a somewhat acceptable workaround. Unfortunately, since those buttons were not focusable, and ↑ and ↓ keys were not supported, it was impossible to increment or decrement values in GcalMultiChoice with a keyboard.

Additionally, GcalMultiChoice lacked the semantics to communicate with assistive technologies. So, for example, a screen reader would never say anything meaningful.

All of the above problems remained problems until merge request !603. For starters, it implements GtkAccessible and GtkAccessibleRange, and then implements keyboard navigation.

Implementing GtkAccessible and GtkAccessibleRange§

The merge request implements the GtkAccessible interface to retrieve information from the flat toggle button.

Fundamentally, since the toggle button was the only widget capable of gaining keyboard focus throughout GcalMultiChoice, this caused two distinct problems.

The first issue was that assistive technologies only retrieved semantic information from the flat toggle button, such as the type of widget (accessible role), its label, and its description. However, the toggle button was semantically just a toggle button; since it contained semantics and provided information to assistive technologies, the information it provided was actually misleading, because it only provided information as a toggle button, not a spin button!

So, the solution to this is to strip the semantics from the flat toggle button. Setting its accessible role to “none” makes assistive technologies ignore its information. Then, setting the accessible role of the top-level (GcalMultiChoice) to “spin-button” gives semantic meaning to assistive technologies, which allows the widget to appropriately convey these information, when focused.

This led to the second issue: Assistive technologies only retrieved information from the flat toggle button, not from the top-level. Generally, assistive technologies retrieve information from the focused widget. Since the toggle button was the only widget capable of gaining focus, it was also the only widget providing information to them; however, since its semantics were stripped, it had no information to share, and thus assistive technologies would retrieve absolutely nothing.

The solution to this is to override the Gtk.Accessible.get_platform_state () virtual method, which allows us to bridge communication between the states of child widgets and the top-level widget. In this case, both GcalMultiChoice and the flat toggle button share the state—if the flat toggle button is focused, then GcalMultiChoice is considered focused; and since GcalMultiChoice is focused, assistive technologies can then retrieve its information and state.

The last issue that needed to be addressed was that GcalMultiChoice still did not provide any of the values to assistive technologies. The solution to this was straightforward: implementing the GtkAccessibleRange interface, which makes it necessary to set values for the following accessible properties: “value-max”, “value-min”, “value-now”, and “value-text”.

After all this effort, GcalMultiChoice now provides correct semantics to assistive technologies. It appropriately reports its role, the current textual value, and whether it contains a popover.

To summarize:

  • The flat toggle button was the only widget conveying information to assistive technologies, as it was the only widget capable of gaining focus and providing semantic information. To solve this, its semantics were stripped away.
  • The top-level, being GcalMultiChoice, was assigned the “spin-button” role to provide semantics; however, it was still incapable of providing information to assistive technologies, because it was never getting focused. To solve this, the state of the toggle button, including the focused state, carried over to the top-level to allow assistive technologies to retrieve information from the top-level.
  • GcalMultiChoice still did not provide its values to assistive technologies. This is solved by implementing the GtkAccessibleRange interface.

Providing Top-Level Semantics to a Child Widget As Opposed to the Top-Level Widget Is Discouraged§

As you read through the previous section, you may have asked yourself: “Why go through all of those obstacles and complications when you could have just re-assigned the flat toggle button as “spin-button” and not worry about the top-level’s role and focus state?

Semantics should be provided by the top-level, because they are represented by the top-level. What makes GcalMultiChoice a spin button is not just the flat toggle button, but it is the combination of all the child widgets/objects, event handlers (touch, key presses, and other inputs), accessibility attributes (role, states, relationships), widget properties, signals, and other characteristics. As such, we want to maintain that consistency for practically everything, including the state. The only exception to this is widgets whose sole purpose is to contain one or more elements, such as GtkBox.

This is especially important for when we want it to communicate with other widgets and APIs, such as the Gtk.Widget::state-flags-changed signal, the Gtk.Widget.is_focus () method, and other APIs where it is necessary to have the top-level represent data accurately and behave predictably. In the case of GcalMultiChoice, we set accessible labels at the top-level. If we were to re-assign the flat toggle button’s role as “spin-button”, and set the accessible label to the top-level, assistive technologies would only retrieve information from the toggle button while ignoring the labels defined at the top-level.

For the record, GtkSpinButton also overrides Gtk.Accessible.get_platform_state ():

1 2 3 4 5 6 7 8 9 10 11 12 13 14 static gboolean gtk_spin_button_accessible_get_platform_state (GtkAccessible *self, GtkAccessiblePlatformState state) { return gtk_editable_delegate_get_accessible_platform_state (GTK_EDITABLE (self), state); } static void gtk_spin_button_accessible_init (GtkAccessibleInterface *iface) { iface->get_platform_state = gtk_spin_button_accessible_get_platform_state; }

To be fair, assigning the “spin-button” role to the flat toggle button is unlikely to cause major issues, especially for an app. Re-assigning the flat toggle button was my first instinct. The initial implementation did just that as well. I was completely unaware of the Gtk.Accessible.get_platform_state () virtual method before finalizing the merge request, so I initially thought that was the correct way to do. Even if the toggle button had the “spin-button” role instead of the top-level, it would not have stopped us from implementing workarounds, such as a getter method that retrieves the flat toggle button that we can then use to manipulate it.

In summary, we want to provide semantics at the top-level, because they are structurally part of it. This comes with the benefit of making the widget easier to work with, because APIs can directly communicate with it, instead of resorting to workarounds.

The Now and Future of Accessibility in GNOME Calendar§

All these accessibility improvements will be available on GNOME 49, but you can the pre-release on the “Nightly GNOME Apps” DLC Flatpak remote on nightly.gnome.org.

In the foreseeable future, I want to continue working on !564 to make the month view itself accessible with a keyboard, as seen in the following:

A screen recording demoing keyboard navigation within the month view. Focus rings appear and disappear as the user moves focus between cells. Going out of bounds in the vertical axis scrolls the view to the direction, and going out of bounds in the horizontal axis moves focus to the logical sibling.

However, it is already adding 640 lines of code, and I can only see it increasing overtime. We also want to make cells in the week view accessible, but this will also be a monstrous merge request, just like the above merge request.

Most importantly, we want (and need) to collaborate and connect with people who rely on assistive technologies to use their computer, especially when everybody working on GNOME Calendar does not rely on assistive technologies themselves.

Conclusion§

I am overwhelmingly satisfied of the progress we have made with accessibility on GNOME Calendar in six months. Just a year ago, if I was asked about what needs to be done to incorporate accessibility features in GNOME Calendar, I would have shamefully said “dude, I don’t know where to even begin”; but as of today, we somehow managed to turn GNOME Calendar into an actual, usable calendaring app for people who rely on assistive technologies and/or a keyboard.

Since this is still Disability Pride Month, and GNOME 49 is not out yet, I encourage you to get the alpha release of GNOME Calendar on the “Nightly GNOME Apps” Flatpak remote at nightly.gnome.org. The alpha release is in a state where gays with disabilities can do crimes using GNOME Calendar to organize them thanks to the accessibility effort 😎 /j

Read Entire Article