Copilot Accessibility Practices

Building accessible Copilot interfaces for the Web.

This guide demonstrates how to implement the Copilot Accessibility Principles in practice on web interfaces. Each practice includes guidance on what to avoid, how to identify issues, recommended approaches, and real-world examples from Copilot experiences.

Designers can use the Annotation Toolkit to document these practices in their designs.

Structure

Structure speaks to the underlying semantics and craft of the HTML rendered to the DOM.

Utilize valid HTML

Related principles:

What to avoid

Certain declarations are not allowed HTML. Writing them to the DOM may visually render as desired, but also will break compatibility with assistive technologies.

How to determine if there is a problem

Validate your markup using a service such as the Nu HTML Validator.

What to do instead

Use valid HTML declarations.

Example

The list of “icebreaker” prompts located underneath the “Ask Copilot” input uses an ul, with one li per prompt. The icebreaker itself is a button element, which is nested inside the li. This code validates if supplied to the Nu HTML Validator service.


Ensure each view has a concise, descriptive title

Related principles:

What to avoid

Don’t list the title as only the name of the product or service (ex: just listing “Copilot” throughout the whole experience).

How to determine if there is a problem

Check the tab title, the title listed on the browser’s UI chrome, or the title element declared in the document’s head element in the DOM.

What to do instead

Prepend the overall purpose of the current view to the product or service name. Avoid being overly-descriptive, as well as including explicit instructions (ex: prefer “Settings - Copilot” over “You can configure your settings here - Copilot”).

Example

The Loop name is prepended to the view title when using its detailed view (ex: GitHub Battle Royale · Loops · GitHub”).


Don't over-use landmarks

Related principles:

What to avoid

Landmark regions should identify overall "areas" of the page, such as header, footer, primary navigation, and main content area. Using too many landmarks makes navigation via landmark tedious, which is counter to their intended purpose. Additionally, nesting landmarks can create additional confusion, as it may disrupt a user's mental map of how content is laid out.

How to determine if there is a problem

Use a browser plugin such as Landmark Navigation to review the type of landmarks used, how many are present, and if they are nested.

What to do instead

Use a simple, landmark structure that is as flat as possible. Use accessible names to disambiguate landmarks if more than one is present (ex: Using aria-label="Primary" for the primary navigation nav landmark, and aria-label="Conversations" for the conversation nav landmark).

You don’t have to specify content like “navigation” or “sidebar” in the aria-label. Using the appropriate landmark element will do this for you automatically.

Example

Copilot uses four landmarks: A main content area, a complementary sidebar, and nav for primary navigation and quick links.


Don't nest interactive controls

Related principles:

What to avoid

Interactive elements cannot be nested inside of other interactive elements.

How to determine if there is a problem

Validate your markup using a service such as the Nu HTML Validator.

What to do instead

Ensure interactive elements are declared as siblings in the DOM, or on separate areas of layout.

Example

The list of Copilot conversations includes two interactive elements per list item. The first interactive element links to the conversation in more detail, and the second is a button that allows you to rename or delete the conversation. The link and button are sibling elements in the DOM.

Color

Color can enhance a design's aesthetics, as well as draw attention to specific parts of the page. For more detailed guidance, see Color considerations.

Don't rely only on color to communicate meaning

Related principles:

What to avoid

Color, without some form of explanation of what the color represents, runs the risk of not being able to be perceived by individuals who have a color vision deficiency, or different cultural understandings of the color value used.

How to determine if there is a problem

Check to see if there is accompanying text or iconography to go along with the color used.

What to do instead

Use text and iconography to explain what the color is drawing attention to.

Example

Leaving a Spark name blank when attempting to rename it will show an error message. In addition to using the color red for the error message and its accompanying icon, the message itself describes the problem.


Ensure that color has sufficient contrast

Related principles:

What to avoid

Color values that are too light or similar to the background they are placed over runs the risk of not being able to be read by users with low vision.

How to determine if there is a problem

Use WebAIM’s Contrast Checker tool to evaluate the contrast ratio of the foreground color value compared to the background color value. GitHub requires a 4.5:1 ratio for text and 3:1 for icons and graphics.

What to do instead

Use the appropriate Primer color values as much as possible, as well as ensuring there is a proper semantic mapping (i.e. use --fgColor-link for links). This will ensure the color both meets contrast requirements as well as honor GitHub’s color themes.

Animation

Animation helps communicate when Copilot is thinking, processing, or streaming responses. However, animation must be implemented carefully to avoid accessibility barriers.

