Accessibility in Visual Studio and ASP.NET

Accessibility standards enable you to build Web pages that can be used by people who have disabilities. This topic provides an overview of the relevant standards and of some techniques for how to configure ASP.NET Web server controls in Web Forms pages to make sure that they generate accessible HTML.

Except as noted, the standards reviewed in this topic pertain to both HTML and XHTML, and references to HTML apply to both markup languages. For details about XHTML standards and how you can comply with them in ASP.NET, see XHTML Standards in Visual Studio and ASP.NET. For detailed information about how the HTML that is rendered for specific ASP.NET Web Forms server controls complies with accessibility standards, see ASP.NET Controls and Accessibility.

Note

This topics discusses general accessibility guidelines that apply no matter what ASP.NET technology you are using (Web Forms, MVC, or Web Pages). However, much of the topic concentrates on how accessibility works with ASP.NET Web Forms and with ASP.NET Web Forms server controls. In the Web Forms model, ASP.NET generates HTML markup for you, and you must be aware of how the markup is generated and how you can specify this. (In the MVC and Web Pages models, you create all your own markup.)

This topic contains the following sections:

  • Scenarios

  • Web Content Accessibility Guidelines (WCAG)

  • Accessibility for Rich Internet Applications (ARIA)

  • Client Script and Accessibility

  • Accessibility Features in ASP.NET

  • Guideline 1.1 - Providing Alternate Text for Images

  • Guideline 1.2 - Providing Alternatives for Time-Based Media

  • Guideline 1.3 - Separating Structure from Presentation

  • Guideline 2.1 - Making Functionality Keyboard-Accessible

  • Guideline 2.4 - Making Web Sites Navigable

  • Guideline 3.1 - Making Web Content Readable and Understandable

  • Guideline 3.2 - Responding Predictably to User Input

  • Guideline 3.3 - Helping Users Avoid and Correct Mistakes

  • Guideline 4.1 - Making Sure that Web Pages Are Robust

  • Validating Web Pages for Accessibility

Scenarios

Some people who browse the Web find it difficult or impossible to see a computer screen. Some are not able to work with a mouse. Some have difficulty reading, or they have difficulty learning how to navigate a complex site. For example, elderly persons frequently develop a combination of these disabilities, but they still have to use Web sites.

Some disabilities can only be overcome by the use of assistive technology. An example of assistive technology is screen reader software that converts text on a page into speech for people who cannot see the screen. Many accessibility standards are intended to make sure that assistive technology can work effectively with Web pages.

Other accessibility standards are intended to help make Web sites understandable and easy to use for people who use standard browsers, even if they do not require assistive technology. Complying with accessibility standards benefits all users, not just users who have disabilities.

Helping people who have disabilities and making your site easier to use for everyone are worthwhile goals. In addition, you might be legally required to comply with accessibility standards. Laws that mandate accessible Web sites include the following:

  • In the United States, any Web site that is developed by a federal agency is required by Section 508 of the Rehabilitation Act to be accessible to persons with disabilities. This law applies to federal agencies and to companies that contract with them. You can read the complete text of the Section 508 guidelines at the Section 508 Web site.

    Other U.S. laws that are not specifically oriented toward Web sites, such as the Americans with Disabilities Act (ADA), have been applied to Web sites. In 2007, a major retailer was sued because its Web site lacked accessibility features. The retailer agreed to make its Web site accessible and to pay damages of six million dollars to the plaintiffs. It also submitted to ongoing accessibility monitoring by the plaintiffs.

  • In Canada, the Treasury Board Common Look and Feel Standards require that Web sites developed by government agencies be accessible.

  • In Australia, the Disability Discrimination Act requires that all Web sites hosted on Australian servers (regardless of whether a site is a government Web site) be accessible.

  • The European Union is working on laws based on W3C standards.

Many other countries have similar legislation or regulatory requirements. For more information about accessibility laws, see the Web Accessibility Initiative on the World Wide Web Consortium (W3C) Web site.

Web Content Accessibility Guidelines (WCAG)

The most widely used accessibility guidelines are the Web Content Accessibility Guidelines (WCAG) that have been drafted by the World Wide Web Consortium (W3C). Version 1.0 of WCAG was published on May 5, 1999. That version has been superseded by WCAG 2.0, which was published on December 11, 2008. (For the complete specification of both versions, see the W3C Web site.) Many governments and organizations also publish guidelines for how to create accessible applications, but most of them derive from WCAG.

WCAG 2.0 is organized around four design principles of Web accessibility. Each principle has one or more guidelines, and each guideline has testable success criteria. The success criteria are the basis for determining whether a Web site conforms to the WCAG 2.0 guidelines.

The W3C site also provides recommended techniques that you can use to follow the guidelines and pass the success criteria. These techniques are not part of the official specification. They are intended to be changeable over time as technologies develop, whereas the guidelines and success criteria remain the same over time.

WCAG Design Principles

The four design principles of WCAG state that Web content must have the following characteristics:

  1. Perceivable.

    Information and user interface components must be presentable to users in ways that users can perceive. For example, a person who is blind cannot perceive an image on a screen, but the person can perceive words spoken by screen-reader software. By providing a textual description of the image, you make sure that the information that is on the page can be made perceivable.

  2. Operable.

    The user interface should not require interaction that a user is incapable of performing. For example, a user who cannot use a mouse must be able to navigate a Web site by using the keyboard.

  3. Understandable.

    Information in a Web page and the operation of the user interface must be understandable. For example, it is easier to navigate a Web site that displays navigation links in the same order and with the same appearance throughout the site than if navigation links are inconsistently displayed.

  4. Robust.

    A Web page's content must be compatible with a wide variety of browsers and assistive technology. As browsers and assistive technology software evolve, the content should remain accessible. For example, even if a particular browser adequately displays a certain form of non-standard HTML, you should avoid relying on that because future versions of the browser, or other browsers, might not behave the same way.

WCAG Guidelines

The guidelines for making sure that Web content is perceivable are as follows:

  • Provide alternate text for non-text content such as images. (1.1)

  • Provide alternate text for time-based media such as audio or video content. (1.2)

  • Structure content so that it can be presented in different ways (other than by a standard graphical browser) without losing information. (1.3)

    For example, make sure that the HTML of a Web form explicitly indicates which label elements pertain to which input elements, because otherwise a screen reader might announce them in a confusing order.

  • Make it easier to see and hear content. (1.4)

    For example, make sure that there is sufficient contrast between foreground and background colors and make sure the user can pause an audio track.

The guidelines for making sure that a Web site is operable are as follows:

  • Make all functionality available from a keyboard. (2.1)

  • Provide users enough time to read and use content. (2.2)

    For example, if a page is shown only for a few seconds before it automatically switches to a different page, display a pop-up window that gives the user an opportunity to delay the switch.

  • Do not design content in a way that is known to cause seizures. (2.3)

    Causing visual elements to flash more than three times a second can cause seizures.

  • Provide ways to help users navigate and find content. (2.4)

The guidelines for making sure that a Web site is understandable are as follows:

  • Make text content readable and understandable. (3.1)

  • Make Web pages appear and operate in predictable ways. (3.2)

  • Help users avoid and correct mistakes. (3.3)

The sole guideline for making sure that a Web site is robust essentially repeats the principle:

  • Maximize compatibility with current and future user agents. User agents include browsers and assistive technology software. (4.1)

    The primary way to follow this guideline is to make sure that Web pages contain only valid HTML.

WCAG Success Criteria and Levels of Conformance

Testable success criteria are specified for each guideline. For details about these test criteria, see the WCAG 2.0 specifications on the W3C Web site. The success criteria are grouped into three levels that specify increasing degrees of conformance to accessibility guidelines:

  • Level A.

    According to the W3C, you must follow the guidelines that enable a site to pass these basic success criteria. Otherwise, one or more groups of users will find it impossible to access certain information or features in the site.

  • Level Double-A (AA).

    You should also follow the guidelines that enable a site to pass these more rigorous guidelines. Otherwise, one or more groups of users will find it difficult to access certain information or features in the site.

  • Level Triple-A (AAA).

    You might decide to follow the guidelines that enable a site to pass these very rigorous guidelines. This level is reached by very few Web sites, and the W3C does not recommend it as a goal for a whole site because it is not possible to reach AAA conformance for some types of content

If your Web site conforms to the WCAG guidelines, you can display a logo that announces this to the users of the site. The logo indicates the conformance level that your site has attained.

