[Note:  This document is a preliminary version of a white paper that was released with the Beta 2 version of Visual Studio 2010 and ASP.NET 4.  The revision with changes resulting from the comments and with information about Beta 2 enhancements to ASP.NET 4 is now available here. Thanks to everyone who sent comments about this version.]

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 some techniques for configuring ASP.NET Web server controls 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 generically to both markup languages.

For detailed information about how the HTML that is rendered for specific ASP.NET server controls conforms to accessibility standards, see ASP.NET Controls and Accessibility.

Scenarios

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

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

Other accessibility standards are intended to help ensure understandability and ease of use for people who use standard browsers, with or without the aid of assistive technology. Complying with these standards benefits all users, not only those who have disabilities.

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

·         In the United States, any Web site 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 any 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 pay a penalty 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 federal 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 actively 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 well-known 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 (for the complete specification, see http://www.w3.org/TR/WCAG10). That version has been superseded by WCAG 2.0, which was published on December 11, 2008 (for the complete specification, see http://www.w3.org/TR/WCAG20).

Many governments and organizations also publish guidelines for how to create accessible applications, but almost all 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 may be used by Web developers 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, while the guidelines and success criteria remain stable.

WCAG Design Principles

The four design principles of WCAG state that Web content must be:

1.       Perceivable.

Information and user interface components must be presentable to users in ways they can perceive. For example, a person who is blind cannot perceive an image on a screen but can perceive words spoken by screen reader software. By providing a textual description of the image, a Web developer makes 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, you can make menu links in a navigation bar have the same order and appearance throughout a Web site in order to sure that a person can easily learn to navigate the site.

4.       Robust.

A Web page's content must be compatible with a wide variety of browsers and assistive technology. And 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, Web developers should avoid relying on that because future versions of the browser, or other browsers, may not behave in the same way.

WCAG Guidelines

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

·         Provide text alternatives for non-text content such as images. (1.1)

·         Provide text alternatives 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.

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 automatically navigating to a different page, show a pop-up that gives the user an opportunity to delay the navigation.

·         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 ensure 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 accessibility attained by a Web site:

·         Level A.

According to the W3C, Web developers "must" follow the guidelines that enable their sites 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).

Web developers "should" also follow 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).

Web developers "may" decide to follow these extremely rigorous guidelines. This level is attained 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 attain 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 attain 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 as well as the Level AAA criteria. For more information, see http://www.w3.org/WAI/WCAG2-Conformance.html on the W3C site.

 Accessibility for Rich Internet Applications (ARIA)

The W3C is creating a new standard that is intended to ensure that assistive technology can work well with Rich Internet Applications (RIAs). RIA in this context refers to Web pages that include client-side controls (named widgets in the W3C documents). These widgets typically consist of HTML elements and JavaScript or AJAX code. The client-side markup generated by some ASP.NET server controls would be considered widgets, as would the controls in the AJAX Control Toolkit. 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 ARIA standard consists primarily of new attributes that can be placed on HTML elements. Assistive technology software can use these attributes to identify the function, properties, and state of widgets. The standards also includes guidelines that specify how widgets should respond to keyboard input, in order to ensure that they are made keyboard-accessible in a consistent manner.

Without the new ARIA attributes, widgets cannot be recognized or manipulated programmatically by assistive technology. For example, if a calendar widget is composed of an HTML table, a screen reader will read all of the table cells one by one in a long sequence that is not very helpful. If the HTML table can be programmatically identified as a calendar control, the screen reader could announce it in a way that would make more sense to the listener.

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">

Property and state attributes provide other information about the widget:

<button role=slider

  aria-valuemin="0" aria-valuemax="100"

  aria-valuenow="0">

Some ARIA attributes are not specifically for widgets. For example, landmark roles indicate the logical function of regions of a Web page:

<div role="banner">

<div role="navigation">

<div role="main">

<div role="complementary">

A page marked with landmark roles allows a person who uses a screen reader to quickly go to the desired part of the page without having to listen to other parts.

Because ARIA is relatively new and is still being developed, there is limited support for ARIA attributes in Web browsers and assistive technology software. Also, 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 simply ignored by browsers that do not support them.