Avoid flashing, throbbing, and strobing animation effects

Related principles:

What to avoid

Animations that use rapid flashing, throbbing, and strobing animation effects can trigger conditions such nausea, migraines, and seizures. Additionally, many people who have attention disorders find them distracting to the point where they impede productivity.

How to determine if there is a problem

Use caution! Make sure you are not susceptible to animation-based triggers before evaluating an experience.

Check to see if an animation uses flashing, throbbing, and strobing animation effects. Be especially on the lookout for quickly repeating animation loops, as well as full-screen effects.

What to do instead

Consider removing the animated effect if it is not a critical part of communicating information. If the animated effect cannot be completely removed, conditionally disable it with a prefers-reduced-motion media query.

Also consider offering a way to pause animated effects for a non-prefers-reduced-motion experience, as not every user is aware of the operating system preference.


Avoid autoplaying media

Related principles:

What to avoid

Animation, video, and audio content that plays automatically can be distracting, disorienting, and disrespectful to the end user. It may also unnecessarily eat into their internet provider’s monthly bandwidth allocation.

How to determine if there is a problem

Check to see if there is animation, video, or audio that plays automatically when visiting a page or view.

What to do instead

Use an opt-in approach where users press a play button to begin playing media. If this isn't possible, offer this experience to users who have enabled prefers-reduced-motion on their device's operating system.

Headings

Headings allow screen reader users to quickly navigate through Copilot's interface and understand the structure of AI-generated content, conversation threads, and different sections of the AI experience.

Use headings to demarcate overall areas of the view

Related principles:

What to avoid

Without headings, screen reader users can't efficiently navigate between conversation threads, prompt inputs, AI responses, settings panels, and other sections of the Copilot interface. This is especially problematic when AI generates long responses or when multiple agents are involved.

How to determine if there is a problem

Use a browser plugin such as HeadingsMap to see the current headings being used on a view, as well as their associated levels.

What to do instead

Add headings to different overall important areas of each view of a Copilot experience.

Example

The start of the list of conversations in the sidebar is indicated by a heading called “Conversations”.


Adjust heading styles via CSS

Related principles:

What to avoid

Heading levels are significant and follow a sequential order. For example, a third level heading follows a second level heading and can't skip to a fifth level.

How to determine if there is a problem

Use a browser plugin such as HeadingsMap to see the current heading levels being used on a view and ensure levels are not skipped.

What to do instead

Set headings to use a logical order, then use CSS to adjust their visual styling. CSS styling will not affect how a heading is announced by assistive technology.

Example

A parent first-level heading is used to indicate the Space that is currently being used. It is followed by a child second-level heading called “Instructions”. The second-level heading has been styled to look smaller and like a natural part of the instructions input area.


Use visually-hidden headings where appropriate

Related principles:

What to avoid

There may be a lack of a heading in the design of a Copilot experience to indicate a significant area of a view. This could be because the surrounding design visually communicates it is significant. Users who cannot see the screen won’t be able to understand the visual significance.

How to determine if there is a problem

Use a browser plugin such as HeadingsMap to see the current headings being used on a view, and if there are visually significant areas that aren’t demarcated with a heading.

What to do instead

Add a heading with an appropriate level to the DOM, right before the area the heading will demarcate. Then, use Primer’s sr-only class to hide the heading visually, but ensure that screen readers can still access it.

Example

A visually-hidden heading in the Copilot sidebar called “Quick links” comes before the list of different service offerings (Agents, Loops, Spark, etc.).

Accessible names

Accessible names are what assistive technology announces for interactive elements. In Copilot interfaces, this includes controls for managing AI responses (regenerate, stop generation), navigating conversations, managing agents, and interacting with AI-generated content. They make up one of three important parts of creating accessible interactive elements, which also includes a role and an optional value.

Ensure each interactive element has an accessible name

Related principles:

What to avoid

Interactive elements that only use an icon or other visual, but don’t have a text equivalent.

How to determine if there is a problem

Inspect an interactive element using the browser's inspector tools. Check the "Name" entry under "Computed Properties" in the accessibility panel, and see if a string value is present. Apply the string value to the interactive element itself, not to its parent or child elements.

What to do instead

Provide a string value. Ideally the value is displayed to all users. If not, use techniques such as a visually-hidden div or span element, aria-label, or aria-labelledby to provide the accessible name. For icon-only buttons, see Tooltips guidance.

Example

The book icon button for Copilot spaces documentation has an accessible name of "Copilot Spaces documentation", provided by the aria-labelledby attribute.