If your Web site satisfies all Level A success criteria, you can display a logo that indicates Conformance Level A. To reach Conformance Level AA you must meet all Level A and AA success criteria (not just the level AA criteria). For Level AAA you must meet Level A and Level AA criteria in addition to the Level AAA criteria. For more information, see the page about WCAG 2.0 Conformance Logos on the W3C Web site.

Accessibility for Rich Internet Applications (ARIA)

The W3C is creating a new standard that is intended to make sure that assistive technology can work well with rich Internet applications (RIAs). An RIA in this context is a Web page that includes client controls (referred to as widgets in the W3C documents). These widgets typically consist of HTML elements and JavaScript or Ajax code. A calendar control that shows a calendar from which you can select a date to fill in a date text box is an example of a widget. The client-side markup generated by some ASP.NET Web Forms server controls are considered a widget, as are the controls in the ASP.NET Ajax Control Toolkit.

The ARIA standard consists primarily of new attributes that can be added to HTML elements. Assistive technology software can use these attributes to identify the function, properties, and state of widgets. The standards also include guidelines that specify how widgets should respond to keyboard input in order to make sure that they are made keyboard-accessible in a consistent manner.

The most important new attribute that ARIA introduces is the role attribute, which identifies the type of widget, as shown in the following example:

<table role="datepicker">

This attribute notifies assistive technology about the widget's function so that the assistive technology knows how to represent and work with the widget. In this example, instead of announcing a series of table cells, a screen reader can announce that a calendar is being presented from which the user can select a date.

Property and state attributes provide other information about the widget, as shown in the following example:

<button role=slider

  aria-valuemin="0"

aria-valuemax="100"

  aria-valuenow="0">

These attributes not only provide more information about the widget, but also enable assistive technology to programmatically work with the widget. In the example, a screen reader can announce that this widget is a slider control and that the slider's current setting is zero. The screen reader can programmatically move the slider by updating the aria-valuenow attribute within the limits that are indicated by the minimum and maximum values.

Some ARIA attributes are not specifically for widgets. For example, landmark roles (which are indicated by using the same role attribute) indicate the logical function of regions of a Web page, as in the following examples:

<div role="banner">

<div role="navigation">

<div role="main">

<div role="complementary">

A page whose main sections are marked with landmark roles enables screen reader software to read a list of section titles to the user. The user can then select one of the sections in order to instruct the screen reader to begin reading at a specific part of the page. This saves time because users can skip the sections they are not interested in.

Because ARIA is relatively new and is still being developed, there is limited support for ARIA attributes in Web browsers and in assistive technology software. In addition, pages that include the ARIA attributes may fail the W3C markup validation service. However, you can include ARIA attributes in Web pages, because the attributes are just ignored by browsers that do not support them.

For more information about ARIA, see the ARIA Overview on the W3C Web site. For information about ASP.NET controls that generate HTML that conforms to ARIA standards, see ASP.NET Controls and Accessibility. To add ARIA support for ASP.NET controls that do not inherently support ARIA, you can use JavaScript or expando attributes.

Client Script and Accessibility

WCAG 1.0 requires that Web pages work without using client script. The specification says the following:

Ensure that pages are usable when scripts, applets, or other programmatic objects are turned off or not supported. If this is not possible, provide equivalent information on an alternative accessible page. (6.3)

This requirement was prompted by the state of technology in 1999. Since then, assistive technology software has added support for JavaScript, and WCAG 2.0 allows Web pages to rely on client script.

However, if you must conform to an older accessibility standard, you might still be subject to this requirement. In that case, you should avoid ASP.NET controls that require JavaScript in order to function. For example, The LinkButton and ImageButton controls require client script in order to perform postbacks. For a list of all ASP.NET controls that use client script, see ASP.NET Web Server Controls That Use Client Script.

WCAG 2.0 does require that a Web site be operable by using a keyboard. Therefore, an accessible Web site cannot use JavaScript in a way that makes functionality available only through mouse actions. For example, if you write JavaScript code that provides certain functionality in response to a mouseover event, make sure that the same functionality can be accessed from the keyboard. All ASP.NET controls enable keyboard access by default or can be configured to enable keyboard access.

Accessibility Features in ASP.NET Web Forms Controls

ASP.NET Web Forms server controls are designed to be accessible by default. This means that they automatically render accessible HTML if possible, or they expose properties that you can set to make the pages accessible. For example, ASP.NET Web Forms controls offer the following features to support accessibility by default:

  • Generate HTML that uses CSS for visual formatting.

  • Use tables to present data, not to arrange visual elements on a page.

  • Provide indications of table structure by marking header and footer rows.

  • Associate labels with the controls that they pertain to.

  • Generate client script that is device independent, such as client script that responds both to mouse clicks and to keyboard actions.

  • Specify tab index settings for input elements.

  • Provide a way to specify a text equivalent for any non-text element.

Some controls generate HTML based on templates for which you provide the HTML. In those cases you must manually configure the markup in the templates so that the generated HTML complies with accessibility guidelines.

There are some exceptional situations in which controls generate HTML that may not comply with accessibility standards. ASP.NET 4 includes many enhancements that eliminate most of the exceptions that existed in earlier versions of ASP.NET, or that provide alternatives to them. For more information, see What's New in ASP.NET 4 and Visual Web Developer.

The following sections present techniques for creating accessible Web pages that conform to each WCAG guideline by using Visual Studio and ASP.NET. For some guidelines, there are no considerations that are specific to ASP.NET. Therefore, sections devoted to those guidelines are omitted in this part of the topic.

Guideline 1.1 - Providing Alternate Text for Images

Every image in a Web page should include an alt attribute. The alt attribute is used by assistive technologies such as screen readers to announce the content of the image to a user who cannot see the image. The following example shows an alt attribute on an HTML img element:

<img src="Products23.gif" alt="Image of Widgets" />

In order to decide what to put in the alt attribute, you must determine whether the image is only for decorative purposes or whether it provides information. If it provides information, you must also determine whether this information can be summarized in a short phrase or requires a longer block of text.

This section explains how to make sure that following types of images are accessible in ASP.NET Web sites:

  • Decorative images.

  • Images that communicate information.

  • Complex images.

  • Images that are generated by ASP.NET controls.

Decorative Images

If an image is used only as a design element and has no useful information to communicate, you must include an alt attribute but set it to an empty string, as shown in the following example:

<img src="PageDivider.gif" alt="" />

If you omit the alt attribute altogether, many screen readers will announce the file name, creating unnecessary clutter.

ASP.NET controls that render images omit the alt attribute in the rendered HTML if you assign an empty string to the AlternateText property. For example, suppose you add the following Image control to a page:

<asp:Image ImageUrl="PageDivider.gif" AlternateText=""

    runat="server" />

In this case, the following HTML is rendered:

<img src="PageDivider.gif" />

Notice that the alt attribute has not been rendered. This is the default behavior of all ASP.NET control attributes. When you do not assign a value to an attribute, the attribute is not rendered. However, in this case, you want ASP.NET to render an empty string as the alt attribute value. In order to make sure that the HTML that is rendered for an Image control includes alt="", you must set the GenerateEmptyAlternateText property to true, as shown in the following example:

<asp:Image ImageUrl="PageDivider.gif" GenerateEmptyAlternateText="true"

    runat="server" />

Images that Convey Information

For images that communicate information, the alt attribute should contain a description of the image or its function on the page. For example, if the image for a search button shows a magnifying glass, the appropriate alternate text would be "Search", not "Magnifying glass".

The alternate text should not just repeat the content of the file-name attribute. The purpose of the alt attribute is to communicate to someone who is blind the same information that the image communicates to someone who is sighted.

Composing an alt attribute value requires human interpretation of the meaning of an image. For this reason, the process of creating alt attributes cannot be automated.

The Image, ImageButton, and ImageMap controls include an AlternateText property that you can use to set the alt attribute, as shown in the following example:

<asp:Image ImageUrl="Products23.gif" AlternateText="Image of widgets"

    runat="server" />

Complex Images

The alt attribute is intended to provide a short description or summary of the image. This may be insufficient for complex images, such as organizational charts. In such cases you can provide a summary in the alt attribute and then provide detailed information in a separate Web page. You link the image to the descriptive Web page by using the longdesc attribute, as shown in the following example of an HTML img element:

<img src="OrgChart.gif"

  alt="Company Organization Chart"

  longdesc="OrgChartDescription.aspx" />

The ASP.NET Image control includes a DescriptionUrl property that corresponds to the HTML longdesc attribute. The following example shows how to use this property:

<asp:Image ImageUrl="OrgChart.gif"

    AlternateText="Company Organization Chart"

    DescriptionUrl="OrgChartDescription.aspx"

    runat="server" />

