Skip to content

Commit 5532d60

Browse files
Improve focus handling and accessibility of dialogs (#122)
1 parent d3ab862 commit 5532d60

File tree

4 files changed

+67
-61
lines changed

4 files changed

+67
-61
lines changed
Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,29 @@ import type { CollectionEntry } from "astro:content";
88
type Speaker = CollectionEntry<"speakers">["data"];
99
1010
interface Props {
11-
speakers: Speaker[];
11+
speaker: Speaker;
1212
showSessions?: boolean;
1313
}
1414
15-
const { speakers, showSessions = true } = Astro.props;
15+
const { speaker, showSessions = true } = Astro.props;
1616
---
1717

1818
{
19-
speakers.map((speaker) => (
19+
(
2020
<dialog
2121
id={`speaker-${speaker.id}`}
2222
closedby="any"
2323
aria-labelledby={`speaker-${speaker.id}-name`}
2424
>
2525
<header>
26-
<form method="dialog">
27-
<button type="submit" class="close-button" aria-label="Close modal">
28-
29-
</button>
30-
</form>
26+
<button
27+
autofocus
28+
commandfor={`speaker-${speaker.id}`}
29+
command="close"
30+
aria-label="Close speaker details"
31+
>
32+
33+
</button>
3134
<div>
3235
<PersonCard
3336
name={speaker.fullName}
@@ -75,7 +78,7 @@ const { speakers, showSessions = true } = Astro.props;
7578
)}
7679
</div>
7780
</dialog>
78-
))
81+
)
7982
}
8083

8184
<style>
@@ -165,25 +168,22 @@ const { speakers, showSessions = true } = Astro.props;
165168
justify-content: center;
166169
}
167170