Use concise, descriptive accessible names

Related principles:

What to avoid

Names for interactive elements that are exceedingly long, or include control hints that speak to how to operate them.

How to determine if there is a problem

  1. If the name is multiple words or sentences, and also these words or sentences do not communicate purpose.
  2. If the accessible name includes instructions (ex: “Click here to save your progress”).

What to do instead

Use a string value that describes functionality in an as straightforward way as possible. For more guidance, see Buttons.

Example

Copilot Spaces' "Install MCP" clearly and concisely indicates what action will be performed (installing), as well as for what (MCP).


Disambiguate repeated accessible names

Related principles:

What to avoid

Users who cannot see the screen, or have difficulty seeing the entire screen, may not know which control applies to what piece of content if multiple controls use the same accessible name.

How to determine if there is a problem

Check for an interactive UI element that is repeated two or more times throughout the page or view. If the interactive UI element’s accessible name does not speak to which content it alters, its accessible name is not specific enough.

What to do instead

Specify the item that is being modified by the interactive UI element by appending its name to the element’s accessible name. This can be done via techniques such as a visually hidden span or aria-label.

Example

Each Copilot conversation in the conversation sidebar is followed by an overflow button. The button's accessible name is "Manage: {conversation name}", supplied via an aria-label. This allows a user to know which conversation they are about to manage in a list of conversations.


Inputs

Inputs allow users to communicate with Copilot through prompts, configure AI behavior and preferences, and provide context that helps the AI respond effectively. Clear, accessible inputs are essential for users to successfully interact with AI.

Use visual labels for inputs

Related principles:

What to avoid

Unlabeled inputs and inputs that only use placeholders may not be understood by the people who use them. The lack of a label may make it unclear what input someone is expected to provide, and a placeholder may not directly communicate what is expected of someone to provide.

How to determine if there is a problem

An input does not have a label placed in close visual proximity to it, or only relies on a placeholder value.

What to do instead

Place a label in close visual proximity to the input.

Example

A label called, “Space name” is placed in close visual proximity to the input that allows a user to create the name for a Copilot Space.


Programmatically associate labels with their corresponding inputs

Related principles:

What to avoid

A label is wrapped in an element other than label. Additionally, no for attribute is present, which programmatically links the label to its associated input by using the input’s id value.

How to determine if there is a problem

Click the label. If focus is not moved to the corresponding input it is not programmatically associated.

What to do instead

Use a label element with a for attribute value that matches the id value of the input it is associated with.

Example

Clicking the label “Share link” will place focus inside the input that contains the shareable URL of a Copilot conversation.


Don't use placeholders as an input label

Related principles:

What to avoid

Don't use placeholders as the only way to communicate an input's purpose.

How to determine if there is a problem

Check to see if there is a visual label associated with the input in addition to the placeholder.

What to do instead

Consider ways to communicate primary and secondary information that are always persistent and don’t disappear when the input receives focus.

Example

The Copilot feedback dialog has an input with a visible label called “Message”, then a persistent message after the input that provides additional guidance. This includes things to watch out for, as well as links to self-serve help resources.


Don't rely on minlength or maxlength HTML attributes for validation

Related principles:

What to avoid

Certain native HTML validation attributes have accessibility issues baked into them. For example, the maxlength attribute's presence is not announced by assistive technologies. This means there is no feedback mechanism for when the maximum number of allowed characters has been reached.

How to determine if there is a problem

Inspect an input to see if minlength or maxlength are used.

What to do instead

Rely on Primer form validation practices to ensure your inputs can be used with assistive technology.

Example

An input that can be 10 characters or less has explicit instructions that let the user know about this limitation. The instructions are programmatically associated with the input, along with an accompanying live region announcement to let users who cannot see know they have hit the character limit.

Focus

Focus allows keyboard-based users to navigate through AI-generated content, interact with streaming responses, and control Copilot's behavior. Managing focus becomes especially important when AI generates new content dynamically, updates responses in real-time, or when agents complete actions. For comprehensive guidance, see Focus management.

Ensure focus is visible

Related principles:

What to avoid

The focus treatment needs a compliant contrast ratio.

How to determine if there is a problem

Take the color of the focus treatment and compare it to the background color the focus treatment is placed on top of. It should be a 3:1 ratio or higher, and you can use a tool like WebAIM’s Contrast Checker.

What to do instead

Use Primer’s focus ring treatment, with its associated CSS Custom Properties. This will ensure a compliant ratio regardless of what color theme a user has enabled.