Images That Are Generated by ASP.NET Web Forms Controls

Some Web Forms controls automatically render images or links as part of their markup, such as the TreeView control, the Menu control, and Web Parts controls. In these cases, the control creates alternate text for each image or link.

For example, the TreeView control renders images for the expand and collapse buttons for each node. The TreeView control generates alternate text for these images based on the text of the node. By default, the alternate text for the image to expand a node that is named Start is rendered as Expand Start. You can specify custom alternate text by setting the ExpandImageToolTip and CollapseImageToolTip properties for the TreeView control.

Similarly, the Menu control renders alternate text for the links that it generates to expand and collapse menu items.

Similarly, the buttons in a Web Parts control title bar render alternate text that describes the function for each button.

Guideline 1.2 - Providing Alternatives for Time-Based Media

Video and audio content are called time-based media in the WCAG guidelines. You can provide time-based media in your Web page by using the Silverlight plug-in. If you do this, you should consider how to make the same information available to people who have vision or hearing impairment. The guidelines for guaranteeing accessibility when you use the media player depend on whether the content is live or prerecorded and whether the media includes video, audio, or both. The Level A success criteria apply only to prerecorded content (that is, they do not apply to live content):

  • For an audio clip that includes speech or dialog, you should make available text that provides the same information. For example, next to a Silverlight plug-in you could add a hyperlink to a page that provides a transcript.

  • For a video clip that does not contain audio, you should either make available alternate text or add a sound track in which someone describes the visual content. For example, if the video shows how to perform a task, next to the plug-in you could position a hyperlink that points to a page that provides step-by-step instructions in text.

  • For a video clip that includes a synchronized audio track, you should provide captions for people who have impaired hearing, and text or an audio clip for people who have impaired vision.

These guidelines apply only if the time-based media provides information that is not found in the text of the Web page. If the media only repeats information already provided in text, there is no need to provide an additional alternative.

Silverlight can also be used to create rich Internet applications (RIAs). For information about how to follow accessibility guidelines in Silverlight applications, see the Accessibility Overview in the Silverlight documentation Web site.

Guideline 1.3 - Separating Structure from Presentation

A key principle of accessible Web page design is that HTML should be semantically correct. This means that you should use HTML elements for their intended purposes.

For example, heading elements (h1, h2, h3) are intended to indicate a hierarchy of headings that describe the content of sections below them. Browsers give each heading element a default appearance to indicate the element's function on the page. However, a developer could use a heading element to get a particular appearance or could provide a custom style for headings without using h elements at all.

If a Web page has only h3 elements because the developer did not like the default font size of h1 and h2 elements, the screen reader user might wonder where the missing higher-level sections are. Or if a Web page uses bold text instead of h elements for headings, the user might be confused because the screen reader cannot differentiate between text that is bold for emphasis and text that is bold to function as a heading.

The correct way to structure markup is to use HTML elements appropriately and to use cascading style sheet (CSS) rules to specify their appearance. For example, use heading (h) elements to correctly structure a document, and use CSS rules to specify the appropriate font size for each heading level.

This section contains the following sections that explain how to avoid using tables to define page layout, and how to make sure that tables created by ASP.NET controls are accessible:

  • Designing page layout without tables.

  • Making HTML tables accessible.

  • Associating input fields with labels.

Designing Page Layout Without Tables

Developers often mix structure and presentation in Web page design by using table elements to lay out the page or to specify the relative locations of visual elements on the page. When tables are used for layout, the result is often a complex system of outer tables and embedded inner tables. Such structures are likely to cause screen readers to announce cell contents in a confusing sequence that is unrelated to the actual logical structure of the page.

There are many ways to use CSS with div and other HTML elements to control the appearance and location of visual features of the page. However, if you cannot avoid using tables for layout, you should verify that the content of the tables makes sense when linearized (that is, read in table-cell order: top row first, then the next row down, and so on).

For more information about how to use CSS, see Working with CSS Overview.

Using ASP.NET Web Forms Controls that Generate Tables for Page Layout

Some ASP.NET Web Forms controls automatically generate HTML tables for page layout, depending on how they are configured. The following table lists these controls and explains how to configure them to avoid generating tables that do not comply with accessibility guidelines.

Note

These controls generate semantically correct HTML and can use CSS instead of tables for formatting when the Control.RenderingCompatibility property is set to 4.0 or greater.

Controls

Comments

By default, these controls render HTML that is wrapped in a table element whose purpose is to apply inline styles to the whole control. (You apply inline styles by setting properties such as BackColor or CssClass.) You do not have to use inline styles if you use templates to specify how HTML will be rendered for these controls. In that case you can set the RenderOuterTable property to false to prevent the outer table from being rendered.

By default, these controls render lists as table elements. However, they can render ul (unordered list) elements, ol (ordered list) elements, or span elements instead, depending on how you set the RepeatLayout property. To render semantically correct markup, set the RepeatLayout property to UnorderedList or OrderedList.

The following formatting limitations apply if you use an ordered list or unordered list:

  • The layout direction must be vertical.

  • You cannot specify multiple columns.

  • Headers, footers, and separators are not supported.

By default, this control generates HTML that complies with accessibility guidelines in the following ways:

  • The generated HTML is structured as an unordered list (ul element).

  • CSS is used for visual formatting.

  • The menu behaves in compliance with ARIA standards for keyboard access.

  • ARIA role and property attributes are added to the generated HTML.

However, the control provides the RenderingMode property for backward compatibility. If you set this property to Table, the control will generate HTML that uses table elements for formatting, as it did in ASP.NET 3.5 and earlier versions

Making HTML Tables Accessible

Even if you use HTML tables to present tabular data instead of for page layout, they can cause accessibility problems. When the content of a table is read aloud, it is easy for the listener to lose track of the current position in the table and it can be hard to remember which heading a particular cell pertains to. However, you can include features that make tables easier to understand.

thead and th Elements

To make sure that screen reader software can clearly associate table cells with their associated headers, table headings should always be in th elements, and the heading row should be within a thead element, as shown in the following example:

    <table>
      <thead>
        <tr>
            <th>Product</th>
            <th>Price</th>
        </tr>
      </thead>
      <tbody>
        <tr>
            <td>Milk</td>
            <td>$2.33</td>
        </tr>
        <tr>
            <td>Cereal</td>
            <td>$5.61</td>
        </tr>  
      </tbody>
    </table>

Table with thead and th elements

Some developers avoid using th elements because they do not like the default visual appearance of these elements. However, the default appearance of an HTML element should not the determining factor in whether an element is used for particular purpose. If you want the column headings to resemble ordinary table cells, you can add a style rule such as the following:

<style type="text/css">

  th {text-align:left;font-weight:normal}

</style>

scope, headers, and axis Attributes

In order to make a table accessible, you should also explicitly indicate the heading or headings that are associated with each cell. There are several attributes that you can use for this purpose: scope, headers, and axis.

The scope attribute can be used to indicate whether a th element is a column heading or a row heading. For example, the following table contains both column headings and row headings, marked with th tags that use the scope attribute:

    <table>
      <thead>
        <tr>
          <th></th>
          <th scope="col">First Train</th>
          <th scope="col">Last Train</th>
        </tr>
      </thead>
      <tbody>
      <tr>
          <th scope="row">Alewife</th>
          <td>5:24am</td>
          <td>12:15am</td>
      </tr>
      <tr>
          <th scope="row">Braintree</th>
          <td>5:15am</td>
          <td>12:18am</td>
      </tr>  
      </tbody>      
    </table>

Table with scope attributes

This example table contains the schedule for the Red Line subway in Boston, Massachusetts. Notice that each column heading includes a scope="col" attribute, and each row heading includes a scope="row" attribute.

The scope attribute works well for simple tables. For more complex tables, a better method is to use the headers attribute. For example, a nested table might have three or more headings that are associated with a single cell. In the following example of a Red Line Schedule table, the cell that contains 5:24am pertains to three headings: First Train, Weekday, and Alewife:

Table with headers and axis attributes

The headers attribute enables you to explicitly identify the headings that pertain to each cell. You can specify multiple headings as a space-delimited list.

The axis attribute enables you to categorize headings. In the Red Line Schedule table, you can identify Alewife and Braintree as location headings, Weekday and Saturday as day headings, and First Train and Last Train as train headings. If a single heading belongs to multiple categories, you can specify a comma-delimited list for the axis attribute.

