Loading...
Information, structure, and relationships conveyed through presentation can be programmatically determined
1. Perceivable
1.3 Adaptable
WCAG 2.0
When content is structured using visual formatting (like headings, lists, or tables), that same structure must be available to assistive technologies through proper markup. Screen readers and other tools need to understand the relationships between different pieces of content to navigate and interpret it correctly.
Heading Structure
Logical hierarchy from h1 to h6
Lists and Groups
Proper ul, ol, dl elements
Table Structure
Headers, captions, and data relationships
Form Relationships
Labels, fieldsets, and form structure
❌ Problems:
<!-- Proper heading structure -->
<h1>Main Page Title</h1>
<h2>Section Heading</h2>
<h3>Subsection</h3>
<h3>Another Subsection</h3>
<h2>Another Section</h2>
<!-- Proper list structure -->
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
<!-- Proper table structure -->
<table>
<caption>Monthly Sales Data</caption>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">Revenue</th>
<th scope="col">Units</th>
</tr>
</thead>
<tbody>
<tr>
<td>January</td>
<td>$15,000</td>
<td>150</td>
</tr>
</tbody>
</table>
<!-- Proper form structure -->
<form>
<fieldset>
<legend>Personal Information</legend>
<label for="name">Name:</label>
<input type="text" id="name" required>
<label for="email">Email:</label>
<input type="email" id="email" required>
</fieldset>
</form>// Semantic structure component
function ArticleLayout({ title, sections }) {
return (
<article>
<h1>{title}</h1>
{sections.map((section, index) => (
<section key={index}>
<h2>{section.title}</h2>
{section.subsections?.map((subsection, subIndex) => (
<div key={subIndex}>
<h3>{subsection.title}</h3>
<p>{subsection.content}</p>
</div>
))}
</section>
))}
</article>
);
}
// Accessible form component
function ContactForm() {
return (
<form>
<fieldset>
<legend>Contact Information</legend>
<div>
<label htmlFor="name">Full Name:</label>
<input
type="text"
id="name"
required
aria-describedby="name-help"
/>
<div id="name-help">Enter your first and last name</div>
</div>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
required
aria-describedby="email-help"
/>
<div id="email-help">We'll never share your email</div>
</div>
</fieldset>
<fieldset>
<legend>Preferences</legend>
<div role="group" aria-labelledby="contact-method">
<span id="contact-method">Preferred contact method:</span>
<label>
<input type="radio" name="contact" value="email" />
Email
</label>
<label>
<input type="radio" name="contact" value="phone" />
Phone
</label>
</div>
</fieldset>
</form>
);
}
// Accessible data table
function DataTable({ data, columns }) {
return (
<table>
<caption>Sales Data for Q1 2024</caption>
<thead>
<tr>
{columns.map(col => (
<th key={col.key} scope="col">{col.label}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr key={index}>
{columns.map(col => (
<td key={col.key}>{row[col.key]}</td>
))}
</tr>
))}
</tbody>
</table>
);
}/* Enhance semantic structure with CSS */
/* Heading hierarchy */
h1 { font-size: 2rem; margin-bottom: 1rem; }
h2 { font-size: 1.5rem; margin-bottom: 0.75rem; }
h3 { font-size: 1.25rem; margin-bottom: 0.5rem; }
/* List styling */
ul, ol {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
li {
margin-bottom: 0.25rem;
}
/* Table structure */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
caption {
font-weight: bold;
margin-bottom: 0.5rem;
caption-side: top;
}
th, td {
padding: 0.5rem;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
th {
background-color: #f8fafc;
font-weight: 600;
}
/* Form structure */
fieldset {
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
legend {
font-weight: 600;
padding: 0 0.5rem;
}
label {
display: block;
font-weight: 500;
margin-bottom: 0.25rem;
}
input, select, textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
margin-bottom: 1rem;
}
/* Focus indicators for keyboard navigation */
:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Screen reader only content */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}