ASP.NET controls do not automatically support ARIA yet. However, you can add ARIA attributes manually by writing custom JavaScript or by using expando attributes. When you declare an ASP.NET control in markup, the attributes you can specify are not limited to the properties defined for the control's class. You can include any other attributes you please, and the attribute name and value will be carried through to the rendered HTML exactly as you define them. Attributes defined this way are called expando attributes.

This topic does not provide guidance for implementing ARIA guidelines. For more information about ARIA, see the ARIA Overview on the W3C Web site.

 Client-Side Script and Accessibility

WCAG 2.0 contains nothing that corresponds to the following WCAG 1.0 priority 1 checkpoint (equivalent to a level A success criterion in WCAG 2.0):

·         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)

The requirement that a page be usable with client-side JavaScript turned off was prompted by the state of technology in 1999. Since then, assistive technology software has become able to interact with JavaScript.

However, if you must conform to an older accessibility standard, you may still be subject to this requirement. In that case, you may want to avoid using ASP.NET controls which require JavaScript in order to function. For example, The LinkButton and ImageButton controls require client script 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, to be fully conformant you cannot use JavaScript in a way that makes functionality available only by means of mouse actions. All ASP.NET controls enable keyboard access by default or can be configured to enable keyboard access.

 Separating Structure from Presentation

One basic principle of accessible Web page design that has not changed from WCAG 1.0 to 2.0 is that HTML should be semantically correct. This means that you should use HTML elements for the purposes for which they are intended.

For example, heading elements (h1, h2, h3) are intended to indicate a hierarchy of headings that describe the content of sections below them. There should only be one h1 element on a page to function as the heading for all of the content on the page.

Browsers give each heading element a default appearance to provide a visual indication of the element's function on the page, with h1 being the largest font, h2 being smaller, and so forth. What developers sometimes do, however, is use heading elements merely for the visual effect of a large, bold font.

This may not be a problem for a user who can see the screen and immediately understand that a large font is used for emphasis and is not really a heading. But when screen reader software encounters a heading element, it announces the text of the element as a heading, which might be confusing. Also, the user of a screen reader may rely on headings to navigate a document, because a user can ask the screen reader to read out all the headings and select one of them to go to that section. If some heading elements are used only for visual effects, this method of navigating a document will be difficult because of all of the extra clutter caused by heading elements that are not really headings.

You can avoid this problem by using HTML to encode the structure of the information on a web page and Cascading Style Sheets (CSS) to determine the visual appearance. For example, if you want some text to display with a large, bold font, but it is not a heading, you can use the CSS font-size attribute.

A common way that Web pages fail to separate structure from presentation is by using tables for visual formatting. Tables should be used to provide structure for the presentation of tabular data, and CSS should be used to format the visual presentation of the page. More information is provided about how to do this later in this topic. 

The remainder of this topic reviews some of the more important techniques for following accessibility guidelines in an ASP.NET Web site.

 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. It also specifies the text that is displayed on mobile internet devices that do not display images, or in standard browsers when a mouse pointer hovers over the image. The following example shows an alt attribute on an HTML img element:

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

Decorative Images

If an image is used only as a design element and has no useful information to convey, 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 simply assign an empty string to the AlternateText property. For example, suppose you add the following ASP.NET 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 disappeared. This is the default behavior of all ASP.NET control attributes. When you do not assign an attribute a value, it is not rendered. Unfortunately, in this case, we really want to render an empty string as the alt attribute value.

In order to make sure that HTML rendered for an ASP.NET 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 convey information, the alt attribute should generally 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 never simply repeat the content of the file name attribute. The purpose of the alt attribute is to convey to someone who is blind the same information that the image conveys 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 inadequate for complex images, such as organizational charts. In such cases you can provide a summary in the alt attribute and 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 property named DescriptionUrl 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" />

ASP.NET Controls that Generate Inherent Images

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

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 with the text 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.

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

 Providing Alternatives to Silverlight Media Content

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 ensuring 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:

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

·         For a video clip that does not contain audio, you should either make available a textual alternative or add a sound track with a verbal description of the visual content. For example, if the video shows how to do something, you could position next to the plug-in 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 a 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.

To attain Conformance Level AA you must also provide the following for video that is synchronized with audio:

·         Captions for the audio portion if the content is live.