The following .aspx page renders the Red Line Schedule, using headers and axis attributes.

    <%@ Page %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
    
    <head id="Head1" runat="server">
      <title>Red Line Subway Schedule</title>
      <style type="text/css">
        caption {color:white;background-color:red;font-size:xx-large}
        table {width:500px;border-collapse:collapse}
        td,th {padding:5px}
        td {border:1px solid black}
        tbody th {text-align:right}
        .headerRow th {font-size:x-large;text-align:left}    
      </style>
    </head>
    
    <body>
      <form id="form1" runat="server">
      <div>
    
      <table summary="This table contains the schedule of train 
                      departures for the Red Line">
        <caption>Red Line Schedule</caption>
        <thead>
          <tr>
            <th></th>
            <th id="hdrFirstTrain" axis="train">First Train</th>
            <th id="hdrLastTrain" axis="train">Last Train</th>
          </tr>
        </thead>
        <tbody>
          <tr class="headerRow">
              <th id="hdrWeekday" axis="day" colspan="3">Weekday</th>
          </tr>
          <tr>
            <th id="hdrAlewife1" axis="location">Alewife</th>
            <td headers="hdrAlwife1 hdrWeekday hdrFirstTrain">5:24am</td>
            <td headers="hdrAlwife1 hdrWeekday hdrLastTrain">12:15am</td>
          </tr>
          <tr>
            <th id="hdrBraintree1" axis="location">Braintree</th>
            <td headers="hdrBraintree1 hdrWeekday hdrFirstTrain">
              5:15am
            </td>
            <td headers="hdrBraintree1 hdrWeekday hdrLastTrain">
              12:18am
            </td>
          </tr>  
          <tr class="headerRow">
            <th id="hdrSaturday" axis="day" colspan="3">Saturday</th>
          </tr>
          <tr>
            <th id="hdrAlewife2" axis="location">Alewife</th>
            <td headers="hdrAlewife2 hdrSaturday hdrFirstTrain">8:24am</td>
            <td headers="hdrAlewife2 hdrSaturday hdrLastTrain">11:15pm</td>
          </tr>
          <tr>
            <th id="hdrBraintree2" axis="location">Braintree</th>
            <td headers="hdrBraintree2 hdrSaturday hdrFirstTrain">
              7:16am
            </td>
            <td headers="hdrBraintree2 hdrSaturday hdrLastTrain">
              10:18pm
            </td>
          </tr>  
        </tbody>      
      </table>
    
      </div>
      </form>
    </body>
    </html>

Each heading is a th element that has a unique id value, and each table cell is a td element that has a headers attribute. Each headers attribute contains a list of the heading id values that pertain to that cell. Each th element also has an axis attribute that identifies the category that is associated with the heading.

Note

The HTML markup for a complex table that is illustrated here is one of two alternative approaches. Instead of using headers attributes you can group rows in tbody elements and identify a group header by using a scope="rowgroup" attribute. For more information, see the W3C recommendations for HTML tables on the W3C Web site, especially the section about table rendering for non-visual user agents.

caption and summary Elements

The semantically correct way to provide a title for an HTML table is to use the caption element. By default, browsers render the contents of the caption element as the title of the table. The title of the Red Line Schedule table that is shown in the previous section is a caption element that has been formatted with a red background and white foreground by using CSS.

You can also provide a longer description of the table by using the summary attribute. The summary attribute is not rendered by the browser but can be announced by a screen reader, similar to the way that the alt attribute for an image is used. The .aspx page in the previous example also defines a summary attribute for the Red Line Schedule table.

Using the Table Control

If you use the ASP.NET Web Forms Table control to create a table, you can set the caption attribute by setting the Caption property. You can create table headers by creating instances of the TableHeaderRow class and by setting the TableSection property to the TableRowSection.TableHeader enumeration value. This causes the table to render thead and tbody elements. When you create a cell by using the TableCell control, you can set the AssociatedHeaderCellID property to the ID of a table header cell. This causes the cell to render a header attribute that associates the cell with the corresponding column heading.

The following example shows how to use the Table control to create a table that is identical to the one that is shown in Figure 2. The HTML that is rendered for this table includes thead and tbody elements and header attributes.

    <asp:Table ID="Table1" runat="server">
        <asp:TableHeaderRow TableSection="TableHeader">
            <asp:TableHeaderCell ID="productheader">Product</asp:TableHeaderCell>
            <asp:TableHeaderCell ID="priceheader">Price</asp:TableHeaderCell>
        </asp:TableHeaderRow>
        <asp:TableRow>
            <asp:TableCell AssociatedHeaderCellID="productheader">Milk</asp:TableCell>
            <asp:TableCell AssociatedHeaderCellID="priceheader">$2.33</asp:TableCell>
        </asp:TableRow>
        <asp:TableRow>
            <asp:TableCell AssociatedHeaderCellID="productheader">Cereal</asp:TableCell>
            <asp:TableCell AssociatedHeaderCellID="priceheader">$5.61</asp:TableCell>
        </asp:TableRow>
    </asp:Table>

Using Web Forms Data Controls that Automatically Create Tables

ASP.NET Web Forms controls that automatically display data in HTML tables include the following:

The tables that these controls generate include accessibility features by default, such as th elements that have scope attributes. In addition, you can enable additional accessibility features by setting certain properties. For example, the GridView control supports several properties that are relevant to accessibility:

  • Caption and CaptionAlign. Use these properties to add a caption to the HTML table that is generated by the GridView control.

  • RowHeaderColumn. Use this property to indicate a column that is used for row headers. Set the property to the name of a column returned from the data source (such as CustomerID). This property only works with bound fields. It does not work with template fields.

  • HeaderRow and FooterRow. Use these properties property to generate thead, tbody, and tfoot elements. Set the TableSection property of the HeaderRow property to TableHeader in order to generate thead elements, and set the TableSection property of the FooterRow property to TableFooter to generate tfoot elements. If either thead or tfoot elements are generated, tbody elements are generated also.

  • UseAccessibleHeader. Use this property to indicate whether column headings should be rendered within th elements or td elements. By default, this property is set to true.

The GridView control does not have a predefined property that maps to the summary attribute. However, you can declare the summary attribute in markup as an expando attribute.

In the following example, a GridView control is bound to a SqlDataSource control that retrieves Customer table rows from the database. When the page is opened in a browser, the customer information is displayed in an HTML table.

<%@ Page Language="VB"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
        GridView1.HeaderRow.TableSection = TableRowSection.TableHeader
    End Sub
</script>

<html xmlns="https://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Display Customers</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="GridView1" runat="server" 
            AutoGenerateColumns="False"
            RowHeaderColumn="CustomerID"
            Caption="Customers"
            summary="This table shows a list of customers."
            DataKeyNames="CustomerID" DataSourceID="SqlDataSource1">
            <Columns>
                <asp:BoundField DataField="CustomerID" 
                    HeaderText="Customer ID" 
                    InsertVisible="False" ReadOnly="True" 
                    SortExpression="CustomerID" />
                <asp:BoundField DataField="FirstName" 
                    HeaderText="FirstName" 
                    SortExpression="FirstName" />
                <asp:BoundField DataField="MiddleName" 
                    HeaderText="MiddleName" 
                    SortExpression="MiddleName" />
                <asp:BoundField DataField="LastName" 
                    HeaderText="LastName" 
                    SortExpression="LastName" />
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:AdventureWorksLTConnectionString %>" 
            SelectCommand="SELECT CustomerID, FirstName, MiddleName, 
                LastName FROM SalesLT.Customer">
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>
<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
  protected void Page_Load(object sender, EventArgs e)
  {
      GridView1.HeaderRow.TableSection = TableRowSection.TableHeader;
  }
</script>

<html xmlns="https://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Display Customers</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="GridView1" runat="server" 
            AutoGenerateColumns="False"
            RowHeaderColumn="CustomerID"
            Caption="Customers"
            summary="This table shows a list of customers."
            DataKeyNames="CustomerID" DataSourceID="SqlDataSource1">
            <Columns>
                <asp:BoundField DataField="CustomerID" 
                    HeaderText="Customer ID" 
                    InsertVisible="False" ReadOnly="True" 
                    SortExpression="CustomerID" />
                <asp:BoundField DataField="FirstName" 
                    HeaderText="FirstName" 
                    SortExpression="FirstName" />
                <asp:BoundField DataField="MiddleName" 
                    HeaderText="MiddleName" 
                    SortExpression="MiddleName" />
                <asp:BoundField DataField="LastName" 
                    HeaderText="LastName" 
                    SortExpression="LastName" />
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:AdventureWorksLTConnectionString %>" 
            SelectCommand="SELECT CustomerID, FirstName, MiddleName, 
                LastName FROM SalesLT.Customer">
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