168-
.close-button {
171+
button {
169172
all: unset;
170-
cursor: pointer;
171173
font-size: var(--text-xl);
172-
line-height: 1;
174+
padding-inline: 0.5rem;
173175
color: var(--color-text-light);
174176
transition: color 0.2s ease;
175-
flex-shrink: 0;
176-
align-self: flex-start;
177177
position: absolute;
178178
top: var(--space-6);
179179
right: var(--space-6);
180180
}
181181

182-
.close-button:hover {
182+
button:hover {
183183
color: var(--color-text);
184184
}
185185

186-
.close-button:focus-visible {
186+
button:focus-visible {
187187
outline: 2px solid var(--color-purple);
188188
outline-offset: 2px;
189189
border-radius: var(--radius);
@@ -266,7 +266,7 @@ const { speakers, showSessions = true } = Astro.props;
266266
padding: var(--space-4);
267267
}
268268

269-
.close-button {
269+
button {
270270
top: var(--space-4);
271271
right: var(--space-4);
272272
}

src/pages/home-page/sections/Tickets.astro

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
import Button from "@components/Button.astro";
33
import PersonCard from "@components/PersonCard.astro";
4-
import SpeakersDetailDialog from "@components/SpeakersDetailDialog.astro";
4+
import SpeakerDetailModal from "@components/SpeakerDetailModal.astro";
55
import { getCollection } from "astro:content";
66
77
const speakers = (await getCollection("speakers")).map((entry) => entry.data);
@@ -12,26 +12,29 @@ const displayedSpeakers = speakers.slice(0, 5);
1212
<section aria-labelledby="tickets">
1313
<h2 id="tickets">Tickets</h2>
1414
<div>
15-
<div>
15+
<ul>
1616
{
1717
displayedSpeakers.map((speaker) => (
18-
<button
19-
type="button"
20-
commandfor={`speaker-${speaker.id}`}
21-
command="show-modal"
22-
data-speaker-target={`speaker-${speaker.id}`}
23-
aria-label={`View details for ${speaker.fullName}`}
24-
>
25-
<PersonCard
26-
name={speaker.fullName}
27-
image={speaker.profilePicture}
28-
showMeta={false}
29-
sizes="(max-width: 768px) 80px, 120px"
30-
/>
31-
</button>
18+
<li>
19+
<SpeakerDetailModal speaker={speaker} />
20+
<button
21+
type="button"
22+
commandfor={`speaker-${speaker.id}`}
23+
command="show-modal"
24+
data-speaker-target={`speaker-${speaker.id}`}
25+
aria-label={`View details for ${speaker.fullName}`}
26+
>
27+
<PersonCard
28+
name={speaker.fullName}
29+
image={speaker.profilePicture}
30+
showMeta={false}
31+
sizes="(max-width: 768px) 80px, 120px"
32+
/>
33+
</button>
34+
</li>
3235
))
3336
}
34-
</div>
37+
</ul>
3538

3639
<div>
3740
<p>
@@ -48,8 +51,6 @@ const displayedSpeakers = speakers.slice(0, 5);
4851
</div>
4952
</section>
5053

51-
<SpeakersDetailDialog speakers={displayedSpeakers} />
52-
5354
<style>
5455
section {
5556
display: flex;
@@ -74,14 +75,24 @@ const displayedSpeakers = speakers.slice(0, 5);
7475
margin-inline: auto;
7576
}
7677

77-
section > div > div:first-child {
78+
section > div > ul {
7879
display: flex;
7980
justify-content: center;
8081
align-items: center;
8182
max-width: 700px;
83+
padding: 0;
84+
margin: 0;
8285
}
8386

84-
section > div > div:first-child > button {
87+
ul > li {
88+
list-style: none;
89+
}
90+
91+
ul > li:not(:first-child) {
92+
margin-left: -24px;
93+
}
94+
95+
ul > li > button {
8596
display: block;
8697
all: unset;
8798
cursor: pointer;
@@ -93,64 +104,60 @@ const displayedSpeakers = speakers.slice(0, 5);
93104
transition: transform 0.2s ease;
94105
}
95106

96-
section > div > div:first-child > button:hover {
107+
ul > li > button:hover {
97108
--lift: -10px;
98109
z-index: 6;
99110
}
100111

101-
section > div > div:first-child > button:focus-visible {
112+
ul > li > button:focus-visible {
102113
--lift: -10px;
103114
z-index: 6;
104115
outline: 2px solid var(--color-purple);
105116
outline-offset: 4px;
106117
border-radius: var(--radius);
107118
}
108119

109-
section > div > div:first-child > button + button {
110-
margin-left: -24px;
111-
}
112-
113120
@media (min-width: 768px) {
114-
section > div > div:first-child > button {
121+
ul > li > button {
115122
width: 120px;
116123
}
117124

118-
section > div > div:first-child > button + button {
125+
ul > li:not(:first-child) {
119126
margin-left: -36px;
120127
}
121128
}
122129

123-
section > div > div:first-child > button:nth-child(1) {
130+
ul > li:nth-child(1) {
124131
--base-y: 8px;
125132
--tilt: -8deg;
126133
z-index: 1;
127134
}
128135

129-
section > div > div:first-child > button:nth-child(2) {
136+
ul > li:nth-child(2) {
130137
--base-y: 3px;
131138
--tilt: -4deg;
132139
z-index: 2;
133140
}
134141

135-
section > div > div:first-child > button:nth-child(3) {
142+
section > div > ul > li:nth-child(3) {
136143
--base-y: 0px;
137144
--tilt: 0deg;
138145
z-index: 3;
139146
}
140147

141-
section > div > div:first-child > button:nth-child(4) {
148+
section > div > ul > li:nth-child(4) {
142149
--base-y: 3px;
143150
--tilt: 4deg;
144151
z-index: 4;
145152
}
146153

147-
section > div > div:first-child > button:nth-child(5) {
154+
section > div > ul > li:nth-child(5) {
148155
--base-y: 8px;
149156
--tilt: 8deg;
150157
z-index: 5;
151158
}
152159

153-
section > div > div:nth-child(2) {
160+
section > div > div:last-child {
154161
text-align: center;
155162
display: flex;
156163
flex-direction: column;
@@ -182,12 +189,12 @@ const displayedSpeakers = speakers.slice(0, 5);
182189
}
183190

184191
@media (prefers-reduced-motion: reduce) {
185-
section > div > div:first-child > button {
192+
section > div > ul > li > button {
186193
transition: none;
187194
}
188195

189-
section > div > div:first-child > button:hover,
190-
section > div > div:first-child > button:focus-visible {
196+
section > div > ul > li > button:hover,
197+
section > div > ul > li > button:focus-visible {
191198
--lift: 0;
192199
}
193200
}

src/pages/sessions.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
---
22
import PersonCard from "@components/PersonCard.astro";
3-
import SpeakersDetailDialog from "@components/SpeakersDetailDialog.astro";
3+
import SpeakerDetailModal from "@components/SpeakerDetailModal.astro";
44
import Layout from "@layouts/Layout.astro";
55
import { getCollection } from "astro:content";
6+
import Speakers from "./speakers.astro";
67
78
const speakers = await getCollection("speakers");
89
const speakersById = new Map(speakers.map((entry) => [entry.data.id, entry.data]));
@@ -40,6 +41,7 @@ const sessionSpeakers = speakers
4041
{session.speakers.map(
4142
(speaker) =>
4243
speaker.profilePicture && (
44+
<SpeakerDetailModal speaker={sessionSpeakers.find((s) => s.id === speaker.id)!} showSessions={false} />
4345
<button
4446
type="button"
4547
commandfor={`speaker-${speaker.id}`}
@@ -96,8 +98,6 @@ const sessionSpeakers = speakers
9698
)
9799
}
98100

99-
<SpeakersDetailDialog speakers={sessionSpeakers} showSessions={false} />
100-
101101
<style>
102102
h1 {
103103
text-align: center;

src/pages/speakers.astro

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
import PersonCard from "@components/PersonCard.astro";
3-
import SpeakersDetailDialog from "@components/SpeakersDetailDialog.astro";
3+
import SpeakerDetailModal from "@components/SpeakerDetailModal.astro";
44
import Layout from "@layouts/Layout.astro";
55
import { getCollection, type CollectionEntry } from "astro:content";
66
@@ -17,6 +17,7 @@ const speakers = (await getCollection("speakers")).map((entry) => entry.data) as
1717
<section class="speakers-grid">
1818
{
1919
speakers.map((speaker) => (
20+
<SpeakerDetailModal speaker={speaker} />
2021
<button
2122
type="button"
2223
commandfor={`speaker-${speaker.id}`}
@@ -35,8 +36,6 @@ const speakers = (await getCollection("speakers")).map((entry) => entry.data) as
3536
}
3637
</section>
3738

38-
<SpeakersDetailDialog speakers={speakers} />
39-
4039
<style>
4140
h1 {
4241
text-align: center;

0 commit comments

Comments
 (0)