·         An audio clip describing the content if the content is prerecorded.

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

Avoiding the Use of Tables for Page Layout

The most important way to ensure that a Web page still makes sense when its HTML is rendered into speech by assistive technology, or displayed only in text by a non-graphical browser, is to separate structure from presentation. And one of the most common ways that developers fail to follow this guideline is by using table elements to format the page or to control the relative locations of visual elements on the page.

When tables are used for formatting, 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. For example, the page in the following example has a three-column layout, but does not contain a single table element.

<%@ Page %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html  >
<head id="Head1" runat="server">
    <title>Tableless Layout</title>
    <style type="text/css">
    #content
    {
        margin-left:auto;
        margin-right:auto;
        width:800px;
    }
 
    #leftColumn 
    {
        float:left;
        width:150px;
        border:1px solid black;
        padding:10px;
    }
 
    #middleColumn
    {
        float:left;
        width:430px;
        padding:10px;
    }
 
    #rightColumn 
    {
        float:right;
        width:150px;
        border:1px solid black;
        padding:10px;
    }
    </style>
</head>
<body>
    <form id="form1" runat="server">
 
    <div id="content">
 
    <div id="leftColumn">
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    </div>
 
    <div id="middleColumn">
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    </div>

This page contains four div elements. The first div element is named content and specifies the width of the page's content area. The remaining three div tags (named left, middle, and right)divide the content area into three columns.

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).

As has been noted earlier in this topic, some ASP.NET controls automatically generate HTML tables for formatting purposes. These have been updated for ASP.NET 4 so that they generate accessible HTML automatically or can be configured to do so.

Identifying Row and Column Headers in Tables

Even if you use HTML tables correctly to present tabular data instead of formatting a page, they can cause accessibility problems. When the content of a table is read aloud, you can easily lose track of your current position in the table. For example, imagine that you use a table to display a list of product information, with columns for product number, weight, number in stock, and reorder level. After you hear several rows, when you hear "52," "32," "48," and "67" it might be difficult to remember that 48 is the weight and 32 is the product number.

When you look at an HTML table in a graphical browser, you can readily determine the meaning of a particular cell by glancing at either the column or row heading. In order to provide a similar experience for persons who are using screen readers, you need to explicitly associate table cells with the headings that pertain to them.

A table heading should always be marked by using th tags, 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>

In this example, the th tags mark the column headings Product and Price, and the thead tags mark the heading row.

Some developers avoid using th tags because they do not like the default visual appearance. In most browsers, the contents of a th element are centered and bold by default. However, remember that tags should never be relied upon to control presentation. If you want the column headings to resemble normal table cells, you can add a style rule such as the following:

<style type="text/css">

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

</style>

In order to make a table accessible, you should also explicitly indicate the heading or headings 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>

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

The scope attribute works great for simple tables. However, for more complex tables, you need to use the headers attribute. For example, a nested table might have three or more headings 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:

The headers attribute enables you to explicitly identify the headings that pertain to each cell. You can specify multiple headings by entering them 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 to make sure that the table is comprehensible when it is rendered by a screen reader.

<%@ Page %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://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, 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.

Providing Captions and Summaries for Tables

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 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, very much the way the alt attribute for an image. The preceding .aspx page also defines a summary attribute for the Red Line Schedule table.

 Using the Table Control to Create Accessible Tables

If you use the 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 with 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 Data Controls that Automatically Create Tables

The ASP.NET framework includes a rich set of controls for displaying database data. Controls that automatically display data in HTML tables include the following:

·         DetailsView

·         FormView

·         GridView

The tables that these controls generate include accessibility features by default, such 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 relevant to accessibility:

·         Caption and CaptionAlign

Use these properties to add a caption to the HTML table 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).

·         HeaderRow and FooterRow

Use these properties property to cause thead, tbody, and tfoot tags to be generated. Set HeaderRow to TableHeader to generate thead tags and set FooterRow to TableFooter to generate tfoot tags. If either thead or tfoot tags are generated, tbody tags will be generated also.

·         UseAccessibleHeader

Use this property to indicate whether column headings should be rendered with <th scope="col"> tags or <td> tags. By default, this property has the value true.