Example

A user presses Tab. The button labeled, “Add repositories, files, and spaces” is surrounded in a blue ring, indicating that it has received focus.


Ensure focus is managed when content is deleted

Related principles:

What to avoid

When users delete Copilot conversations, clear AI-generated responses, remove agents, or discard generated artifacts, focus should not disappear, leaving keyboard users disoriented in the interface.

How to determine if there is a problem

The focus ring disappears following a destructive action. Pressing Tab after performing the places focus on the browser’s UI chrome or the first focusable item in the DOM (typically a skiplink or the logo in the top-lefthand corner).

What to do instead

It is considered good practice to place focus on the previous interactive element present in the DOM.

Example

A user elects to delete the third Copilot conversation in their sidebar list of conversations. After confirming they want to delete, focus is placed on the overflow menu on the second conversation in their list of sidebar conversations.


Ensure focus is managed when content is added

Related principles:

What to avoid

When Copilot creates new conversations, generates responses, adds new agents, or creates artifacts, focus should not get lost. This is especially important since AI often generates content asynchronously.

How to determine if there is a problem

The focus ring disappears following a creation event. Pressing Tab after performing the places focus on the browser’s UI chrome or the first focusable item in the DOM (typically a skiplink or the logo in the top-lefthand corner).

What to do instead

It is considered good practice to place focus on the first interactive element the newly created item contains, or another applicable UI element.

Example

Focus is placed on the “Ask Copilot” text input following the creation of a new conversation.

Live regions

Live regions are critical for AI interfaces because Copilot operates asynchronously: generating responses in the background, streaming content over time, and processing requests that may take several seconds or minutes. Screen reader users need to know when AI starts processing, when responses arrive, and when errors occur. For more information, see Assistive technology announcements.

Inform users that Copilot is working on a response for long-running tasks

Related principles:

What to avoid

Loading states that solely rely on animation cannot be understood by users who cannot see the screen.

How to determine if there is a problem

Use a screen reader such as VoiceOver or NVDA to prompt Copilot with a complicated task. If a loading animation is used, listen for a companion screen reader announcement that communicates the loading state.

What to do instead

Use a live region to periodically announce the response is still loading. Be sure to stop the loading announcements once the response from Copilot has been completed.

Example

Copilot chat will announce “loading” via a live region every five seconds while working on a reply.


Announce disruptions and errors to realtime tasks

Related principles:

What to avoid

AI operations can fail in various ways: LLM requests timing out, agents losing context, rate limits being hit, or generated content being rejected. When these failures only show visually (like a red error icon), screen reader users won't know the AI stopped working or why their request wasn't completed.

How to determine if there is a problem

Use a screen reader such as VoiceOver or NVDA to prompt Copilot with a complicated task. Use a code editor or browser dev tools to trigger an error with the LLM processing the task. Listen for a companion screen reader announcement that accompanies the error state.

What to do instead

Use a live region to announce the error message in conjunction with what is visually displayed to the user.

State

In AI interfaces, state communicates what Copilot is doing: whether an agent is processing a request, streaming a response, idle and ready for input, or encountering an error. Clear state communication helps users understand agent behavior and know when they can interact with different parts of the AI experience.

Utilize ARIA and HTML attributes to programmatically communicate state

Related principles:

What to avoid

An update in state is only communicated visually, and has no way of being understood by assistive technology.

How to determine if there is a problem

Indicating state is only performed via CSS, with no accompanying ARIA or HTML attribute used as a styling hook for the visual treatment.

What to do instead

Use an applicable ARIA or HTML attribute as the styling hook in CSS, or ensure the applicable ARIA or HTML attribute accompanies the visual styling.

Example

The Copilot breadcrumb navigation uses aria-expanded=”false|true” to communicate when a conversation’s rename and delete controls are announced by assistive technology as being collapsed away, or exposed after being activated.


Don't hardcode a state into an accessible name

Related principles:

What to avoid

State should be announced via ARIA or HTML attributes, and not as part of a piece of UI's accessible name.

How to determine if there is a problem

Inspect a node’s accessible name via the browser inspect tools. Be on the lookout for strings such as “expended”, “collapsed”, “pressed”, “current”, etc.

What to do instead

Use an applicable ARIA or HTML attribute to communicate the state, and remove the hardcoded string value from the component’s accessible name.

Example

aria-current="page" is declared on the current active Copilot service (ex: Spaces) instead of using aria-label=”Spaces (currently viewing)”.

Further reading