GridView table showing accessibility features

The source of the rendered page shows the following features in the generated HTML:

  • th elements that have scope attributes for the heading row are generated automatically.

  • thead and tbody elements are generated because the HeaderRow property is set in the Page_Load method.

  • The caption element is generated because the Caption property is set in markup.

  • th elements with scope attributes are generated for the Customer ID column because the RowHeaderColumn property is set in markup.

  • The summary attribute is generated because a summary expando attribute is set in markup.

Using ASP.NET Dynamic Data

ASP.NET Dynamic Data uses GridView controls to display data and FormView controls to display forms in which you can update the table data.

The tables displayed by GridView controls do not have caption attributes, thead elements, or tbody elements. However, you can add these features by creating a page for each data table in the CustomPages folder and customizing the markup and page code for the GridView control.

The forms that are displayed by the FormView controls use HTML tables for page layout. However, they conform to accessibility guidelines, because they make sense when linearized, and because field labels are rendered by using label elements that have for attributes.

For more information about ASP.NET Dynamic Data, see ASP.NET Dynamic Data Content Map.

Using Web Forms Data Controls that Require Templates

For some Web Forms data controls, you can use templates to explicitly define the HTML elements that the data will be displayed in. The following ASP.NET controls require you to use templates:

These controls do not render any markup automatically. You define the header, body, and footer templates for the control and specify the markup for those templates. If you want these controls to render an HTML table, you must include the appropriate markup to meet accessibility standards.

The flexibility of templates makes it possible to generate complex tables. For example, imagine that you want to display a list of customers and under each customer you want to display a list of that customer's addresses. (A customer can have multiple addresses.) In other words, you want to create a single-page master/detail form. In that case, you must include the headers attribute for each table cell.

The ASP.NET page in the following example creates a single-page master/detail form. This example illustrates how you can nest one ListView control in a second ListView control in order to generate a complex table that follows accessibility guidelines.

<%@ Page Language="VB" %>

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    Private AddressesTable As New DataTable()

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
        Dim AddressesAdapter As New SqlDataAdapter(
            "SELECT SalesLT.CustomerAddress.CustomerID, " &
            "SalesLT.CustomerAddress.AddressID, " &
            "SalesLT.Address.AddressLine1, " &
            "SalesLT.Address.City, SalesLT.Address.StateProvince, " &
            "SalesLT.Address.PostalCode " &
            "FROM SalesLT.CustomerAddress " &
            "INNER JOIN SalesLT.Address ON " &
            "SalesLT.CustomerAddress.AddressID = SalesLT.Address.AddressID",
            "Data Source=.\SQLEXPRESS;" &
            "AttachDbFilename=|DataDirectory|\AdventureWorksLT_Data.mdf;" &
            "Integrated Security=True;User Instance=True")
        AddressesAdapter.Fill(AddressesTable)
    End Sub

    Protected Function GetAddresses(ByVal customerID As Object) As DataView
        Dim addressesView As DataView = AddressesTable.DefaultView
        addressesView.RowFilter = "CustomerID=" & customerID.ToString()
        Return (addressesView)
    End Function

    Private Function GetCustomerHeaderID(ByVal item As ListViewItem) As String
        Return ("hdrCustomer" & item.DataItemIndex.ToString())
    End Function

    Private Function GetAddressHeaderID(ByVal item As ListViewItem) As String
        Return ("hdrAddress" & 
                CType(item.DataItem, DataRowView)("AddressID").ToString())
    End Function

    Protected Function GetColumnHeaderIDs(ByVal item As ListViewDataItem,
        ByVal columnHeader As String) As String

        Dim customerHeaderID As String =
            GetCustomerHeaderID(
                CType(item.NamingContainer.NamingContainer, ListViewItem))

        Dim addressHeaderID As String = GetAddressHeaderID(item)

        Return (String.Format("{0} {1} {2}",
                              customerHeaderID,
                              addressHeaderID,
                              columnHeader))
    End Function

    Protected Sub CustomersListView_ItemDataBound(ByVal sender As Object,
        ByVal e As ListViewItemEventArgs)

        Dim addressesListView As New ListView()
        addressesListView = CType(e.Item.FindControl("AddressesListView"), ListView)

        Dim drv As DataRowView = CType(e.Item.DataItem, DataRowView)

        addressesListView.DataSource = GetAddresses(drv("CustomerID"))
        addressesListView.DataBind()
    End Sub
</script>