The GridView control does not have a Summary property. However, like most ASP.NET controls, the GridView control supports expando attributes. Therefore, if you want to add a summary to a GridView, you can declare the summary attribute in markup.

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.

Visual Basic

<%@ Page Language="VB"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://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 >
<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>

C#

<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://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 >
<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>

If you select View Source in your browser, you can see 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 Templates with Data Controls

For some data controls you can use templates to explicitly specify the HTML elements and attributes that data will be displayed in. Controls that require the use of templates include the following:

·         ListView

·         Repeater

These controls do not render any markup automatically; you define the header, body, and footer templates for the control, in which you can specify any markup. If you want one of these controls to render an HTML table, you should include the appropriate markup to meet accessibility standards.

The flexibility of templates makes it possible to generate complicated tables. Imagine, for example, that you want to display a list of customers and, under each customer, you want to display a list of that customer's addresses. In other words, you want to create a single-page Master/Detail form. In that case, you'll need to include the headers attribute for each table cell.

The .aspx 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.

Visual Basic

<%@ Page Language="VB" %>
 
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://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 >
<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>

C#

<%@ Page Language="C#" %>
 
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://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 >
 
<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>

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

The .aspx 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.

Providing Access Keys

WCAG guideline 2.1 states that all of a Web page's functionality should be accessible from a keyboard. 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. An accesskey attribute on an HTML element specifies a letter or number key that can be pressed together with some other function key to cause the focus to go directly to that element.

The function key that you use along with an access key depends on the browser and platform. For example, to shift focus to an element that has the letter A defined as its access key:

·         In most browsers that run on Windows computers, you press ALT+A.

·         In the Opera browser you press SHIFT+ESC and then press A.

·         In most browsers that run on Apple computers, you press CMD+A.

Access keys can make a form that has many optional input fields easier to navigate without a mouse, because you can go directly to the desired fields by using the keyboard.

However, there are some pitfalls that you should consider. Implementing access keys without careful planning can make a Web page's usability worse instead of better. For example:

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

·         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. A browser might not automatically mark them, or a browser might do so automatically in some instances but not in others. Unless you make sure to inform a Web page's users about them, some users may never find out that they are available.

·         The 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.

Some of these problems can be overcome by using only number keys as access keys, but this limits the number of access keys that can be assigned. However, there are so many problems with access keys that some accessibility advocates believe they generally create more problems than they solve. For more information, see the article about access keys on the WebAIM site.

You can use the AccessKey.property 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.

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" />

Notice that 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.

Ensuring Logical Tab Order

Another way to use the keyboard to shift focus to an input element or link that you want to work with is to use the TAB key. However, this method can be confusing if the tab order does not follow a logical sequence. For example, you may have a page that has three columns of form fields. The logical order would be to tab through the fields in each column, and at the end of a column move on to the next column. But unless you specify the expected order, 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.

Tab order can be controlled by the HTML tabindex attribute, which may be placed on input elements and hyperlinks. Browsers that comply with W3C standards respond to this element in the following ways:

·         Elements that have a positive (non-zero) tabindex value get the focus first. Navigation proceeds from the element with the lowest value to the element with the highest value. Elements that have identical values are navigated in the order they appear in the character stream.

·         Elements that do not support the tabindex attribute or assign it a value of zero are navigated next, in the order they appear in the character stream.

·         There is no tab stop for elements that are disabled, regardless of tabindex setting.

In ASP.NET controls you can use the TabIndex property to set the HTML tabindex attribute of the generated HTML elements. You can also 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.

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

<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" />

