Transform data tables into responsive, mobile-friendly components with effective data visualization and mobile data patterns.
Data tables are notoriously challenging on mobile devices. Traditional tables with multiple columns become impossible to read when compressed into narrow viewports. Yet data remains crucial for business applications and user experiences.
Modern responsive design requires creative approaches to present tabular data effectively across all screen sizes. From card-based layouts to horizontal scrolling patterns, there are multiple strategies to maintain data accessibility and usability on mobile devices.
In this lesson, you'll learn to transform rigid data tables into flexible, mobile-friendly components that preserve information hierarchy while enhancing user experience across all devices.
Different table structures require different responsive strategies. Understanding when and how to apply each approach is key to effective data presentation.
1. Horizontal Scroll Tables:
1/* Container with horizontal scroll */
2.table-container {
3 overflow-x: auto;
4 -webkit-overflow-scrolling: touch; /* iOS momentum scrolling */
5 border: 1px solid #e2e8f0;
6 border-radius: 8px;
7 background: white;
8 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
9}
10
11.responsive-table {
12 width: 100%;
13 min-width: 600px; /* Minimum width to maintain readability */
14 border-collapse: collapse;
15 font-size: 14px;
16}
17
18.responsive-table th,
19.responsive-table td {
20 padding: 12px 16px;
21 text-align: left;
22 border-bottom: 1px solid #e2e8f0;
23 white-space: nowrap; /* Prevent text wrapping */
24}
25
26.responsive-table th {
27 background: #f8fafc;
28 font-weight: 600;
29 color: #374151;
30 position: sticky;
31 top: 0;
32 z-index: 2;
33}
34
35/* Sticky first column for context */
36.responsive-table th:first-child,
37.responsive-table td:first-child {
38 position: sticky;
39 left: 0;
40 background: inherit;
41 z-index: 1;
42 box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
43}
44
45.responsive-table th:first-child {
46 z-index: 3; /* Higher than sticky column cells */
47}
48
49/* Visual scroll indicators */
50.table-container::before,
51.table-container::after {
52 content: '';
53 position: absolute;
54 top: 0;
55 bottom: 0;
56 width: 20px;
57 pointer-events: none;
58 z-index: 2;
59}
60
61.table-container::before {
62 left: 0;
63 background: linear-gradient(90deg, rgba(0,0,0,0.1), transparent);
64}
65
66.table-container::after {
67 right: 0;
68 background: linear-gradient(270deg, rgba(0,0,0,0.1), transparent);
69}2. Card-Based Transformation:
1/* Mobile-first card approach */
2.data-grid {
3 display: grid;
4 gap: 16px;
5 padding: 16px;
6}
7
8.data-card {
9 background: white;
10 border-radius: 12px;
11 padding: 20px;
12 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
13 border: 1px solid #e2e8f0;
14 transition: transform 0.2s ease, box-shadow 0.2s ease;
15}
16
17.data-card:hover {
18 transform: translateY(-2px);
19 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
20}
21
22.card-header {
23 display: flex;
24 justify-content: space-between;
25 align-items: center;
26 margin-bottom: 16px;
27 padding-bottom: 16px;
28 border-bottom: 1px solid #e2e8f0;
29}
30
31.card-title {
32 font-size: 18px;
33 font-weight: 600;
34 color: #1f2937;
35}
36
37.card-status {
38 padding: 4px 12px;
39 border-radius: 20px;
40 font-size: 12px;
41 font-weight: 500;
42 text-transform: uppercase;
43}
44
45.status-active {
46 background: #dcfce7;
47 color: #166534;
48}
49
50.status-pending {
51 background: #fef3c7;
52 color: #92400e;
53}
54
55.status-inactive {
56 background: #fee2e2;
57 color: #991b1b;
58}
59
60.card-content {
61 display: grid;
62 gap: 12px;
63}
64
65.data-row {
66 display: flex;
67 justify-content: space-between;
68 align-items: center;
69}
70
71.data-label {
72 font-size: 14px;
73 color: #6b7280;
74 font-weight: 500;
75}
76
77.data-value {
78 font-size: 14px;
79 color: #1f2937;
80 font-weight: 400;
81 text-align: right;
82}
83
84.data-value.highlight {
85 font-weight: 600;
86 color: #059669;
87}
88
89/* Progressive enhancement for larger screens */
90@media (min-width: 768px) {
91 .data-grid {
92 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
93 gap: 20px;
94 padding: 24px;
95 }
96}
97
98@media (min-width: 1024px) {
99 .data-grid {
100 grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
101 gap: 24px;
102 }
103}3. Stacked Table Layout:
1/* Stacked rows for mobile */
2.stacked-table {
3 display: block;
4 width: 100%;
5 border: 1px solid #e2e8f0;
6 border-radius: 8px;
7 overflow: hidden;
8 background: white;
9}
10
11.stacked-table thead {
12 display: none; /* Hide headers on mobile */
13}
14
15.stacked-table tbody {
16 display: block;
17}
18
19.stacked-table tr {
20 display: block;
21 border-bottom: 1px solid #e2e8f0;
22 padding: 16px;
23 position: relative;
24}
25
26.stacked-table tr:last-child {
27 border-bottom: none;
28}
29
30.stacked-table td {
31 display: block;
32 padding: 8px 0;
33 border: none;
34 position: relative;
35 padding-left: 140px; /* Space for labels */
36}
37
38.stacked-table td::before {
39 content: attr(data-label);
40 position: absolute;
41 left: 0;
42 top: 8px;
43 font-weight: 600;
44 color: #374151;
45 width: 120px;
46}
47
48/* Tablet and desktop styles */
49@media (min-width: 768px) {
50 .stacked-table {
51 display: table;
52 }
53
54 .stacked-table thead {
55 display: table-header-group;
56 }
57
58 .stacked-table tbody {
59 display: table-row-group;
60 }
61
62 .stacked-table tr {
63 display: table-row;
64 border-bottom: 1px solid #e2e8f0;
65 padding: 0;
66 }
67
68 .stacked-table th,
69 .stacked-table td {
70 display: table-cell;
71 padding: 12px 16px;
72 border-bottom: 1px solid #e2e8f0;
73 padding-left: 16px;
74 }
75
76 .stacked-table th {
77 background: #f8fafc;
78 font-weight: 600;
79 color: #374151;
80 }
81
82 .stacked-table td::before {
83 display: none;
84 }
85}4. Priority-Based Column Hiding:
1/* Progressive disclosure based on screen size */
2.priority-table {
3 width: 100%;
4 border-collapse: collapse;
5 background: white;
6 border-radius: 8px;
7 overflow: hidden;
8 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
9}
10
11.priority-table th,
12.priority-table td {
13 padding: 12px 16px;
14 text-align: left;
15 border-bottom: 1px solid #e2e8f0;
16}
17
18.priority-table th {
19 background: #f8fafc;
20 font-weight: 600;
21 color: #374151;
22}
23
24/* Column priority system */
25.col-priority-1 {
26 /* Always visible - most important */
27}
28
29.col-priority-2 {
30 display: none;
31}
32
33.col-priority-3 {
34 display: none;
35}
36
37.col-priority-4 {
38 display: none;
39}
40
41/* Show more columns as screen size increases */
42@media (min-width: 480px) {
43 .col-priority-2 {
44 display: table-cell;
45 }
46}
47
48@media (min-width: 768px) {
49 .col-priority-3 {
50 display: table-cell;
51 }
52}
53
54@media (min-width: 1024px) {
55 .col-priority-4 {
56 display: table-cell;
57 }
58}
59
60/* Column toggle controls */
61.column-controls {
62 margin-bottom: 16px;
63 padding: 12px;
64 background: #f8fafc;
65 border-radius: 8px;
66 display: flex;
67 flex-wrap: wrap;
68 gap: 8px;
69}
70
71.column-toggle {
72 padding: 6px 12px;
73 border: 1px solid #d1d5db;
74 background: white;
75 border-radius: 6px;
76 font-size: 14px;
77 cursor: pointer;
78 transition: all 0.2s ease;
79}
80
81.column-toggle.active {
82 background: #3b82f6;
83 color: white;
84 border-color: #3b82f6;
85}
86
87.column-toggle:hover {
88 border-color: #3b82f6;
89 color: #3b82f6;
90}Key Strategies:
Effective mobile data presentation goes beyond responsive tables. It requires thoughtful information architecture and visual hierarchy design.
Mobile-First Data Cards:
1/* Comprehensive data card system */
2.data-dashboard {
3 display: grid;
4 gap: 16px;
5 padding: 16px;
6 background: #f8fafc;
7 min-height: 100vh;
8}
9
10.summary-cards {
11 display: grid;
12 grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
13 gap: 12px;
14 margin-bottom: 20px;
15}
16
17.summary-card {
18 background: white;
19 border-radius: 12px;
20 padding: 16px;
21 text-align: center;
22 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
23 border: 1px solid #e2e8f0;
24}
25
26.summary-number {
27 font-size: 24px;
28 font-weight: 700;
29 color: #1f2937;
30 margin-bottom: 4px;
31}
32
33.summary-label {
34 font-size: 12px;
35 color: #6b7280;
36 text-transform: uppercase;
37 font-weight: 500;
38}
39
40.summary-trend {
41 margin-top: 8px;
42 font-size: 12px;
43 display: flex;
44 align-items: center;
45 justify-content: center;
46 gap: 4px;
47}
48
49.trend-up {
50 color: #059669;
51}
52
53.trend-down {
54 color: #dc2626;
55}
56
57.trend-neutral {
58 color: #6b7280;
59}
60
61/* Detailed data list */
62.data-list {
63 background: white;
64 border-radius: 12px;
65 overflow: hidden;
66 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
67 border: 1px solid #e2e8f0;
68}
69
70.list-header {
71 padding: 16px 20px;
72 background: #f8fafc;
73 border-bottom: 1px solid #e2e8f0;
74 display: flex;
75 justify-content: space-between;
76 align-items: center;
77}
78
79.list-title {
80 font-size: 16px;
81 font-weight: 600;
82 color: #1f2937;
83}
84
85.list-actions {
86 display: flex;
87 gap: 8px;
88}
89
90.action-btn {
91 padding: 6px 12px;
92 border: 1px solid #d1d5db;
93 background: white;
94 border-radius: 6px;
95 font-size: 12px;
96 cursor: pointer;
97 transition: all 0.2s ease;
98}
99
100.action-btn:hover {
101 border-color: #3b82f6;
102 color: #3b82f6;
103}
104
105.data-item {
106 padding: 16px 20px;
107 border-bottom: 1px solid #e2e8f0;
108 display: flex;
109 justify-content: space-between;
110 align-items: center;
111 transition: background-color 0.2s ease;
112}
113
114.data-item:hover {
115 background: #f8fafc;
116}
117
118.data-item:last-child {
119 border-bottom: none;
120}
121
122.item-info {
123 flex: 1;
124}
125
126.item-title {
127 font-size: 14px;
128 font-weight: 500;
129 color: #1f2937;
130 margin-bottom: 4px;
131}
132
133.item-subtitle {
134 font-size: 12px;
135 color: #6b7280;
136}
137
138.item-value {
139 text-align: right;
140 flex-shrink: 0;
141}
142
143.value-primary {
144 font-size: 16px;
145 font-weight: 600;
146 color: #1f2937;
147}
148
149.value-secondary {
150 font-size: 12px;
151 color: #6b7280;
152 margin-top: 2px;
153}
154
155/* Interactive data visualization */
156.chart-container {
157 background: white;
158 border-radius: 12px;
159 padding: 20px;
160 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
161 border: 1px solid #e2e8f0;
162 margin-bottom: 20px;
163}
164
165.chart-header {
166 display: flex;
167 justify-content: space-between;
168 align-items: center;
169 margin-bottom: 16px;
170}
171
172.chart-title {
173 font-size: 16px;
174 font-weight: 600;
175 color: #1f2937;
176}
177
178.chart-period {
179 font-size: 12px;
180 color: #6b7280;
181 padding: 4px 8px;
182 background: #f3f4f6;
183 border-radius: 4px;
184}
185
186/* Simple bar chart using CSS */
187.bar-chart {
188 display: flex;
189 align-items: end;
190 height: 120px;
191 gap: 8px;
192 padding: 0 4px;
193 border-bottom: 1px solid #e2e8f0;
194}
195
196.bar {
197 flex: 1;
198 background: linear-gradient(180deg, #3b82f6, #1d4ed8);
199 border-radius: 4px 4px 0 0;
200 min-height: 20px;
201 position: relative;
202 transition: all 0.3s ease;
203 cursor: pointer;
204}
205
206.bar:hover {
207 opacity: 0.8;
208 transform: scale(1.05);
209}
210
211.bar::after {
212 content: attr(data-value);
213 position: absolute;
214 top: -20px;
215 left: 50%;
216 transform: translateX(-50%);
217 font-size: 10px;
218 font-weight: 500;
219 color: #374151;
220 background: white;
221 padding: 2px 4px;
222 border-radius: 2px;
223 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
224 opacity: 0;
225 transition: opacity 0.2s ease;
226}
227
228.bar:hover::after {
229 opacity: 1;
230}
231
232.chart-labels {
233 display: flex;
234 justify-content: space-between;
235 margin-top: 8px;
236 padding: 0 4px;
237}
238
239.chart-label {
240 font-size: 10px;
241 color: #6b7280;
242 text-align: center;
243 flex: 1;
244}
245
246/* Progress indicators */
247.progress-list {
248 display: grid;
249 gap: 12px;
250}
251
252.progress-item {
253 display: flex;
254 align-items: center;
255 gap: 12px;
256}
257
258.progress-info {
259 flex: 1;
260 min-width: 0;
261}
262
263.progress-label {
264 font-size: 14px;
265 font-weight: 500;
266 color: #1f2937;
267 margin-bottom: 4px;
268 white-space: nowrap;
269 overflow: hidden;
270 text-overflow: ellipsis;
271}
272
273.progress-bar {
274 height: 6px;
275 background: #e5e7eb;
276 border-radius: 3px;
277 overflow: hidden;
278 position: relative;
279}
280
281.progress-fill {
282 height: 100%;
283 background: linear-gradient(90deg, #10b981, #059669);
284 border-radius: 3px;
285 transition: width 0.3s ease;
286}
287
288.progress-value {
289 font-size: 14px;
290 font-weight: 600;
291 color: #374151;
292 flex-shrink: 0;
293}Data Filtering and Search:
1/* Mobile-friendly filter controls */
2.filter-controls {
3 background: white;
4 border-radius: 12px;
5 padding: 16px;
6 margin-bottom: 16px;
7 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
8 border: 1px solid #e2e8f0;
9}
10
11.search-box {
12 position: relative;
13 margin-bottom: 16px;
14}
15
16.search-input {
17 width: 100%;
18 padding: 12px 16px 12px 40px;
19 border: 1px solid #d1d5db;
20 border-radius: 8px;
21 font-size: 14px;
22 background: white;
23 transition: border-color 0.2s ease;
24}
25
26.search-input:focus {
27 border-color: #3b82f6;
28 outline: none;
29 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
30}
31
32.search-icon {
33 position: absolute;
34 left: 12px;
35 top: 50%;
36 transform: translateY(-50%);
37 color: #6b7280;
38 font-size: 16px;
39}
40
41.filter-tabs {
42 display: flex;
43 gap: 8px;
44 overflow-x: auto;
45 padding: 4px 0;
46 -webkit-overflow-scrolling: touch;
47}
48
49.filter-tab {
50 padding: 8px 16px;
51 border: 1px solid #d1d5db;
52 background: white;
53 border-radius: 20px;
54 font-size: 14px;
55 white-space: nowrap;
56 cursor: pointer;
57 transition: all 0.2s ease;
58 flex-shrink: 0;
59}
60
61.filter-tab.active {
62 background: #3b82f6;
63 color: white;
64 border-color: #3b82f6;
65}
66
67.filter-tab:hover:not(.active) {
68 border-color: #3b82f6;
69 color: #3b82f6;
70}
71
72/* Sort controls */
73.sort-controls {
74 display: flex;
75 justify-content: space-between;
76 align-items: center;
77 margin-bottom: 16px;
78 padding: 12px 16px;
79 background: #f8fafc;
80 border-radius: 8px;
81}
82
83.sort-label {
84 font-size: 14px;
85 color: #374151;
86 font-weight: 500;
87}
88
89.sort-select {
90 padding: 6px 12px;
91 border: 1px solid #d1d5db;
92 border-radius: 6px;
93 font-size: 14px;
94 background: white;
95 cursor: pointer;
96}
97
98/* Loading and empty states */
99.loading-state {
100 display: flex;
101 flex-direction: column;
102 align-items: center;
103 justify-content: center;
104 padding: 40px 20px;
105 text-align: center;
106 color: #6b7280;
107}
108
109.loading-spinner {
110 width: 32px;
111 height: 32px;
112 border: 3px solid #e5e7eb;
113 border-top-color: #3b82f6;
114 border-radius: 50%;
115 animation: spin 1s linear infinite;
116 margin-bottom: 16px;
117}
118
119@keyframes spin {
120 to { transform: rotate(360deg); }
121}
122
123.empty-state {
124 display: flex;
125 flex-direction: column;
126 align-items: center;
127 justify-content: center;
128 padding: 40px 20px;
129 text-align: center;
130 color: #6b7280;
131}
132
133.empty-icon {
134 font-size: 48px;
135 margin-bottom: 16px;
136 opacity: 0.5;
137}
138
139.empty-title {
140 font-size: 16px;
141 font-weight: 500;
142 color: #374151;
143 margin-bottom: 8px;
144}
145
146.empty-description {
147 font-size: 14px;
148 color: #6b7280;
149 max-width: 300px;
150 line-height: 1.5;
151}Key Mobile Data Patterns:
Accessibility in data tables is crucial for screen readers and keyboard navigation. Proper semantic markup and ARIA attributes ensure all users can access and understand your data.
Semantic HTML Structure:
1<!-- Properly structured accessible table -->
2<div class="table-wrapper" role="region" aria-labelledby="table-caption" tabindex="0">
3 <table class="accessible-table" role="table">
4 <caption id="table-caption" class="table-caption">
5 Sales Data for Q4 2024
6 <div class="caption-description">
7 Quarterly sales figures showing revenue, units sold, and growth percentages
8 </div>
9 </caption>
10
11 <thead role="rowgroup">
12 <tr role="row">
13 <th role="columnheader"
14 scope="col"
15 aria-sort="none"
16 tabindex="0"
17 data-column="product">
18 Product
19 <button class="sort-btn" aria-label="Sort by product name">
20 <span class="sort-indicator" aria-hidden="true">↕</span>
21 </button>
22 </th>
23 <th role="columnheader"
24 scope="col"
25 aria-sort="descending"
26 tabindex="0"
27 data-column="revenue">
28 Revenue
29 <button class="sort-btn" aria-label="Sort by revenue, currently descending">
30 <span class="sort-indicator" aria-hidden="true">↓</span>
31 </button>
32 </th>
33 <th role="columnheader"
34 scope="col"
35 aria-sort="none"
36 tabindex="0"
37 data-column="units">
38 Units Sold
39 <button class="sort-btn" aria-label="Sort by units sold">
40 <span class="sort-indicator" aria-hidden="true">↕</span>
41 </button>
42 </th>
43 <th role="columnheader"
44 scope="col"
45 aria-sort="none"
46 tabindex="0"
47 data-column="growth">
48 Growth %
49 <button class="sort-btn" aria-label="Sort by growth percentage">
50 <span class="sort-indicator" aria-hidden="true">↕</span>
51 </button>
52 </th>
53 </tr>
54 </thead>
55
56 <tbody role="rowgroup">
57 <tr role="row">
58 <th role="rowheader" scope="row">Widget Pro</th>
59 <td role="gridcell" aria-describedby="revenue-note">
60 $124,500
61 </td>
62 <td role="gridcell">2,490</td>
63 <td role="gridcell" class="positive-growth" aria-label="15.3% growth, positive">
64 +15.3%
65 </td>
66 </tr>
67 <tr role="row">
68 <th role="rowheader" scope="row">Super Gadget</th>
69 <td role="gridcell">$89,200</td>
70 <td role="gridcell">1,784</td>
71 <td role="gridcell" class="negative-growth" aria-label="8.7% decline, negative">
72 -8.7%
73 </td>
74 </tr>
75 </tbody>
76 </table>
77
78 <div id="revenue-note" class="table-note">
79 Revenue figures are in USD and include tax
80 </div>
81</div>
82
83<!-- Accessible card alternative -->
84<div class="data-cards" role="list" aria-label="Sales data for Q4 2024">
85 <div class="data-card" role="listitem">
86 <h3 class="card-title" id="product-1">Widget Pro</h3>
87 <dl class="card-details" aria-labelledby="product-1">
88 <div class="detail-row">
89 <dt>Revenue:</dt>
90 <dd>$124,500</dd>
91 </div>
92 <div class="detail-row">
93 <dt>Units Sold:</dt>
94 <dd>2,490</dd>
95 </div>
96 <div class="detail-row">
97 <dt>Growth:</dt>
98 <dd class="positive-growth" aria-label="15.3% growth, positive">+15.3%</dd>
99 </div>
100 </dl>
101 </div>
102
103 <div class="data-card" role="listitem">
104 <h3 class="card-title" id="product-2">Super Gadget</h3>
105 <dl class="card-details" aria-labelledby="product-2">
106 <div class="detail-row">
107 <dt>Revenue:</dt>
108 <dd>$89,200</dd>
109 </div>
110 <div class="detail-row">
111 <dt>Units Sold:</dt>
112 <dd>1,784</dd>
113 </div>
114 <div class="detail-row">
115 <dt>Growth:</dt>
116 <dd class="negative-growth" aria-label="8.7% decline, negative">-8.7%</dd>
117 </div>
118 </dl>
119 </div>
120</div>Accessible CSS Styling:
1/* Accessible table styles */
2.table-wrapper {
3 position: relative;
4 overflow: auto;
5 max-height: 400px;
6 border: 2px solid #e5e7eb;
7 border-radius: 8px;
8 background: white;
9}
10
11.table-wrapper:focus {
12 outline: 3px solid #3b82f6;
13 outline-offset: 2px;
14}
15
16.accessible-table {
17 width: 100%;
18 border-collapse: collapse;
19 font-size: 14px;
20}
21
22.table-caption {
23 padding: 16px;
24 text-align: left;
25 font-size: 16px;
26 font-weight: 600;
27 color: #1f2937;
28 background: #f9fafb;
29 border-bottom: 1px solid #e5e7eb;
30}
31
32.caption-description {
33 font-size: 14px;
34 font-weight: 400;
35 color: #6b7280;
36 margin-top: 4px;
37}
38
39.accessible-table th,
40.accessible-table td {
41 padding: 12px 16px;
42 text-align: left;
43 border-bottom: 1px solid #e5e7eb;
44}
45
46.accessible-table th {
47 background: #f9fafb;
48 font-weight: 600;
49 color: #374151;
50 position: sticky;
51 top: 0;
52 z-index: 2;
53}
54
55/* Focus management for keyboard navigation */
56.accessible-table th:focus,
57.accessible-table td:focus {
58 outline: 2px solid #3b82f6;
59 outline-offset: -2px;
60 background-color: #dbeafe;
61}
62
63/* Sortable column headers */
64.accessible-table th[aria-sort] {
65 cursor: pointer;
66 user-select: none;
67 position: relative;
68 padding-right: 40px;
69}
70
71.sort-btn {
72 position: absolute;
73 right: 8px;
74 top: 50%;
75 transform: translateY(-50%);
76 background: none;
77 border: none;
78 font-size: 12px;
79 color: #6b7280;
80 cursor: pointer;
81 padding: 4px;
82 border-radius: 2px;
83}
84
85.sort-btn:focus {
86 outline: 2px solid #3b82f6;
87 outline-offset: 1px;
88 background: #dbeafe;
89}
90
91.sort-indicator {
92 display: inline-block;
93 transition: transform 0.2s ease;
94}
95
96th[aria-sort="ascending"] .sort-indicator {
97 transform: rotate(180deg);
98}
99
100th[aria-sort="descending"] .sort-indicator {
101 transform: rotate(0deg);
102}
103
104/* Row hover and selection */
105.accessible-table tbody tr:hover {
106 background-color: #f3f4f6;
107}
108
109.accessible-table tbody tr:focus-within {
110 background-color: #dbeafe;
111 outline: 2px solid #3b82f6;
112 outline-offset: -2px;
113}
114
115/* Data visualization accessibility */
116.positive-growth {
117 color: #059669;
118}
119
120.negative-growth {
121 color: #dc2626;
122}
123
124.positive-growth::before {
125 content: '↗ ';
126 font-size: 12px;
127 margin-right: 2px;
128}
129
130.negative-growth::before {
131 content: '↘ ';
132 font-size: 12px;
133 margin-right: 2px;
134}
135
136/* High contrast mode support */
137@media (prefers-contrast: high) {
138 .accessible-table th,
139 .accessible-table td {
140 border: 1px solid;
141 }
142
143 .positive-growth,
144 .negative-growth {
145 font-weight: bold;
146 }
147}
148
149/* Accessible card layout */
150.data-cards {
151 display: grid;
152 gap: 16px;
153 padding: 16px 0;
154}
155
156.data-card {
157 background: white;
158 border: 2px solid #e5e7eb;
159 border-radius: 8px;
160 padding: 20px;
161 transition: border-color 0.2s ease;
162}
163
164.data-card:focus-within {
165 border-color: #3b82f6;
166 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
167}
168
169.card-title {
170 font-size: 16px;
171 font-weight: 600;
172 color: #1f2937;
173 margin-bottom: 12px;
174}
175
176.card-details {
177 display: grid;
178 gap: 8px;
179}
180
181.detail-row {
182 display: flex;
183 justify-content: space-between;
184 align-items: center;
185}
186
187.detail-row dt {
188 font-weight: 500;
189 color: #6b7280;
190}
191
192.detail-row dd {
193 font-weight: 600;
194 color: #1f2937;
195}
196
197/* Screen reader announcements */
198.sr-announcement {
199 position: absolute;
200 left: -10000px;
201 width: 1px;
202 height: 1px;
203 overflow: hidden;
204}
205
206/* Table notes and descriptions */
207.table-note {
208 padding: 12px 16px;
209 background: #f9fafb;
210 border-top: 1px solid #e5e7eb;
211 font-size: 12px;
212 color: #6b7280;
213 font-style: italic;
214}JavaScript for Accessibility:
1class AccessibleDataTable {
2 constructor(table) {
3 this.table = table;
4 this.currentSort = { column: null, direction: null };
5 this.init();
6 }
7
8 init() {
9 this.setupKeyboardNavigation();
10 this.setupSortableColumns();
11 this.setupAriaLiveRegion();
12 }
13
14 setupKeyboardNavigation() {
15 // Arrow key navigation within table
16 this.table.addEventListener('keydown', (e) => {
17 const focusedCell = document.activeElement;
18 const isTableCell = focusedCell.matches('th, td');
19
20 if (!isTableCell) return;
21
22 const row = focusedCell.parentElement;
23 const cells = Array.from(row.children);
24 const rows = Array.from(this.table.querySelectorAll('tr'));
25 const currentCellIndex = cells.indexOf(focusedCell);
26 const currentRowIndex = rows.indexOf(row);
27
28 let nextFocus = null;
29
30 switch(e.key) {
31 case 'ArrowRight':
32 nextFocus = cells[currentCellIndex + 1];
33 break;
34 case 'ArrowLeft':
35 nextFocus = cells[currentCellIndex - 1];
36 break;
37 case 'ArrowDown':
38 const nextRow = rows[currentRowIndex + 1];
39 nextFocus = nextRow?.children[currentCellIndex];
40 break;
41 case 'ArrowUp':
42 const prevRow = rows[currentRowIndex - 1];
43 nextFocus = prevRow?.children[currentCellIndex];
44 break;
45 case 'Home':
46 nextFocus = cells[0];
47 e.preventDefault();
48 break;
49 case 'End':
50 nextFocus = cells[cells.length - 1];
51 e.preventDefault();
52 break;
53 }
54
55 if (nextFocus) {
56 nextFocus.focus();
57 e.preventDefault();
58 }
59 });
60 }
61
62 setupSortableColumns() {
63 const sortableHeaders = this.table.querySelectorAll('th[aria-sort]');
64
65 sortableHeaders.forEach(header => {
66 header.addEventListener('click', () => this.sortColumn(header));
67 header.addEventListener('keydown', (e) => {
68 if (e.key === 'Enter' || e.key === ' ') {
69 e.preventDefault();
70 this.sortColumn(header);
71 }
72 });
73 });
74 }
75
76 sortColumn(header) {
77 const column = header.dataset.column;
78 let direction = 'ascending';
79
80 // Toggle direction if same column
81 if (this.currentSort.column === column) {
82 direction = this.currentSort.direction === 'ascending' ? 'descending' : 'ascending';
83 }
84
85 // Reset all headers
86 this.table.querySelectorAll('th[aria-sort]').forEach(th => {
87 th.setAttribute('aria-sort', 'none');
88 th.querySelector('.sort-indicator').textContent = '↕';
89 });
90
91 // Set current header
92 header.setAttribute('aria-sort', direction);
93 const indicator = header.querySelector('.sort-indicator');
94 indicator.textContent = direction === 'ascending' ? '↑' : '↓';
95
96 // Update sort button label
97 const sortBtn = header.querySelector('.sort-btn');
98 sortBtn.setAttribute('aria-label',
99 `Sort by ${header.textContent.trim()}, currently ${direction}`);
100
101 this.currentSort = { column, direction };
102 this.announceSort(header.textContent.trim(), direction);
103
104 // Actual sorting logic would go here
105 console.log(`Sorting by ${column} in ${direction} order`);
106 }
107
108 setupAriaLiveRegion() {
109 if (!document.getElementById('table-announcements')) {
110 const liveRegion = document.createElement('div');
111 liveRegion.id = 'table-announcements';
112 liveRegion.className = 'sr-announcement';
113 liveRegion.setAttribute('aria-live', 'polite');
114 liveRegion.setAttribute('aria-atomic', 'true');
115 document.body.appendChild(liveRegion);
116 }
117 }
118
119 announceSort(columnName, direction) {
120 const liveRegion = document.getElementById('table-announcements');
121 liveRegion.textContent = `Table sorted by ${columnName} in ${direction} order`;
122
123 // Clear announcement after delay
124 setTimeout(() => {
125 liveRegion.textContent = '';
126 }, 1000);
127 }
128
129 announceDataUpdate(message) {
130 const liveRegion = document.getElementById('table-announcements');
131 liveRegion.textContent = message;
132
133 setTimeout(() => {
134 liveRegion.textContent = '';
135 }, 1000);
136 }
137}
138
139// Initialize accessible tables
140document.addEventListener('DOMContentLoaded', () => {
141 const accessibleTables = document.querySelectorAll('.accessible-table');
142 accessibleTables.forEach(table => new AccessibleDataTable(table));
143});Accessibility Best Practices:
Build a comprehensive data dashboard with responsive tables, mobile-friendly cards, interactive charts, and full accessibility support.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Responsive Data Tables & Dashboard</title>
7 <style>
8 * {
9 margin: 0;
10 padding: 0;
11 box-sizing: border-box;
12 }
13
14 body {
15 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16 background: #f8fafc;
17 color: #1f2937;
18 line-height: 1.6;
19 -webkit-font-smoothing: antialiased;
20 }
21
22 .dashboard {
23 max-width: 1200px;
24 margin: 0 auto;
25 padding: 20px;
26 }
27
28 .dashboard-header {
29 text-align: center;
30 margin-bottom: 32px;
31 }
32
33 .dashboard-title {
34 font-size: clamp(24px, 4vw, 32px);
35 font-weight: 700;
36 color: #1f2937;
37 margin-bottom: 8px;
38 }
39
40 .dashboard-subtitle {
41 color: #6b7280;
42 font-size: 16px;
43 }
44
45 /* Summary Cards */
46 .summary-section {
47 margin-bottom: 32px;
48 }
49
50 .section-title {
51 font-size: 20px;
52 font-weight: 600;
53 color: #1f2937;
54 margin-bottom: 16px;
55 }
56
57 .summary-grid {
58 display: grid;
59 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
60 gap: 16px;
61 margin-bottom: 24px;
62 }
63
64 .summary-card {
65 background: white;
66 border-radius: 12px;
67 padding: 20px;
68 text-align: center;
69 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
70 border: 1px solid #e5e7eb;
71 transition: transform 0.2s ease, box-shadow 0.2s ease;
72 }
73
74 .summary-card:hover {
75 transform: translateY(-2px);
76 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
77 }
78
79 .summary-number {
80 font-size: 28px;
81 font-weight: 700;
82 color: #1f2937;
83 margin-bottom: 8px;
84 }
85
86 .summary-label {
87 font-size: 14px;
88 color: #6b7280;
89 text-transform: uppercase;
90 font-weight: 500;
91 margin-bottom: 8px;
92 }
93
94 .summary-trend {
95 font-size: 12px;
96 display: flex;
97 align-items: center;
98 justify-content: center;
99 gap: 4px;
100 }
101
102 .trend-up { color: #059669; }
103 .trend-down { color: #dc2626; }
104 .trend-neutral { color: #6b7280; }
105
106 /* Data Table Section */
107 .table-section {
108 background: white;
109 border-radius: 12px;
110 margin-bottom: 32px;
111 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
112 border: 1px solid #e5e7eb;
113 overflow: hidden;
114 }
115
116 .table-header {
117 padding: 20px;
118 background: #f8fafc;
119 border-bottom: 1px solid #e5e7eb;
120 display: flex;
121 justify-content: space-between;
122 align-items: center;
123 flex-wrap: wrap;
124 gap: 12px;
125 }
126
127 .table-title {
128 font-size: 18px;
129 font-weight: 600;
130 color: #1f2937;
131 }
132
133 .table-controls {
134 display: flex;
135 gap: 12px;
136 align-items: center;
137 flex-wrap: wrap;
138 }
139
140 .view-toggle {
141 display: flex;
142 border: 1px solid #d1d5db;
143 border-radius: 6px;
144 overflow: hidden;
145 }
146
147 .view-btn {
148 padding: 6px 12px;
149 border: none;
150 background: white;
151 font-size: 14px;
152 cursor: pointer;
153 transition: all 0.2s ease;
154 }
155
156 .view-btn.active {
157 background: #3b82f6;
158 color: white;
159 }
160
161 .view-btn:not(.active):hover {
162 background: #f3f4f6;
163 }
164
165 .search-input {
166 padding: 6px 12px;
167 border: 1px solid #d1d5db;
168 border-radius: 6px;
169 font-size: 14px;
170 width: 200px;
171 }
172
173 .search-input:focus {
174 border-color: #3b82f6;
175 outline: none;
176 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
177 }
178
179 /* Responsive Table */
180 .table-container {
181 overflow-x: auto;
182 -webkit-overflow-scrolling: touch;
183 position: relative;
184 }
185
186 .responsive-table {
187 width: 100%;
188 min-width: 700px;
189 border-collapse: collapse;
190 }
191
192 .responsive-table th,
193 .responsive-table td {
194 padding: 16px;
195 text-align: left;
196 border-bottom: 1px solid #e5e7eb;
197 }
198
199 .responsive-table th {
200 background: #f8fafc;
201 font-weight: 600;
202 color: #374151;
203 position: sticky;
204 top: 0;
205 z-index: 2;
206 cursor: pointer;
207 user-select: none;
208 }
209
210 .responsive-table th:hover {
211 background: #e5e7eb;
212 }
213
214 .responsive-table th:first-child,
215 .responsive-table td:first-child {
216 position: sticky;
217 left: 0;
218 background: inherit;
219 z-index: 1;
220 box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
221 }
222
223 .responsive-table th:first-child {
224 z-index: 3;
225 }
226
227 .responsive-table tr:hover {
228 background: #f8fafc;
229 }
230
231 .sort-indicator {
232 margin-left: 8px;
233 color: #9ca3af;
234 font-size: 12px;
235 }
236
237 .status-badge {
238 padding: 4px 8px;
239 border-radius: 12px;
240 font-size: 12px;
241 font-weight: 500;
242 text-transform: uppercase;
243 }
244
245 .status-active {
246 background: #d1fae5;
247 color: #065f46;
248 }
249
250 .status-pending {
251 background: #fef3c7;
252 color: #92400e;
253 }
254
255 .status-inactive {
256 background: #fee2e2;
257 color: #991b1b;
258 }
259
260 .progress-bar {
261 width: 60px;
262 height: 8px;
263 background: #e5e7eb;
264 border-radius: 4px;
265 overflow: hidden;
266 }
267
268 .progress-fill {
269 height: 100%;
270 background: linear-gradient(90deg, #10b981, #059669);
271 border-radius: 4px;
272 transition: width 0.3s ease;
273 }
274
275 /* Card View */
276 .card-view {
277 display: none;
278 padding: 20px;
279 }
280
281 .card-view.active {
282 display: block;
283 }
284
285 .data-grid {
286 display: grid;
287 grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
288 gap: 16px;
289 }
290
291 .data-card {
292 background: white;
293 border: 1px solid #e5e7eb;
294 border-radius: 8px;
295 padding: 20px;
296 transition: all 0.2s ease;
297 }
298
299 .data-card:hover {
300 border-color: #3b82f6;
301 transform: translateY(-1px);
302 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
303 }
304
305 .card-header {
306 display: flex;
307 justify-content: space-between;
308 align-items: start;
309 margin-bottom: 16px;
310 }
311
312 .card-title {
313 font-size: 16px;
314 font-weight: 600;
315 color: #1f2937;
316 }
317
318 .card-content {
319 display: grid;
320 gap: 12px;
321 }
322
323 .card-row {
324 display: flex;
325 justify-content: space-between;
326 align-items: center;
327 }
328
329 .card-label {
330 font-size: 14px;
331 color: #6b7280;
332 font-weight: 500;
333 }
334
335 .card-value {
336 font-size: 14px;
337 color: #1f2937;
338 font-weight: 500;
339 }
340
341 /* Chart Section */
342 .chart-section {
343 background: white;
344 border-radius: 12px;
345 padding: 24px;
346 margin-bottom: 32px;
347 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
348 border: 1px solid #e5e7eb;
349 }
350
351 .chart-header {
352 display: flex;
353 justify-content: space-between;
354 align-items: center;
355 margin-bottom: 24px;
356 }
357
358 .chart-title {
359 font-size: 18px;
360 font-weight: 600;
361 color: #1f2937;
362 }
363
364 .chart-period {
365 font-size: 14px;
366 color: #6b7280;
367 padding: 6px 12px;
368 background: #f3f4f6;
369 border-radius: 6px;
370 }
371
372 .bar-chart {
373 display: flex;
374 align-items: end;
375 height: 200px;
376 gap: 12px;
377 padding: 20px 8px;
378 border-bottom: 2px solid #e5e7eb;
379 margin-bottom: 16px;
380 }
381
382 .bar {
383 flex: 1;
384 background: linear-gradient(180deg, #3b82f6, #1d4ed8);
385 border-radius: 4px 4px 0 0;
386 min-height: 20px;
387 position: relative;
388 transition: all 0.3s ease;
389 cursor: pointer;
390 }
391
392 .bar:hover {
393 opacity: 0.8;
394 transform: scaleY(1.05);
395 }
396
397 .bar::after {
398 content: attr(data-value);
399 position: absolute;
400 top: -24px;
401 left: 50%;
402 transform: translateX(-50%);
403 font-size: 12px;
404 font-weight: 500;
405 color: #374151;
406 background: white;
407 padding: 4px 8px;
408 border-radius: 4px;
409 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
410 opacity: 0;
411 transition: opacity 0.2s ease;
412 white-space: nowrap;
413 }
414
415 .bar:hover::after {
416 opacity: 1;
417 }
418
419 .chart-labels {
420 display: flex;
421 justify-content: space-between;
422 padding: 0 8px;
423 }
424
425 .chart-label {
426 font-size: 12px;
427 color: #6b7280;
428 text-align: center;
429 flex: 1;
430 }
431
432 /* Filter Section */
433 .filter-section {
434 background: white;
435 border-radius: 12px;
436 padding: 20px;
437 margin-bottom: 32px;
438 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
439 border: 1px solid #e5e7eb;
440 }
441
442 .filter-tabs {
443 display: flex;
444 gap: 8px;
445 overflow-x: auto;
446 padding: 8px 0;
447 margin-bottom: 16px;
448 -webkit-overflow-scrolling: touch;
449 }
450
451 .filter-tab {
452 padding: 8px 16px;
453 border: 1px solid #d1d5db;
454 background: white;
455 border-radius: 20px;
456 font-size: 14px;
457 white-space: nowrap;
458 cursor: pointer;
459 transition: all 0.2s ease;
460 flex-shrink: 0;
461 }
462
463 .filter-tab.active {
464 background: #3b82f6;
465 color: white;
466 border-color: #3b82f6;
467 }
468
469 .filter-tab:hover:not(.active) {
470 border-color: #3b82f6;
471 color: #3b82f6;
472 }
473
474 /* Loading States */
475 .loading-spinner {
476 display: inline-block;
477 width: 20px;
478 height: 20px;
479 border: 2px solid #e5e7eb;
480 border-top-color: #3b82f6;
481 border-radius: 50%;
482 animation: spin 1s linear infinite;
483 }
484
485 @keyframes spin {
486 to { transform: rotate(360deg); }
487 }
488
489 .loading-overlay {
490 position: absolute;
491 top: 0;
492 left: 0;
493 right: 0;
494 bottom: 0;
495 background: rgba(255, 255, 255, 0.8);
496 display: flex;
497 align-items: center;
498 justify-content: center;
499 opacity: 0;
500 visibility: hidden;
501 transition: all 0.3s ease;
502 }
503
504 .loading-overlay.active {
505 opacity: 1;
506 visibility: visible;
507 }
508
509 /* Responsive Design */
510 @media (max-width: 768px) {
511 .dashboard {
512 padding: 16px;
513 }
514
515 .summary-grid {
516 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
517 gap: 12px;
518 }
519
520 .summary-card {
521 padding: 16px;
522 }
523
524 .table-header {
525 flex-direction: column;
526 align-items: stretch;
527 gap: 16px;
528 }
529
530 .table-controls {
531 justify-content: space-between;
532 }
533
534 .search-input {
535 width: 100%;
536 max-width: none;
537 }
538
539 .responsive-table {
540 min-width: 600px;
541 }
542
543 .data-grid {
544 grid-template-columns: 1fr;
545 gap: 12px;
546 }
547
548 .bar-chart {
549 height: 150px;
550 gap: 8px;
551 }
552
553 .chart-header {
554 flex-direction: column;
555 align-items: start;
556 gap: 12px;
557 }
558 }
559
560 @media (max-width: 480px) {
561 .dashboard-title {
562 font-size: 24px;
563 }
564
565 .summary-grid {
566 grid-template-columns: 1fr;
567 }
568
569 .table-section,
570 .chart-section,
571 .filter-section {
572 border-radius: 8px;
573 }
574
575 .responsive-table {
576 min-width: 500px;
577 font-size: 14px;
578 }
579
580 .responsive-table th,
581 .responsive-table td {
582 padding: 12px 8px;
583 }
584 }
585
586 /* Accessibility */
587 @media (prefers-reduced-motion: reduce) {
588 * {
589 transition-duration: 0.01ms !important;
590 animation-duration: 0.01ms !important;
591 }
592 }
593
594 @media (prefers-contrast: high) {
595 .summary-card,
596 .table-section,
597 .chart-section,
598 .filter-section {
599 border: 2px solid;
600 }
601 }
602
603 .sr-only {
604 position: absolute;
605 width: 1px;
606 height: 1px;
607 padding: 0;
608 margin: -1px;
609 overflow: hidden;
610 clip: rect(0, 0, 0, 0);
611 white-space: nowrap;
612 border: 0;
613 }
614 </style>
615</head>
616<body>
617 <div class="dashboard">
618 <!-- Dashboard Header -->
619 <header class="dashboard-header">
620 <h1 class="dashboard-title">📊 Sales Dashboard</h1>
621 <p class="dashboard-subtitle">Q4 2024 Performance Overview</p>
622 </header>
623
624 <!-- Summary Cards -->
625 <section class="summary-section">
626 <h2 class="section-title">Key Metrics</h2>
627 <div class="summary-grid">
628 <div class="summary-card">
629 <div class="summary-number" id="total-revenue">$847,293</div>
630 <div class="summary-label">Total Revenue</div>
631 <div class="summary-trend trend-up">
632 <span>↗</span> +12.5%
633 </div>
634 </div>
635 <div class="summary-card">
636 <div class="summary-number" id="total-orders">12,847</div>
637 <div class="summary-label">Total Orders</div>
638 <div class="summary-trend trend-up">
639 <span>↗</span> +8.3%
640 </div>
641 </div>
642 <div class="summary-card">
643 <div class="summary-number" id="avg-order">$66</div>
644 <div class="summary-label">Avg Order Value</div>
645 <div class="summary-trend trend-down">
646 <span>↘</span> -2.1%
647 </div>
648 </div>
649 <div class="summary-card">
650 <div class="summary-number" id="conversion">3.4%</div>
651 <div class="summary-label">Conversion Rate</div>
652 <div class="summary-trend trend-up">
653 <span>↗</span> +0.8%
654 </div>
655 </div>
656 </div>
657 </section>
658
659 <!-- Filter Section -->
660 <section class="filter-section">
661 <h2 class="section-title">Filters</h2>
662 <div class="filter-tabs">
663 <button class="filter-tab active" data-filter="all">All Products</button>
664 <button class="filter-tab" data-filter="electronics">Electronics</button>
665 <button class="filter-tab" data-filter="clothing">Clothing</button>
666 <button class="filter-tab" data-filter="home">Home & Garden</button>
667 <button class="filter-tab" data-filter="books">Books</button>
668 </div>
669 </section>
670
671 <!-- Chart Section -->
672 <section class="chart-section">
673 <div class="chart-header">
674 <h2 class="chart-title">Monthly Sales Trend</h2>
675 <div class="chart-period">Last 6 Months</div>
676 </div>
677
678 <div class="bar-chart" role="img" aria-label="Monthly sales chart showing growth from July to December">
679 <div class="bar" style="height: 60%" data-value="$120K" aria-label="July: $120,000"></div>
680 <div class="bar" style="height: 75%" data-value="$150K" aria-label="August: $150,000"></div>
681 <div class="bar" style="height: 45%" data-value="$90K" aria-label="September: $90,000"></div>
682 <div class="bar" style="height: 85%" data-value="$170K" aria-label="October: $170,000"></div>
683 <div class="bar" style="height: 95%" data-value="$190K" aria-label="November: $190,000"></div>
684 <div class="bar" style="height: 100%" data-value="$200K" aria-label="December: $200,000"></div>
685 </div>
686
687 <div class="chart-labels">
688 <div class="chart-label">Jul</div>
689 <div class="chart-label">Aug</div>
690 <div class="chart-label">Sep</div>
691 <div class="chart-label">Oct</div>
692 <div class="chart-label">Nov</div>
693 <div class="chart-label">Dec</div>
694 </div>
695 </section>
696
697 <!-- Data Table Section -->
698 <section class="table-section">
699 <div class="table-header">
700 <h2 class="table-title">Product Performance</h2>
701 <div class="table-controls">
702 <div class="view-toggle">
703 <button class="view-btn active" data-view="table">Table</button>
704 <button class="view-btn" data-view="cards">Cards</button>
705 </div>
706 <input type="text" class="search-input" placeholder="Search products..." aria-label="Search products">
707 </div>
708 </div>
709
710 <div class="loading-overlay" id="tableLoading">
711 <div class="loading-spinner"></div>
712 </div>
713
714 <!-- Table View -->
715 <div class="table-view active">
716 <div class="table-container">
717 <table class="responsive-table" role="table" aria-label="Product performance data">
718 <thead>
719 <tr>
720 <th scope="col" data-sort="name" tabindex="0" role="columnheader" aria-sort="none">
721 Product Name
722 <span class="sort-indicator">↕</span>
723 </th>
724 <th scope="col" data-sort="category" tabindex="0" role="columnheader" aria-sort="none">
725 Category
726 <span class="sort-indicator">↕</span>
727 </th>
728 <th scope="col" data-sort="sales" tabindex="0" role="columnheader" aria-sort="descending">
729 Sales
730 <span class="sort-indicator">↓</span>
731 </th>
732 <th scope="col" data-sort="revenue" tabindex="0" role="columnheader" aria-sort="none">
733 Revenue
734 <span class="sort-indicator">↕</span>
735 </th>
736 <th scope="col" data-sort="growth" tabindex="0" role="columnheader" aria-sort="none">
737 Growth
738 <span class="sort-indicator">↕</span>
739 </th>
740 <th scope="col" data-sort="status" tabindex="0" role="columnheader" aria-sort="none">
741 Status
742 <span class="sort-indicator">↕</span>
743 </th>
744 <th scope="col" data-sort="progress" tabindex="0" role="columnheader" aria-sort="none">
745 Target Progress
746 <span class="sort-indicator">↕</span>
747 </th>
748 </tr>
749 </thead>
750 <tbody>
751 <tr>
752 <th scope="row">Wireless Headphones Pro</th>
753 <td>Electronics</td>
754 <td>2,847</td>
755 <td>$142,350</td>
756 <td class="trend-up">+18.5%</td>
757 <td><span class="status-badge status-active">Active</span></td>
758 <td>
759 <div class="progress-bar" aria-label="85% of target achieved">
760 <div class="progress-fill" style="width: 85%"></div>
761 </div>
762 </td>
763 </tr>
764 <tr>
765 <th scope="row">Smart Fitness Watch</th>
766 <td>Electronics</td>
767 <td>1,923</td>
768 <td>$96,150</td>
769 <td class="trend-up">+12.3%</td>
770 <td><span class="status-badge status-active">Active</span></td>
771 <td>
772 <div class="progress-bar" aria-label="72% of target achieved">
773 <div class="progress-fill" style="width: 72%"></div>
774 </div>
775 </td>
776 </tr>
777 <tr>
778 <th scope="row">Designer Backpack</th>
779 <td>Clothing</td>
780 <td>1,547</td>
781 <td>$77,350</td>
782 <td class="trend-down">-5.2%</td>
783 <td><span class="status-badge status-pending">Pending</span></td>
784 <td>
785 <div class="progress-bar" aria-label="58% of target achieved">
786 <div class="progress-fill" style="width: 58%"></div>
787 </div>
788 </td>
789 </tr>
790 <tr>
791 <th scope="row">Organic Coffee Blend</th>
792 <td>Home & Garden</td>
793 <td>3,245</td>
794 <td>$64,900</td>
795 <td class="trend-up">+25.7%</td>
796 <td><span class="status-badge status-active">Active</span></td>
797 <td>
798 <div class="progress-bar" aria-label="92% of target achieved">
799 <div class="progress-fill" style="width: 92%"></div>
800 </div>
801 </td>
802 </tr>
803 <tr>
804 <th scope="row">Programming Guide 2024</th>
805 <td>Books</td>
806 <td>892</td>
807 <td>$26,760</td>
808 <td class="trend-up">+8.9%</td>
809 <td><span class="status-badge status-active">Active</span></td>
810 <td>
811 <div class="progress-bar" aria-label="44% of target achieved">
812 <div class="progress-fill" style="width: 44%"></div>
813 </div>
814 </td>
815 </tr>
816 <tr>
817 <th scope="row">Vintage Denim Jacket</th>
818 <td>Clothing</td>
819 <td>734</td>
820 <td>$51,380</td>
821 <td class="trend-down">-12.4%</td>
822 <td><span class="status-badge status-inactive">Inactive</span></td>
823 <td>
824 <div class="progress-bar" aria-label="31% of target achieved">
825 <div class="progress-fill" style="width: 31%"></div>
826 </div>
827 </td>
828 </tr>
829 </tbody>
830 </table>
831 </div>
832 </div>
833
834 <!-- Card View -->
835 <div class="card-view">
836 <div class="data-grid">
837 <div class="data-card">
838 <div class="card-header">
839 <h3 class="card-title">Wireless Headphones Pro</h3>
840 <span class="status-badge status-active">Active</span>
841 </div>
842 <div class="card-content">
843 <div class="card-row">
844 <span class="card-label">Category:</span>
845 <span class="card-value">Electronics</span>
846 </div>
847 <div class="card-row">
848 <span class="card-label">Sales:</span>
849 <span class="card-value">2,847</span>
850 </div>
851 <div class="card-row">
852 <span class="card-label">Revenue:</span>
853 <span class="card-value">$142,350</span>
854 </div>
855 <div class="card-row">
856 <span class="card-label">Growth:</span>
857 <span class="card-value trend-up">+18.5%</span>
858 </div>
859 <div class="card-row">
860 <span class="card-label">Target Progress:</span>
861 <div class="progress-bar">
862 <div class="progress-fill" style="width: 85%"></div>
863 </div>
864 </div>
865 </div>
866 </div>
867
868 <div class="data-card">
869 <div class="card-header">
870 <h3 class="card-title">Smart Fitness Watch</h3>
871 <span class="status-badge status-active">Active</span>
872 </div>
873 <div class="card-content">
874 <div class="card-row">
875 <span class="card-label">Category:</span>
876 <span class="card-value">Electronics</span>
877 </div>
878 <div class="card-row">
879 <span class="card-label">Sales:</span>
880 <span class="card-value">1,923</span>
881 </div>
882 <div class="card-row">
883 <span class="card-label">Revenue:</span>
884 <span class="card-value">$96,150</span>
885 </div>
886 <div class="card-row">
887 <span class="card-label">Growth:</span>
888 <span class="card-value trend-up">+12.3%</span>
889 </div>
890 <div class="card-row">
891 <span class="card-label">Target Progress:</span>
892 <div class="progress-bar">
893 <div class="progress-fill" style="width: 72%"></div>
894 </div>
895 </div>
896 </div>
897 </div>
898
899 <div class="data-card">
900 <div class="card-header">
901 <h3 class="card-title">Designer Backpack</h3>
902 <span class="status-badge status-pending">Pending</span>
903 </div>
904 <div class="card-content">
905 <div class="card-row">
906 <span class="card-label">Category:</span>
907 <span class="card-value">Clothing</span>
908 </div>
909 <div class="card-row">
910 <span class="card-label">Sales:</span>
911 <span class="card-value">1,547</span>
912 </div>
913 <div class="card-row">
914 <span class="card-label">Revenue:</span>
915 <span class="card-value">$77,350</span>
916 </div>
917 <div class="card-row">
918 <span class="card-label">Growth:</span>
919 <span class="card-value trend-down">-5.2%</span>
920 </div>
921 <div class="card-row">
922 <span class="card-label">Target Progress:</span>
923 <div class="progress-bar">
924 <div class="progress-fill" style="width: 58%"></div>
925 </div>
926 </div>
927 </div>
928 </div>
929
930 <div class="data-card">
931 <div class="card-header">
932 <h3 class="card-title">Organic Coffee Blend</h3>
933 <span class="status-badge status-active">Active</span>
934 </div>
935 <div class="card-content">
936 <div class="card-row">
937 <span class="card-label">Category:</span>
938 <span class="card-value">Home & Garden</span>
939 </div>
940 <div class="card-row">
941 <span class="card-label">Sales:</span>
942 <span class="card-value">3,245</span>
943 </div>
944 <div class="card-row">
945 <span class="card-label">Revenue:</span>
946 <span class="card-value">$64,900</span>
947 </div>
948 <div class="card-row">
949 <span class="card-label">Growth:</span>
950 <span class="card-value trend-up">+25.7%</span>
951 </div>
952 <div class="card-row">
953 <span class="card-label">Target Progress:</span>
954 <div class="progress-bar">
955 <div class="progress-fill" style="width: 92%"></div>
956 </div>
957 </div>
958 </div>
959 </div>
960 </div>
961 </div>
962 </section>
963 </div>
964
965 <!-- Screen reader announcements -->
966 <div class="sr-only" id="announcements" aria-live="polite" aria-atomic="true"></div>
967
968 <script>
969 document.addEventListener('DOMContentLoaded', function() {
970 console.log('📊 Responsive Data Dashboard Loaded');
971
972 // Data management
973 class DataManager {
974 constructor() {
975 this.data = [
976 { name: 'Wireless Headphones Pro', category: 'Electronics', sales: 2847, revenue: 142350, growth: 18.5, status: 'active', progress: 85 },
977 { name: 'Smart Fitness Watch', category: 'Electronics', sales: 1923, revenue: 96150, growth: 12.3, status: 'active', progress: 72 },
978 { name: 'Designer Backpack', category: 'Clothing', sales: 1547, revenue: 77350, growth: -5.2, status: 'pending', progress: 58 },
979 { name: 'Organic Coffee Blend', category: 'Home & Garden', sales: 3245, revenue: 64900, growth: 25.7, status: 'active', progress: 92 },
980 { name: 'Programming Guide 2024', category: 'Books', sales: 892, revenue: 26760, growth: 8.9, status: 'active', progress: 44 },
981 { name: 'Vintage Denim Jacket', category: 'Clothing', sales: 734, revenue: 51380, growth: -12.4, status: 'inactive', progress: 31 }
982 ];
983 this.filteredData = [...this.data];
984 this.currentSort = { column: 'sales', direction: 'desc' };
985 this.currentFilter = 'all';
986 this.currentView = 'table';
987 }
988
989 filterData(category) {
990 if (category === 'all') {
991 this.filteredData = [...this.data];
992 } else {
993 this.filteredData = this.data.filter(item =>
994 item.category.toLowerCase().includes(category.toLowerCase())
995 );
996 }
997 this.currentFilter = category;
998 this.sortData(this.currentSort.column, this.currentSort.direction);
999 }
1000
1001 searchData(query) {
1002 if (!query) {
1003 this.filterData(this.currentFilter);
1004 return;
1005 }
1006
1007 this.filteredData = this.data.filter(item =>
1008 item.name.toLowerCase().includes(query.toLowerCase()) ||
1009 item.category.toLowerCase().includes(query.toLowerCase())
1010 );
1011 this.sortData(this.currentSort.column, this.currentSort.direction);
1012 }
1013
1014 sortData(column, direction) {
1015 this.filteredData.sort((a, b) => {
1016 let aVal = a[column];
1017 let bVal = b[column];
1018
1019 if (typeof aVal === 'string') {
1020 aVal = aVal.toLowerCase();
1021 bVal = bVal.toLowerCase();
1022 }
1023
1024 if (direction === 'asc') {
1025 return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
1026 } else {
1027 return aVal > bVal ? -1 : aVal < bVal ? 1 : 0;
1028 }
1029 });
1030
1031 this.currentSort = { column, direction };
1032 }
1033 }
1034
1035 // View controller
1036 class ViewController {
1037 constructor(dataManager) {
1038 this.dataManager = dataManager;
1039 this.tableView = document.querySelector('.table-view');
1040 this.cardView = document.querySelector('.card-view');
1041 this.viewButtons = document.querySelectorAll('.view-btn');
1042 this.tableBody = document.querySelector('.responsive-table tbody');
1043 this.cardGrid = document.querySelector('.data-grid');
1044
1045 this.init();
1046 }
1047
1048 init() {
1049 this.viewButtons.forEach(btn => {
1050 btn.addEventListener('click', () => this.switchView(btn.dataset.view));
1051 });
1052
1053 this.renderTableView();
1054 this.renderCardView();
1055 }
1056
1057 switchView(view) {
1058 this.viewButtons.forEach(btn => btn.classList.remove('active'));
1059 document.querySelector(`[data-view="${view}"]`).classList.add('active');
1060
1061 this.tableView.classList.toggle('active', view === 'table');
1062 this.cardView.classList.toggle('active', view === 'cards');
1063
1064 this.dataManager.currentView = view;
1065 this.announceViewChange(view);
1066 }
1067
1068 renderTableView() {
1069 const tbody = this.tableBody;
1070 tbody.innerHTML = '';
1071
1072 this.dataManager.filteredData.forEach(item => {
1073 const row = document.createElement('tr');
1074 row.innerHTML = `1090`;
1091 tbody.appendChild(row);
1092 });
1093 }
1094
1095 renderCardView() {
1096 const grid = this.cardGrid;
1097 grid.innerHTML = '';
1098
1099 this.dataManager.filteredData.forEach(item => {
1100 const card = document.createElement('div');
1101 card.className = 'data-card';
1102 card.innerHTML = `1133`;
1134 grid.appendChild(card);
1135 });
1136 }
1137
1138 updateViews() {
1139 this.renderTableView();
1140 this.renderCardView();
1141 }
1142
1143 announceViewChange(view) {
1144 const announcer = document.getElementById('announcements');
1145 announcer.textContent = `Switched to ${view} view. Showing ${this.dataManager.filteredData.length} items.`;
1146 }
1147 }
1148
1149 // Table sorting
1150 class TableSorter {
1151 constructor(dataManager, viewController) {
1152 this.dataManager = dataManager;
1153 this.viewController = viewController;
1154 this.headers = document.querySelectorAll('.responsive-table th[data-sort]');
1155
1156 this.init();
1157 }
1158
1159 init() {
1160 this.headers.forEach(header => {
1161 header.addEventListener('click', () => this.sort(header));
1162 header.addEventListener('keydown', (e) => {
1163 if (e.key === 'Enter' || e.key === ' ') {
1164 e.preventDefault();
1165 this.sort(header);
1166 }
1167 });
1168 });
1169
1170 // Set initial sort
1171 this.updateSortIndicators('sales', 'desc');
1172 }
1173
1174 sort(header) {
1175 const column = header.dataset.sort;
1176 let direction = 'asc';
1177
1178 if (this.dataManager.currentSort.column === column) {
1179 direction = this.dataManager.currentSort.direction === 'asc' ? 'desc' : 'asc';
1180 }
1181
1182 this.dataManager.sortData(column, direction);
1183 this.viewController.updateViews();
1184 this.updateSortIndicators(column, direction);
1185 this.announceSortChange(header.textContent.trim(), direction);
1186 }
1187
1188 updateSortIndicators(column, direction) {
1189 this.headers.forEach(header => {
1190 const indicator = header.querySelector('.sort-indicator');
1191 const headerColumn = header.dataset.sort;
1192
1193 if (headerColumn === column) {
1194 header.setAttribute('aria-sort', direction === 'asc' ? 'ascending' : 'descending');
1195 indicator.textContent = direction === 'asc' ? '↑' : '↓';
1196 } else {
1197 header.setAttribute('aria-sort', 'none');
1198 indicator.textContent = '↕';
1199 }
1200 });
1201 }
1202
1203 announceSortChange(columnName, direction) {
1204 const announcer = document.getElementById('announcements');
1205 announcer.textContent = `Table sorted by ${columnName} in ${direction}ending order`;
1206 }
1207 }
1208
1209 // Filter controller
1210 class FilterController {
1211 constructor(dataManager, viewController) {
1212 this.dataManager = dataManager;
1213 this.viewController = viewController;
1214 this.filterTabs = document.querySelectorAll('.filter-tab');
1215 this.searchInput = document.querySelector('.search-input');
1216
1217 this.init();
1218 }
1219
1220 init() {
1221 this.filterTabs.forEach(tab => {
1222 tab.addEventListener('click', () => this.filter(tab));
1223 });
1224
1225 this.searchInput.addEventListener('input', (e) => {
1226 this.search(e.target.value);
1227 });
1228 }
1229
1230 filter(activeTab) {
1231 this.filterTabs.forEach(tab => tab.classList.remove('active'));
1232 activeTab.classList.add('active');
1233
1234 const filter = activeTab.dataset.filter;
1235 this.dataManager.filterData(filter);
1236 this.viewController.updateViews();
1237 this.announceFilterChange(activeTab.textContent, this.dataManager.filteredData.length);
1238 }
1239
1240 search(query) {
1241 this.dataManager.searchData(query);
1242 this.viewController.updateViews();
1243
1244 if (query) {
1245 this.announceSearchResults(query, this.dataManager.filteredData.length);
1246 }
1247 }
1248
1249 announceFilterChange(filterName, resultCount) {
1250 const announcer = document.getElementById('announcements');
1251 announcer.textContent = `Filtered to ${filterName}. Showing ${resultCount} items.`;
1252 }
1253
1254 announceSearchResults(query, resultCount) {
1255 const announcer = document.getElementById('announcements');
1256 announcer.textContent = `Search for "${query}" returned ${resultCount} results.`;
1257 }
1258 }
1259
1260 // Chart interactions
1261 class ChartController {
1262 constructor() {
1263 this.bars = document.querySelectorAll('.bar');
1264 this.init();
1265 }
1266
1267 init() {
1268 this.bars.forEach(bar => {
1269 bar.addEventListener('click', () => this.selectBar(bar));
1270 bar.addEventListener('keydown', (e) => {
1271 if (e.key === 'Enter' || e.key === ' ') {
1272 e.preventDefault();
1273 this.selectBar(bar);
1274 }
1275 });
1276 });
1277 }
1278
1279 selectBar(selectedBar) {
1280 const value = selectedBar.dataset.value;
1281 const index = Array.from(this.bars).indexOf(selectedBar);
1282 const months = ['July', 'August', 'September', 'October', 'November', 'December'];
1283
1284 console.log(`Selected ${months[index]}: ${value}`);
1285 this.announceSelection(months[index], value);
1286 }
1287
1288 announceSelection(month, value) {
1289 const announcer = document.getElementById('announcements');
1290 announcer.textContent = `Selected ${month} with sales of ${value}`;
1291 }
1292 }
1293
1294 // Loading state manager
1295 class LoadingManager {
1296 constructor() {
1297 this.overlay = document.getElementById('tableLoading');
1298 }
1299
1300 show() {
1301 this.overlay.classList.add('active');
1302 }
1303
1304 hide() {
1305 this.overlay.classList.remove('active');
1306 }
1307
1308 simulate() {
1309 this.show();
1310 setTimeout(() => {
1311 this.hide();
1312 }, 1000);
1313 }
1314 }
1315
1316 // Initialize all components
1317 const dataManager = new DataManager();
1318 const viewController = new ViewController(dataManager);
1319 const tableSorter = new TableSorter(dataManager, viewController);
1320 const filterController = new FilterController(dataManager, viewController);
1321 const chartController = new ChartController();
1322 const loadingManager = new LoadingManager();
1323
1324 // Simulate loading on filter changes
1325 document.querySelectorAll('.filter-tab').forEach(tab => {
1326 tab.addEventListener('click', () => {
1327 loadingManager.simulate();
1328 });
1329 });
1330
1331 // Animate progress bars on load
1332 setTimeout(() => {
1333 document.querySelectorAll('.progress-fill').forEach(fill => {
1334 const width = fill.style.width;
1335 fill.style.width = '0%';
1336 fill.style.transition = 'width 1s ease';
1337 setTimeout(() => {
1338 fill.style.width = width;
1339 }, 100);
1340 });
1341 }, 500);
1342
1343 // Update summary cards periodically
1344 setInterval(() => {
1345 const revenue = document.getElementById('total-revenue');
1346 const orders = document.getElementById('total-orders');
1347
1348 if (Math.random() > 0.7) {
1349 const currentRevenue = parseInt(revenue.textContent.replace(/[$,]/g, ''));
1350 const newRevenue = currentRevenue + Math.floor(Math.random() * 1000);
1351 revenue.textContent = '$' + newRevenue.toLocaleString();
1352
1353 const currentOrders = parseInt(orders.textContent.replace(/,/g, ''));
1354 const newOrders = currentOrders + Math.floor(Math.random() * 10);
1355 orders.textContent = newOrders.toLocaleString();
1356
1357 console.log('📊 Dashboard data updated');
1358 }
1359 }, 10000);
1360
1361 console.log('🚀 Responsive data dashboard initialized');
1362 console.log('📱 Try switching between table and card views');
1363 console.log('🔍 Test filtering and searching functionality');
1364 });
1365 </script>
1366</body>
1367</html>This comprehensive responsive data dashboard demonstrates modern approaches to displaying complex data across all devices:
Key Features Implemented:
JavaScript Architecture:
The implementation follows modern web standards, ensuring data accessibility and usability across all devices and user contexts.
In the next lesson, we'll explore progressive enhancement principles, learning how to build robust responsive experiences that work across all browsers and devices, with graceful degradation and feature detection.
Your progress is saved in this browser, on this device only.