<html xmlns="https://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Customers and Addresses</title>
    <style type="text/css">
        .customerRow
        {
            background-color: yellow;
        }
        th
        {
            text-align: left;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:SqlDataSource ID="CustomersSqlDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:AdventureWorksLTConnectionString %>"
            SelectCommand="SELECT CustomerID, 
      FirstName, MiddleName, LastName FROM SalesLT.Customer" />
        <asp:ListView ID="CustomersListView" runat="server" DataKeyNames="CustomerID" DataSourceID="CustomersSqlDataSource"
            OnItemDataBound="CustomersListView_ItemDataBound">
            <LayoutTemplate>
                <table summary="A list of customers with one or more addresses for each customer.">
                    <caption>
                        Customers and Addresses</caption>
                    <thead>
                        <tr>
                            <th id="hdrID">
                                ID
                            </th>
                            <th id="hdrStreet">
                                Street
                            </th>
                            <th id="hdrCity">
                                City
                            </th>
                            <th id="hdrState">
                                State
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr id="itemPlaceholder" runat="server">
                        </tr>
                    </tbody>
                </table>

            </LayoutTemplate>
            <ItemTemplate>
                <tr class="customerRow">
                    <th colspan="4" id='<%# GetCustomerHeaderID(Container) %>'>
                        <%# Eval("FirstName") %>
                        <%# Eval("MiddleName") %>
                        <%# Eval("LastName") %>
                    </th>
                </tr>
                <asp:ListView ID="AddressesListView" runat="server">
                    <LayoutTemplate>
                        <tr id="itemPlaceHolder" runat="server">
                        </tr>
                    </LayoutTemplate>
                    <ItemTemplate>
                        <tr>
                            <th id='<%# GetAddressHeaderID(Container) %>'>
                                <%# Eval("AddressID") %>
                            </th>
                            <td headers='<%# GetColumnHeaderIDs(Container, "hdrStreet") %>'>
                                <%# Eval("AddressLine1") %>
                            </td>
                            <td headers='<%# GetColumnHeaderIDs(Container, "hdrCity") %>'>
                                <%# Eval("City") %>
                            </td>
                            <td headers='<%# GetColumnHeaderIDs(Container, "hdrState") %>'>
                                <%# Eval("StateProvince") %>
                            </td>
                        </tr>
                    </ItemTemplate>
                </asp:ListView>
            </ItemTemplate>
        </asp:ListView>
    </div>
    </form>
</body>
</html>
<%@ Page Language="C#" %>

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  private DataTable AddressesTable = new DataTable();

  protected void Page_Load(object sender, EventArgs e)
  {
    SqlDataAdapter AddressesAdapter = new SqlDataAdapter(
        "SELECT SalesLT.CustomerAddress.CustomerID, " +
        "SalesLT.CustomerAddress.AddressID, " +
        "SalesLT.Address.AddressLine1, " +
        "SalesLT.Address.City, SalesLT.Address.StateProvince, " +
        "SalesLT.Address.PostalCode FROM SalesLT.CustomerAddress " +
        "INNER JOIN SalesLT.Address ON " +
        "SalesLT.CustomerAddress.AddressID = SalesLT.Address.AddressID",
        @"Data Source=.\SQLEXPRESS;" +
        @"AttachDbFilename=|DataDirectory|\AdventureWorksLT_Data.mdf;" +
        @"Integrated Security=True;User Instance=True");
    AddressesAdapter.Fill(AddressesTable);
  }

  protected DataView GetAddresses(object customerID)
  {
    DataView view = AddressesTable.DefaultView;
    view.RowFilter = "CustomerID=" + customerID.ToString();
    return view;
  }

  private string GetCustomerHeaderID(ListViewItem item)
  {
    return "hdrCustomer" + item.DataItemIndex.ToString();
  }

  private string GetAddressHeaderID(ListViewItem item)
  {
    return "hdrAddress" +
        ((DataRowView)item.DataItem)["AddressID"].ToString();
  }

  protected string GetColumnHeaderIDs
      (ListViewDataItem item, string columnHeader)
  {
    string customerHeaderID =
        GetCustomerHeaderID
            ((ListViewItem)item.NamingContainer.NamingContainer);

    string addressHeaderID = GetAddressHeaderID(item);

    return string.Format("{0} {1} {2}",
        customerHeaderID, addressHeaderID, columnHeader);
  }

  protected void CustomersListView_ItemDataBound
      (object sender, ListViewItemEventArgs e)
  {
    ListView addressesListView = new ListView();
    addressesListView = e.Item.FindControl("AddressesListView")
        as ListView;
    DataRowView drv = e.Item.DataItem as DataRowView;
    addressesListView.DataSource = GetAddresses(drv["CustomerID"]);
    addressesListView.DataBind();
  }
</script>

<html xmlns="https://www.w3.org/1999/xhtml">

<head id="Head1" runat="server">
  <title>Customers and Addresses</title>
  <style type="text/css">
    .customerRow { background-color: yellow; }
    th { text-align: left; }
  </style>
</head>

<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="CustomersSqlDataSource" runat="server" 
    ConnectionString="<%$ ConnectionStrings:AdventureWorksLTConnectionString %>"
    SelectCommand="SELECT CustomerID, 
      FirstName, MiddleName, LastName FROM SalesLT.Customer" />
    <asp:ListView ID="CustomersListView" runat="server" 
      DataKeyNames="CustomerID" DataSourceID="CustomersSqlDataSource"
      OnItemDataBound="CustomersListView_ItemDataBound">
      <LayoutTemplate>
        <table summary="A list of customers with one or more addresses for each customer.">
          <caption>Customers and Addresses</caption>
          <thead>
            <tr>
              <th id="hdrID">ID</th>
              <th id="hdrStreet">Street</th>
              <th id="hdrCity">City</th>
              <th id="hdrState">State</th>
            </tr>
          </thead>
          <tbody>
            <tr id="itemPlaceholder" runat="server"></tr>
          </tbody>
        </table>

      </LayoutTemplate>
      <ItemTemplate>
        <tr class="customerRow">
          <th colspan="4" id='<%# GetCustomerHeaderID(Container) %>'>
            <%# Eval("FirstName") %>
            <%# Eval("MiddleName") %>
            <%# Eval("LastName") %>
          </th>
        </tr>
        <asp:ListView ID="AddressesListView" runat="server">
          <LayoutTemplate>
            <tr id="itemPlaceHolder" runat="server"></tr>
          </LayoutTemplate>
          <ItemTemplate>
            <tr>
              <th id='<%# GetAddressHeaderID(Container) %>'>
                <%# Eval("AddressID") %>
              </th>
              <td headers='<%# GetColumnHeaderIDs(Container, "hdrStreet") %>'>
                <%# Eval("AddressLine1") %>
              </td>
              <td headers='<%# GetColumnHeaderIDs(Container, "hdrCity") %>'>
                <%# Eval("City") %>
              </td>
              <td headers='<%# GetColumnHeaderIDs(Container, "hdrState") %>'>
                <%# Eval("StateProvince") %>
              </td>
            </tr>
          </ItemTemplate>
        </asp:ListView>
      </ItemTemplate>
    </asp:ListView>
  </div>
  </form>
</body>
</html>

Complex table using nested ListView controls

In this example, the outer ListView control lists the customer names, and the inner ListView control lists the matching addresses . The GetCustomerHeaderID and GetAddressHeaderID functions generate id values for the customer name and address ID headers. The GetColumnHeaderIDs method generates headers attribute values for table cells.

The ASP.NET page that is shown in the preceding example generates an HTML table that looks like the following:

    <table>
      <thead>
        <tr>
          <th id="hdrID">ID</th>
          <th id="hdrStreet">Street</th>
          <th id="hdrCity">City</th>
          <th id="hdrState">State</th>
        </tr>
      </thead>
      <tbody>
        <tr class="customerRow">
          <th colspan="4" id='hdrCustomer0'>Orlando N. Gee</th>
        </tr>
        <tr>
          <th id='hdrAddress832'>832</th>
          <td headers='hdrCustomer0 hdrAddress832 hdrStreet'>
             2251 Elliot Avenue
          </td>
          <td headers='hdrCustomer0 hdrAddress832 hdrCity'>
             Seattle
          </td>
          <td headers='hdrCustomer0 hdrAddress832 hdrState'>
             Washington
          </td>
        </tr>
        <tr class="customerRow">
          <th colspan="4" id='hdrCustomer1'>Keith Harris</th>
        </tr>
        <tr>
          <th id='hdrAddress833'>833</th>
          <td headers='hdrCustomer1 hdrAddress833 hdrStreet'>
             3207 S Grady Way
          </td>
          <td headers='hdrCustomer1 hdrAddress833 hdrCity'>
             Renton
          </td>
          <td headers='hdrCustomer1 hdrAddress833 hdrState'>
             Washington
          </td>
        </tr>
        <tr>
          <th id='hdrAddress297'>297</th>
          <td headers='hdrCustomer1 hdrAddress297 hdrStreet'>
             7943 Walnut Ave
          </td>
          <td headers='hdrCustomer1 hdrAddress297 hdrCity'>
             Renton
          </td>
          <td headers='hdrCustomer1 hdrAddress297 hdrState'>
             Washington
          </td>
        </tr>
        [remaining rows of the table]
      </tbody>
    <table>

Notice that each td tag contains an appropriate headers attribute.

Associating Input Fields With Labels

If you access a Web page form through a screen reader, it might be difficult to associate form fields with their corresponding labels. For example, imagine that a Web page contains the following form that displays input fields for a person's first name and last name: First Name, Last Name: The text and elements all display on the same line, and it would be obvious to a person who is viewing the screen which input box matches which field label. But because the "Last Name" text is next to the txtFirstName text box, it might be difficult for the user of a screen reader to associate each label with its matching form field. In HTML 4.0 you can address this problem by using the label element to explicitly associate a form field label with a form field. The following example shows how the previous form should be written by using a label element: , : Notice that the input fields include an id attribute. The value of the for attribute must be an input field's id attribute value, not its name attribute value.

Using ASP.NET Web Forms Label Controls

Normally, the ASP.NET Web Forms Label control generates a span element. However, if you set the control's AssociatedControlID property, it renders a label element. The following example shows how you can generate an accessible form with ASP.NET Label and TextBox controls. <asp:Label AssociatedControlID="txtFirstName" runat="server">First Name</asp:Label>, <asp:Label AssociatedControlID="txtLastName" runat="server">Last Name</asp:Label>: <asp:TextBox ID="txtFirstName" runat="server" /> <asp:TextBox ID="txtLastName" runat="server" /> When you provide a label for an ASP.NET control, you should use the ASP.NET Label control instead of the HTML label element. This is because by default, ASP.NET renders an HTML id attribute value that differs from the ID that you assign when you declare an input control such as a TextBox control. Therefore, if you use a label element and put the declared ID of the TextBox control in the label element's for attribute, the rendered id and corresponding for attributes might not match. When you use the ASP.NET Label control, ASP.NET automatically makes sure that the rendered id and corresponding for attributes match.

Note

An alternative is to set the ClientIDMode property of the TextBox control to Static. This causes the id attribute that is rendered in HTML to be the same as the declared ID. For more information, see ASP.NET Web Server Control Identification.

Using ASP.NET Web Forms CheckBox and RadioButton Controls

The following ASP.NET Web Forms controls automatically render label elements:

When you add one of these controls in a page, make sure that you use the Text property instead of including text between opening and closing tags in markup. For example, you should not do the following:

<asp:CheckBox runat="Server">Include Gift Wrap</asp:CheckBox>

Instead, create markup like the following example:

<asp:CheckBox text="Include Gift Wrap" runat="Server" />

A label element that has a for attribute is generated for one of these controls only when you set its Text property explicitly.

Guideline 2.1 - Making Functionality Keyboard Accessible

ASP.NET controls automatically make mouse-based functionality available through the keyboard. This section explains how you can improve the experience of people who use the keyboard in the following ways:

  • Providing access keys.

  • Specifying logical tab order.

Providing Access Keys

One way to make a site more usable for people who cannot use a mouse is to include accesskey attributes for HTML elements that can receive focus, such as input elements or links. However, implementing access keys (keyboard shortcuts) without careful planning can create unintended usability problems, such as the following:

  • Not all browsers support access keys.

  • Among browsers that do support access keys, the access keys that are assigned might not always be obvious to a user. Unless you make sure that access keys are identified, some users might never find out that they are available.

  • An access key that is defined by a Web page could mask one that is defined for a browser. The user might be accustomed to using the browser's access key and could find its reassignment to a form field or link to be a nuisance.

  • A typical pattern for access keys is to use a letter from the label of the element they are linked to, usually the first letter. But if you are designing Web pages that will be rendered in multiple languages, those letters might be different in each language. You can minimize this problem by using only number keys as access keys, but this limits the number of access keys that can be assigned.

There are so many limitations with access keys that some accessibility advocates believe they generally create more problems than they solve. For more information, search for "access keys" on the WebAIM site.

If you do decide to define access keys in a Web Forms page, you can use the AccessKey property of Web Forms server controls in order to set the accesskey attribute for ASP.NET controls. When you associate a Label control with an input control such as a TextBox control, you set the AccessKey property on the Label control. For more information, see How to: Use Label Web Server Controls as Captions.

The following example shows the use of accesskey attributes:

    <asp:Label ID="Label1" AssociatedControlID="txtFirstName" 
        AccessKey="f" runat="server">
        <u>F</u>irst Name
    </asp:Label>
    <asp:TextBox ID="txtFirstName" runat="server" />
    <br />
    <asp:Label ID="Label2" AssociatedControlID="txtLastName" 
        AccessKey="l" runat="server">
        <u>L</u>ast Name
    </asp:Label>
    <asp:TextBox ID="txtLastName" runat="server" />

Input form showing use of access keys

When Internet Explorer renders this markup, the first letter of both the First Name label and the Last Name label is underlined. Underlining the letter provides the user of the Web site with a visual indication of the access keys. This is the standard way to mark access keys in Microsoft Windows applications. If you run this page in Internet Explorer you will also notice that you can no longer open the browser's File menu by using the F access key because the page's F access key takes precedence.

Specifying Logical Tab Order

Another way to use the keyboard to shift focus to an input element or link is to use the TAB key. Input forms should be designed so that the default tab order provides a logical path through the input fields and hyperlinks.

The default tab order does not work well for some page designs. For example, you might have a page that has three columns of form fields. The logical order might be to tab through the fields in each column, and at the end of a column to move to the next column. But the browser's default tab order might be to go horizontally across a column first, and then down a row. This would make it difficult for a person who uses a keyboard to fill out the fields in the expected order.

In such situations, the best solution is to redesign the page so that the default tab order is also the logical tab order. However, if that is not practical, the tab order can be controlled by the HTML tabindex attribute. This attribute can be placed on elements that are designed for user input and on hyperlinks. Specifying tab order in this way can help sighted users who rely on the keyboard for form navigation, but at the expense of making the page less accessible to screen reader users. Screen reader users can be disoriented by altered tab orders because the reading order does not match the tab order.

In ASP.NET Web Forms controls you can use the TabIndex property to set the HTML tabindex attribute of the generated HTML elements.

The following example adds tabindex attributes to the accesskey example that was shown earlier in this topic. These attributes specify that the two input fields shown should be the first and second elements in the tab order on the page:

    <asp:Label ID="Label1" AssociatedControlID="txtFirstName" 
        TabIndex="1"
        AccessKey="f" runat="server">
        <u>F</u>irst Name
    </asp:Label>
    <asp:TextBox ID="txtFirstName" runat="server" />
    <br />
    <asp:Label ID="Label2" AssociatedControlID="txtLastName" 
        TabIndex="2"
        AccessKey="l" runat="server">
        <u>L</u>ast Name
    </asp:Label>
    <asp:TextBox ID="txtLastName" runat="server" />

The ARIA standards allow the tabindex attribute on any HTML element because any element may be defined as an RIA widget (for information about ARIA, see Accessibility for Rich Internet Applications (ARIA) earlier in this topic). ARIA also specifies that you can set tabindex to the value "-1" in order to prevent the browser from setting a tab stop on an element that would ordinarily get one. However, not all browsers support these ARIA features, and markup that uses them might fail the W3C markup validation service.

Note

You can set the initial focus when a page is loaded by using methods such as the Page.SetFocus method or by setting the DefaultFocus property for a form.

Guideline 2.4 - Making Web Sites Navigable

Navigating a Web site presents special challenges for users of screen reader software. This section explains the following tasks:

  • Providing a way to skip large blocks of links.

  • Providing meaningful link text.

  • Dividing long forms into sections.

No matter how large a menu is, a person who is viewing a Web page can quickly pass over the menu to the content of a page. But for the person who is using a screen reader, listening to all of the menu options being announced can waste much time.

ASP.NET Web Forms controls automatically provide skip-navigation links to deal with this problem. A skip-navigation link is not visible on the page but can be announced by a screen reader to give the user an option to skip over a whole block of links. The following ASP.NET Web Forms controls automatically generate skip-navigation links:

The following example shows an ASP.NET Web Forms page that contains a Menu control that is used to display a list of links to other pages in the Web site.

    <%@ Page %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html >
    <head id="Head1" runat="server">
      <title>Skip Navigation</title>
    </head>
    <body>
      <form id="form1" runat="server">
      <div>
    
      <asp:Menu 
        id="Menu1"
        Runat="server">
        <Items>
          <asp:MenuItem Text="Home" NavigateUrl="Home.aspx" />
          <asp:MenuItem Text="Products" NavigateUrl="Products.aspx" />
          <asp:MenuItem Text="Services" NavigateUrl="Services.aspx" />
          <asp:MenuItem Text="About" NavigateUrl="About.aspx" />
        </Items>
      </asp:Menu>
    
      <hr />
    
      Here is the main content of the page...
    
      </div>
      </form>
    </body>
    </html>

A simple menu, to show skip-navigation link

The page control renders the following HTML, which includes a link at the top of the menu:

<a href="#Menu1_SkipLink"><img alt="Skip Navigation Links"

  src="/WebResource.axd?d=ChXz41GuDxNm-7TcWyCl_w2&amp;t=632495684475122400"

  width="0" height="0" style="border-width:0px;" />

The page does not display the skip-navigation link. The image in the link has zero width and zero height. However, if you access this page with a screen reader, the alternate text that is associated with the image is read. A person who is blind can skip all the navigation links and move directly to the main content area of the Web.

Each control that generates skip-navigation links has a SkipLinkText property that determines the text of the skip navigation link. By default this property is set to "Skip Navigation Links". If you set this property to an empty string, the control does not render a skip-navigation link.

For more information, search for "skip-navigation links" on the WebAIM site.

Providing complete and accurate text for hyperlinks is an important navigational aid for all users of a Web page and is an important search engine optimization technique. However, it is also important for users of screen readers. A screen reader can help navigate through a page by announcing all the hyperlinks in sequence. If hyperlinks depend on the text around them to make sense - such as links whose text is only Click here or Read more -- they will not make sense when a person listens to all the links on a page.

For example, the two paragraphs in the following illustration accomplish the same purpose for a person who can see the screen. However, but the paragraph on the right is more useful to a person who uses a screen reader.

Example showing alternative to "read more" links

The ASP.NET Web Forms markup that generates these pages is shown in the following example:

    <%@ Page %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <script runat="server">
    
    </script>
    <html xmlns="https://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Meaningful Link Text</title>
        <style type="text/css">
            #leftColumn
            {
                float: left;
                width: 45%;
                border: 1px solid black;
                padding: 10px;
            }
    
            #rightColumn
            {
                float: right;
                width: 45%;
                border: 1px solid black;
                padding: 10px;
            }
        </style>
    </head>
    <body>
        <form id="form1" runat="server">
        <div id="leftColumn">
            <strong>Providing Skip-Navigation Links</strong><br />
            With a simple modification to a navigation bar, 
            you can dramatically improve the
            accessibility of your Web pages.
            <asp:HyperLink ID="HyperLink1" runat="server" 
                NavigateUrl="~/Default.aspx">
                Read more.
            </asp:HyperLink>
            <br />
            <br />
            <strong>Providing Meaningful Link Text</strong><br />
            Providing complete and accurate text for hyperlinks 
            is an important navigational
            aid for all users of a Web page and is an important 
            search engine optimization technique.
            However, it is also important for users of screen readers.
            <asp:HyperLink ID="HyperLink3" runat="server" 
                NavigateUrl="~/Default.aspx">
                Read more.
            </asp:HyperLink>
        </div>
        <div id="rightColumn">
            <asp:HyperLink ID="HyperLink2" runat="server" 
                NavigateUrl="~/Default.aspx">
                <strong>Providing Skip-Navigation Links</strong>
            </asp:HyperLink><br />
            With a simple modification to a navigation bar, 
            you can dramatically improve the
            accessibility of your Web pages.
            <br />
            <br />
            <asp:HyperLink ID="HyperLink4" runat="server" 
                NavigateUrl="~/Default.aspx">
                <strong>Providing Meaningful Link Text</strong>
            </asp:HyperLink><br />
            Providing complete and accurate text for hyperlinks 
            is an important navigational
            aid for all users of a Web page and is an important 
            search engine optimization technique.
            However, it is also important for users of screen readers.
        </div>
        </form>
    </body>
    </html>

Dividing Long Forms into Sections

If a page contains a long form, it can help to break the form into sections, especially for users who use screen readers. You can divide a single form into sections by using the fieldset element, as shown in the following example:

    <form id="form1" runat="server">
    <div>
        <div>
            <fieldset>
                <legend>Contact Information</legend>
                <br />... form fields ...<br /><br />
            </fieldset>
            <fieldset>
                <legend>Payment Information</legend>
                <br />... form fields ...<br /><br />
            </fieldset>
        </div>
    </form>

Form divided into sections using fieldset element

This form is divided into two subforms by using fieldset elements. The legend element is used to label the purpose of the subforms. Most browsers display a border to visually separate these areas. However, the purpose of the fieldset element is to create subforms, not to provide a border. If you do not like the default visual appearance of the fieldset element, you can modify the appearance through a style sheet rule, or you can hide the element by using the CSS display or visibility attributes.

In ASP.NET Web Forms pages, you can use the Panel control to create subforms. If you set the GroupingText property of the Panel control to a string, the control renders a div element that contains a fieldset element for the contents and a legend element that has the string that you used in the GroupingText property.

Guideline 3.1 - Making Web Content Readable and Understandable

You can identify a page's human language by setting the lang attribute on the html element. If you use master pages, you can set this attribute in a master page for all the content pages for that master page. The following example shows an html tag for a page that is written in U.S. English:

<html xmlns="https://www.w3.org/1999/xhtml" lang="en-us">

If a page omits the lang attribute, the screen reader will typically assume that the page is in the language that the user has set for the browser. Therefore, setting this attribute is most important for Web sites that have mixed language content (some pages in one language, some pages in a different language) or for Web sites that will be used by people whose native language is different from the language of the Web site.

When you create an ASP.NET Web Forms page by using Visual Studio, the templates that create new Web pages or master pages create the html element without a lang element. It is up to you to add the lang element. When you do that, IntelliSense provides a list of the available language and culture codes.

Guideline 3.2 - Responding Predictably to User Input

A Web page that behaves in unpredictable ways might be difficult for anyone to use, but especially for readers who use a screen reader. The basic principle is that if any user action (such as moving focus or clicking something) causes significant changes to the Web page, the user should be informed about what to expect.

For example, in a questionnaire, the answer to one question might dynamically display additional questions. Some Web pages handle this by dynamically showing or hiding a section of the form when the user clicks a drop-down list item. If an ASP.NET Web Forms page implements this behavior by using the AutoPostBack property, the entire page might be refreshed. In that case, a person who is using a graphical browser might see the screen flicker as it adds an additional section. However, a person who uses a screen reader might have to listen to the entire form again. In either case, the user experience will be better if the label or description for the drop-down list makes clear what will happen when it is clicked. You can make page updates like these less intrusive by using client script or Ajax so that the browser updates only a portion of the page. However, the page should still make clear what behavior the user can expect.

Guideline 3.3 - Helping Users Avoid and Correct Mistakes

A typical way to help users know fields are required is to display an asterisk (*) next to required fields or by rendering their labels in a bold font. If you use this convention, you should explain on the same page what this convention means. Avoid marking required fields or flagging fields in which an error has been made only by changing a font color. For some people certain colors, such as red and black, might be indistinguishable.

To help users correct mistakes, provide text that explains what kind of input is expected. In an ASP.NET Web Forms page, if you use ASP.NET validator controls, always specify meaningful error messages in the Text and ErrorMessage properties.

Security noteSecurity Note

When you create error messages, make sure that you do not provide information that malicious users could use in a Web attack. For more information, see Basic Security Practices for Web Applications.

Guideline 4.1 - Making Sure that Web Pages Are Robust

Accessible Web pages must work correctly in as many browsers and browser versions as possible. WCAG 2.0 success criterion 4.1.1 states that the page must be composed of valid HTML that complies with the following rules:

  • Elements have complete start and end tags.

  • Elements are nested according to their specifications. For example, it is acceptable to have a structure like <h1><h2></h2></h1>, but not <h2></h2> by itself without <h1> above it. Similarly, it is not acceptable to create a hierarchy that is out of order, such as <h1><h3><h2><h2/></h3></h1> or to specify mismatched end tags, such as <h1><h2></h1></h2>.

  • Elements do not contain duplicate attributes.

  • Element IDs are unique.

By default, ASP.NET Web Forms controls generate HTML that complies with these rules. However, you must be careful not to create invalid HTML when you use control templates to specify custom HTML. Similarly, when you use the Static setting of the ClientIDMode property you should avoid creating duplicate id attributes.

Note

In some cases ASP.NET Web Forms controls generate self-closing tags instead of separate start and end tags (for example, <input type="text" /> instead of <input type="text"></input>). HTML validators that apply HTML 4.01 rules may mark such elements as improperly closed. However, self-closing tags are supported by all major browsers, are valid in the XHTML and HTML 5 specifications, and are handled correctly by assistive technology software

Accessible Web pages must also make sure that all the user interface components in a page can work effectively with assistive technology. WCAG 2.0 success criterion 4.1.2 states that assistive technology should be able to determine the type, properties, and state of any controls on a page. By definition, all standard HTML elements are accessible. But client-side controls that repurpose HTML elements typically do not meet this standard. This is the problem that ARIA is intended to address. As noted earlier in this topic, ARIA is a work in progress, and this document does not provide detailed guidance about how to implement ARIA in ASP.NET Web Forms pages.

Validating Web Pages for Accessibility

Visual Studio (not Visual Web Developer Express) includes an accessibility checker tool. To run the tool, select Check Accessibility in the Tools menu, which displays the following Accessibility Validation dialog box:

Accessibility Validation dialog box

For Web site projects (not for Web application projects), you can also specify that the accessibility checker should be run every time that you build the Web site. The Build tab of the Property Pages dialog box includes check boxes that you can use to make accessibility checking part of the build process, as shown in the following illustration:

Property Pages dialog box

The accessibility checker gives you options for validating a Web site against WCAG 1.0 Priority 1 checkpoints, WCAG 1.0 Priority 2 checkpoints, or Section 508 guidelines.

You can view the results of validating a Web site by opening the Error List window (select the menu option View, Other Windows, Error List).

The accessibility checker also lets you create a manual checklist of accessibility issues. If you select this option, a standard list of possible accessibility issues is displayed in the Error List window whenever you validate a Web site for accessibility. This checklist contains issues that cannot be automatically validated by the accessibility checker.

The accessibility checker is a useful tool to help you find issues that you might otherwise overlook. However, you should be aware of its limitations. The absence of error messages does not mean a Web site follows accessibility guidelines accurately, and error messages that it reports might not apply to your Web site.

The accessibility checker has the following limitations:

  • Many accessibility guidelines require human judgment and cannot be verified automatically. For example, an automated tool can verify that an alt attribute is present on an img tag. However, it cannot verify that the text in the alt attribute is appropriate for the image.

  • The accessibility checker validates against WCAG 1.0 guidelines, not WCAG 2.0. WCAG 1.0 is more than ten years old and many accessibility guidelines have changed. Some sites might be legally required to follow older guidelines. If that is not the case for you, you must be aware of the newer guidelines and be able to determine which accessibility checker messages pertain to the older guidelines.

  • The accessibility checker validates only HTML. It does not validate ASP.NET markup. For example, it will report a missing alt attribute on an img element. But it will not report a missing AlternateText property on an Image control.

See Also

Tasks

Walkthrough: Accessibility Guidelines for Using Image Controls, Menu Controls, and AutoPostBack

Walkthrough: Accessibility Guidelines for Using the GridView Control

Walkthrough: Accessibility Guidelines for Using the ListView Control

Walkthrough: Accessibility Guidelines for Using Nested ListView Controls

Walkthrough: Accessibility Guidelines for Using Label Controls, Validator Controls, and Panel Controls

Concepts

ASP.NET Controls and Accessibility

XHTML Standards in Visual Studio and ASP.NET

Other Resources

ASP.NET Web Forms Pages

The Microsoft Accessibility Web Site

The MSDN Accessibility Web Site