The ARIA standards allow the tabindex attribute on any HTML element because any element may be defined as a RIA widget (for information about ARIA see the section about 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 normally get one. However, not all browsers support these ARIA features, and markup that uses them may fail the W3C markup validation service.

Providing Skip-Navigation Links

Using a screen reader can be like calling a customer support number and having to wait through an interminable list of optional numbers to press, when all that you want is to talk to a real person. A well-designed customer support telephone system gives you the "zero-for-operator" option first so that you have a way to avoid waiting through everything that you do not care about. In a similar manner, a well-designed Web site navigation system gives people who use screen readers a way to avoid listening to all the navigation links before getting to the content they are interested in.

With one simple modification to a navigation bar, you can dramatically improve the accessibility of your Web pages. You simply need to add a method for someone to skip all of the navigation links. You can do this with a Skip-Navigation link.

The following ASP.NET controls automatically generate Skip-Navigation links:

·         Menu

·         TreeView

·         SiteMapPath

·         Wizard

·         CreateUserWizard

The following example shows an ASP.NET Web page with a simple 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"
  "http://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>

If you view the HTML source of this page in your browser, you will see a link appears at the top of the menu similar to the following example:

<a href="http://blogs.msdn.com/ControlPanel/Blogs/posteditor.aspx?SelectedNavItem=NewPost#Menu1_SkipLink"><img alt="Skip Navigation Links"

  src="http://blogs.msdn.com/WebResource.axd?d=ChXz41GuDxNm-7TcWyCl_w2&amp;t=632495684475122400"

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

When you view this page in a browser, you never see the Skip-Navigation link. The image this is contained in the link has zero width and zero height. However, if you access this page with a screen reader, the alternate text associated with the image is read. A person who is blind can decide to skip all of the navigation links and move directly to the main content area of the Web page (the equivalent of pressing 0 in an automated voice system to go directly to the operator).

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 about skip-navigation links, see the article about Skip-Navigation links on the WebAIM site.

 Providing Meaningful Link Text

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. One way that a screen reader can help a person navigate through a page is by announcing all of the hyperlinks in sequence. If hyperlinks depend on the text around them to make sense - such as links that say only Click here or Read more -- they will not make sense when a person listens to all of 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, but the one on the right is more useful to a person who uses a screen reader.

The .aspx markup that generates these pages is shown in the following example:

<%@ Page %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
 
</script>
<html >
<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>

Identifying the Language of the Web Page

One of the most important ways to make sure that a Web page is understandable when it is read by using a screen reader is to make sure that its human language is programmatically identifiable. This is a Level A requirement in WCAG 2.0.

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

<html lang="en-us">

When you create a Web 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 codes.

Dividing Long Forms into Sections

Large forms can create problems for individuals interacting with a Web page through a screen reader. When listening to a large form, it is easy to lose track of the section of the form that you are listening to. All users can benefit when you divide a long form into manageable sections, but especially those who use screen readers.

You can divide a single form into multiple 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>

This form is divided into two sub-forms, by using fieldset elements. The legend element is used to label the purpose of the sub-forms. Most browsers display a border to visually separate these areas. However, it is important to remember the principle of separating structure from presentation. The purpose of the fieldset element is to structure the form into sub-forms, not to provide a border. If you do not like the default visual appearance of the fieldset element, you can modify the default appearance through a style sheet rule, or you can completely hide any visual evidence of the element by using the CSS display or visibility attribute.

An ASP.NET control that you can use to create subdivisions in a form is the Panel control. If you set the GroupingText property for 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.

Some Web Parts controls render div elements also. These controls automatically render fieldset and legend elements.

 Responding Predictably to User Input

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

For example, in long questionnaires it is not uncommon for the answer to one question to determine whether a series of additional questions is asked. 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 page implements this behavior by using the AutoPostBack property, the entire Web page might be refreshed. In that case, a person who is using a graphical browser might just see the screen flicker as it adds an additional section, but a person who is using a screen reader might have to listen to the whole form all over 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.

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:

<table>
  <tr>
    <td>First Name:</td>
    <td><input name="txtFirstName" /></td>
  </tr>
  <tr>
    <td>Last Name:</td>
    <td><input name="txtLastName" /></td>
  </tr>
</table>

Because the form is displayed in a table, it might be difficult for the user of a screen reader to associate each label with its matching form field. HTML 4.0 addressed this problem by introducing the label element to enable you 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:

<table>
  <tr>
    <td><label for="txtFirstName">First Name:</label></td>
    <td><input name="txtFirstName" id="txtFirstName" /></td>
  </tr>
  <tr>
    <td><label for="txtLastName">Last Name:</label></td>
    <td><input name="txtLastName" id="txtLastName" /></td>
  </tr>
</table> 

The label element explicitly associates the form field labels with their corresponding form fields. Notice that the input fields include an id attribute. The value of the for attribute must be an input field's id and not its name attribute.

Using the ASP.NET Label Control

Normally, the ASP.NET Label control generates a span tag. However, if you set the AssociatedControlID property when you declare a Label control, the control renders a label tag. The following example shows how you can generate an accessible form with ASP.NET Label and TextBox controls.

<table>
  <tr>
    <td><asp:Label AssociatedControlID="txtFirstName" 
      runat="server">First Name:</asp:Label></td>
    <td><asp:TextBox ID="txtFirstName" runat="server" /></td>
  </tr>
  <tr>
    <td><asp:Label AssociatedControlID="txtLastName" 
      runat="server">Last Name:</asp:Label></td>
    <td><asp:TextBox ID="txtLastName" runat="server" /></td>
  </tr>
</table>

When you provide a label for an ASP.NET control, you should generally 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 a 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.

(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 Control Identification.)

Using ASP.NET CheckBox and RadioButton Controls

Some ASP.NET controls automatically render label elements, such as:

·         CheckBox

·         RadioButton

·         CheckBoxList

·         RadioButtonList

When you declare one of these controls, make sure that you use the Text property instead of including text between beginning and ending tags in markup. For example, you should not do the following:

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

Instead, do the following:

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

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

 Helping Users Avoid and Correct Mistakes

One way to help users avoid mistakes is to clearly indicate which fields are required. If you do this by displaying an asterisk (*) next to required fields or by rendering their labels in a bold font, you should explain clearly on the same page what this convention means. You should 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 may be indistinguishable.

A good way to help users correct mistakes it to provide text that clearly explains what was done wrong and what kind of input is expected. Therefore, when you use ASP.NET validator controls you should always specify meaningful and helpful error messages in the Text and ErrorMessage properties.

Ensuring that Web Pages are Robust

WCAG 2.0 specifies two Level A success criteria that a Web page must pass in order to be considered robust. The first is intended to ensure that Web pages work correctly in as many browsers and browser versions as possible. It states simply that the page must be composed of valid HTML that complies with the following four rules:

·         Elements have complete start and end tags.

·         Elements are nested according to their specifications.

·         Elements do not contain duplicate attributes.

·         Element IDs are unique.

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

The second Level A success criterion is intended to ensure that a Web page is robust in the sense that all of its user interface components can work effectively with assistive technology. This criterion 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 re-purpose HTML elements generally are not. This is the problem that ARIA is intended to address. As was explained earlier in this topic, ARIA is still a work in progress, and this topic does not provide detailed guidance about how to implement ARIA in ASP.NET Web pages.

 Validating Web Pages for Accessibility

No automated accessibility test can perform a complete diagnosis of the accessibility of a Web page or site. Many of the guidelines for accessibility require human judgment to determine whether features on a page conform to the guideline. For example, an automated test can determine whether you have provided alt text for all images on your page. However, the automated test cannot determine whether an alt text accurately describes the image, or if the alt text should be empty because the image is decorative.

Visual Studio (not Visual Web Developer Express Edition) includes an Accessibility Checker. You can open the Accessibility Checker from the toolbar, or you can select the menu option Tools, Check Accessibility. This opens the Accessibility Validation dialog box, as shown in the following illustration:

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 (select the menu option View, Other Windows, Error List).

The Visual Studio Accessibility Checker also provides you with the option of displaying a "manual checklist" of accessibility issues. If you select this option, the same static list of 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 has not yet been updated to allow you to validate against WCAG 2.0 guidelines. The most important accessibility guidelines are fundamentally similar, but when you run the Accessibility Checker you should be aware that unless your goal is specifically to conform to the older version of WCAG, some of the errors that it reports may not apply to you.

 Conclusion

The ASP.NET framework was designed to Accessible by Default, and this topic has provided an introduction to some basic HTML techniques and the framework's built-in accessibility features. For more detailed information and additional resources about accessibility, see the Microsoft Accessibility Web Site.

 -- Tom Dykstra
ASP.NET User Education
This posting is provided "AS IS" with no warranties, and confers no rights.

 

 

Give Your Feedback on the Documentation

Help us improve the developer documentation by taking the Visual Studio and .NET Framework Content Survey.  This survey will give us a better understanding of the type of applications you are developing as well as how you use Help and how we can improve it. The survey takes only 10 minutes, and we appreciate your feedback.