Accessibility bug: Every post in the feed is a nested link #6305
Description
Steps to Reproduce
- Go to https://bsky.app/ and watch the feed
- To discover this issue quickly, open any screen reader, but you can also see this in the dev tools
- Every post is
<div role="link" tabindex="0">
- Every post contains many links (e.g. profile link, content link, etc.)
Nesting interactive elements is not legal in HTML5 (and 4).
Interactive content is defined in the HTML5 spec. And every single interactive element lists that it may not contain interactive content. Giving an element the role link
and tabindex="0"
indicates that you want to make this an interactive element.
What happens is that assistive technology either flat-out doesn't work, or, in my case using NVDA
the entire post is read out as a link.
This is what happens when I tab into the feed:
Discover tab selected 1 of 4
Following tab selected 2 of 4
Mutuals tab selected 3 of 4
Game Dev tab selected 4 of 4
This is great, makes sense. These are tab
s, so I absolutely expect to be able to use my arrow keys, but I'll open a new issue about that. Then we get to the actual post.
Expo's avatar visited link
View profile visited link
View profile visited link
·
November 13, 2024 at 8:18 PM visited link
😏 This is "Linearlite" built with React Native. Blog post from @schickling.dev link
will drop next Thursday during our Launch Party:
expo.dev/launch-party visited link
Launch Party — Expo visited link
Post by codewithbeto.bsky.social link
Reply (1 reply) button
clickable Repost or quote post menu button collapsed subMenu
Like (1 like) button
clickable Open post options menu
link
Do you see that final link
callout? that's because everything before it is nested inside. So I hear the link end 8 times before it actually ends.
There are several issues here, but the only one this issue should focus on is the fact that the entire post is the link. This method is described in the article about accessible Cards by Heydon Pickering which I recommend you read. The most important parts are:
This is not without its problems. Now, all of the card contents form the label of the link. So when a screen reader encounters it, the announcement might be something link "Card design woes, ten common pitfalls to avoid when designing card components, by Heydon Pickering, link".
It's not disastrous in terms of comprehension, but verbose — especially if the card evolves to contain more content — especially when it's interactive content. It's also quite unexpected to find a block element like an
<h2>
inside an inline element like an<a>
, even though it's technically permissible in HTML5.If I were to start adding interactivity, like linking the author name, things start to get even more confusing. Some screen readers only read out the first element of a 'block link', reducing verbosity but making it easy to miss the additional functionality. You just wouldn't expect there to be another link inside the first link and a blind user might Tab away none the wiser.
Wrapping the entire card in a link: permissible but not advisable. Nesting interactive content inside of interactive content, don't do that. Ever.
- No interactive content is nested inside other interactive content
- Optional: each post is a labelled region, preferably
article
Attachments
Full HTML of a post without making any changes
<div role="link" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" data-testid="feedItem-by-expo.dev">
<div class="css-175oi2r r-1loqt21 r-1hfyk0a r-ry3cjt" style="border-color: rgb(46, 64, 82); padding-bottom: 8px; border-top-width: 0px;">
<div class="css-175oi2r r-1p0dtai r-1d2f490 r-633pao r-u8s1d r-zchlnj r-ipm5af r-1eb9tut" style="background-color: rgb(30, 41, 54); opacity: 0;"></div>
<div class="css-175oi2r" style="flex-direction: row; gap: 10px; padding-left: 8px;">
<div class="css-175oi2r" style="width: 42px;"></div>
<div class="css-175oi2r" style="padding-top: 12px; flex-shrink: 1;"></div>
</div>
<div class="css-175oi2r r-18u37iz r-uaa2di r-1cvj4g8">
<div class="css-175oi2r r-1m04atk r-bnwqim r-c97pre">
<div class="css-175oi2r" style="flex-shrink: 1;">
<div class="css-175oi2r" style="flex-shrink: 1;">
<a href="/profile/expo.dev" aria-label="Expo's avatar" aria-pressed="false" role="link" data-no-underline="1" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" style="flex-direction: row; align-items: center; justify-content: flex-start;">
<div class="css-175oi2r" style="width: 42px; height: 42px;">
<div class="css-175oi2r r-1mlwlqe r-1udh08x r-417010" data-testid="userAvatarImage" style="width: 42px; height: 42px; border-radius: 21px; background-color: rgb(30, 41, 54);">
<div class="css-175oi2r r-1niwhzg r-vvn4in r-u6sd8q r-1p0dtai r-1pi2tsx r-1d2f490 r-u8s1d r-zchlnj r-ipm5af r-13qz1uu r-1wyyakw r-4gszlv" style="background-image: url("https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:xinso5nzzhsfncnr5rtsoqba/bafkreig4lflhzktie3gl3jee6gdgxwhvv2jc7h4ewmwjmhaoehw5nonxwm@jpeg");"></div>
<img alt="" draggable="false" src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:xinso5nzzhsfncnr5rtsoqba/bafkreig4lflhzktie3gl3jee6gdgxwhvv2jc7h4ewmwjmhaoehw5nonxwm@jpeg" class="css-9pa8cd">
</div>
<div class="css-175oi2r" style="position: absolute; inset: 0px; border-width: 1px; border-color: rgb(74, 97, 121); opacity: 0.6; pointer-events: none; border-radius: 21px;"></div>
</div>
</a>
</div>
</div>
</div>
<div class="css-175oi2r r-13awgt0 r-bnwqim r-417010">
<div class="css-175oi2r" style="flex: 1 1 0%; flex-direction: row; align-items: center; padding-bottom: 2px; gap: 4px; z-index: 10;">
<div class="css-175oi2r" style="flex-shrink: 1;">
<div class="css-175oi2r" style="flex-shrink: 1;">
<div dir="auto" class="css-146c3p1 r-dnmrzs r-1udh08x r-1udbk01 r-3s2u2q r-1iln25a" style="font-size: 12.25px; letter-spacing: 0px; color: rgb(241, 243, 245); flex-shrink: 1; line-height: 12.25px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><a href="/profile/expo.dev" aria-label="View profile" role="link" data-no-underline="1" class="css-1jxf684 r-1loqt21" style="font-size: 12.25px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 12.25px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); font-weight: 600; line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">Expo</span></a><a href="/profile/expo.dev" aria-label="View profile" role="link" data-no-underline="1" class="css-1jxf684 r-1loqt21" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"> @expo.dev</span></a></div>
</div>
</div>
<div dir="auto" class="css-146c3p1" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 14px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">·</div>
<a href="/profile/expo.dev/post/3lau2tfa3bk2m" dir="auto" aria-label="November 13, 2024 at 8:18 PM" role="link" data-tooltip="November 13, 2024 at 8:18 PM" data-no-underline="1" class="css-146c3p1 r-1loqt21" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 18px; white-space: nowrap; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">20m</a>
</div>
<div class="css-175oi2r" data-testid="contentHider-post">
<div class="css-175oi2r r-1awozwy r-18u37iz r-1w6e6rj r-1udh08x r-l4nmg1">
<div dir="auto" data-word-wrap="1" class="css-146c3p1" data-testid="postText" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; flex: 1 1 0%; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">
<span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; flex: 1 1 0%; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">😏 This is "Linearlite" built with React Native. Blog post from </span>
<div class="css-175oi2r r-xoduu5" style="flex-shrink: 1;">
<div class="css-175oi2r r-xoduu5" style="flex-shrink: 1;"><a href="/profile/did:plc:53or656ypxoeh73lwshokzdm" role="link" data-no-underline="1" class="css-1jxf684 r-1loqt21" style="font-size: 14px; letter-spacing: 0px; color: rgb(32, 139, 254); line-height: 18px; pointer-events: auto; flex: 1 1 0%; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">@schickling.dev</a></div>
</div>
<span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; flex: 1 1 0%; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"> will drop next Thursday during our Launch Party: </span><a href="https://expo.dev/launch-party" rel="noopener noreferrer" target="_blank" role="link" data-no-underline="1" class="css-1jxf684 r-1loqt21" style="font-size: 14px; letter-spacing: 0px; color: rgb(32, 139, 254); line-height: 18px; pointer-events: auto; flex: 1 1 0%; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">expo.dev/launch-party</a>
</div>
</div>
<div class="css-175oi2r" style="padding-bottom: 4px;">
<div class="css-175oi2r">
<div class="css-175oi2r">
<a href="https://expo.dev/launch-party" rel="noopener noreferrer" target="_blank" aria-label="Launch Party — Expo" aria-pressed="false" role="link" data-no-underline="1" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" style="flex-direction: row; align-items: center; justify-content: flex-start;">
<div class="css-175oi2r" style="transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1); transition-duration: 100ms; flex-direction: column; border-radius: 12px; overflow: hidden; width: 100%; border-width: 1px; margin-top: 8px; border-color: rgb(46, 64, 82);">
<div data-expoimage="true" class="css-175oi2r" style="overflow: hidden; aspect-ratio: 1.91 / 1;">
<div><img src="https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:xinso5nzzhsfncnr5rtsoqba/bafkreigylq6qwthig7tgebw5e2p36ysm4usbbne4ee24xfjbeje5sbizxe@jpeg" fetchpriority="auto" style="object-position: left 50% top 50%; width: 100%; height: 100%; position: absolute; left: 0px; top: 0px; object-fit: cover; transition-duration: 0ms; transition-timing-function: linear;"></div>
</div>
<div class="css-175oi2r" style="flex: 1 1 0%; padding-top: 8px; gap: 3px; border-top-width: 1px; border-color: rgb(46, 64, 82);">
<div class="css-175oi2r" style="gap: 3px; padding-bottom: 4px; padding-left: 12px; padding-right: 12px;">
<div dir="auto" class="css-146c3p1 r-8akbws r-krxsd3 r-dnmrzs r-1udh08x r-1udbk01" style="-webkit-line-clamp: 3; font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); font-weight: 600; line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">Launch Party — Expo</div>
<div dir="auto" class="css-146c3p1 r-8akbws r-krxsd3 r-dnmrzs r-1udh08x r-1udbk01" style="-webkit-line-clamp: 2; font-size: 12.25px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 16px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">A week of feature announcements and community events.</div>
</div>
<div class="css-175oi2r" style="padding-left: 12px; padding-right: 12px;">
<div class="css-175oi2r" style="width: 100%; border-top-width: 1px; border-color: rgb(46, 64, 82);"></div>
<div class="css-175oi2r" style="flex-direction: row; align-items: center; gap: 2px; padding-bottom: 8px; padding-top: 6px;">
<svg fill="none" viewBox="0 0 24 24" width="12" height="12" style="transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1); transition-duration: 100ms; color: rgb(91, 119, 149);">
<path fill="hsl(211, 24%, 47.2%)" fill-rule="evenodd" clip-rule="evenodd" d="M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.953 3.81a1 1 0 0 1-.726.727l-3.444.866-.772 1.533a1 1 0 0 1-1.493.35L4.4 9.493Zm.883-1.84L7.756 9.51l.44-.874a1 1 0 0 1 .649-.52l3.306-.832.807-3.227a7.993 7.993 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.43.162a1 1 0 0 1 .77-.29l1.89.121a1 1 0 0 1 .494.168l2.869 1.928a1 1 0 0 1 .336 1.277l-.973 1.946a1 1 0 0 1-.894.553h-2.92a1 1 0 0 1-.831-.445L9.225 14.5a1 1 0 0 1 .126-1.262l1.08-1.076Zm.915 1.913.177-.177 1.171.074 1.914 1.286-.303.607h-1.766l-1.194-1.79Z"></path>
</svg>
<div dir="auto" class="css-146c3p1 r-dnmrzs r-1udh08x r-1udbk01 r-3s2u2q r-1iln25a" style="font-size: 10.5px; letter-spacing: 0px; color: rgb(174, 187, 201); transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1); transition-duration: 100ms; line-height: 14px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">expo.dev</div>
</div>
</div>
</div>
</div>
</a>
</div>
<div class="css-175oi2r">
<div class="css-175oi2r" style="border-radius: 12px; padding: 12px; margin-top: 8px; border-width: 1px; border-color: rgb(46, 64, 82);">
<div class="css-175oi2r r-1p0dtai r-1d2f490 r-633pao r-u8s1d r-zchlnj r-ipm5af r-1eb9tut" style="background-color: rgb(30, 41, 54); opacity: 0;"></div>
<div aria-label="Post by codewithbeto.bsky.social" role="link" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73">
<div class="css-175oi2r r-633pao">
<div class="css-175oi2r" style="flex: 1 1 0%; flex-direction: row; align-items: center; padding-bottom: 2px; gap: 4px; z-index: 10;">
<div class="css-175oi2r" style="align-self: center; margin-right: 2px;">
<div class="css-175oi2r" style="flex-shrink: 1;">
<div class="css-175oi2r" style="flex-shrink: 1;">
<a href="/profile/codewithbeto.bsky.social" aria-label="Beto's avatar" aria-pressed="false" role="link" data-no-underline="1" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" style="flex-direction: row; align-items: center; justify-content: flex-start;">
<div class="css-175oi2r" style="width: 16px; height: 16px;">
<div class="css-175oi2r r-1mlwlqe r-1udh08x r-417010" data-testid="userAvatarImage" style="width: 16px; height: 16px; border-radius: 8px; background-color: rgb(30, 41, 54);">
<div class="css-175oi2r r-1niwhzg r-vvn4in r-u6sd8q r-1p0dtai r-1pi2tsx r-1d2f490 r-u8s1d r-zchlnj r-ipm5af r-13qz1uu r-1wyyakw r-4gszlv" style="background-image: url("https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:rohhw4gvhh4dwgnokjljs3yj/bafkreihkpp26wyhqthhqsvzpsvzeplgjtmlqmhcz57yk44z4cod2ga6nwe@jpeg");"></div>
<img alt="" draggable="false" src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:rohhw4gvhh4dwgnokjljs3yj/bafkreihkpp26wyhqthhqsvzpsvzeplgjtmlqmhcz57yk44z4cod2ga6nwe@jpeg" class="css-9pa8cd">
</div>
<div class="css-175oi2r" style="position: absolute; inset: 0px; border-width: 1px; border-color: rgb(74, 97, 121); opacity: 0.6; pointer-events: none; border-radius: 8px;"></div>
</div>
</a>
</div>
</div>
</div>
<div class="css-175oi2r" style="flex-shrink: 1;">
<div class="css-175oi2r" style="flex-shrink: 1;">
<div dir="auto" class="css-146c3p1 r-dnmrzs r-1udh08x r-1udbk01 r-3s2u2q r-1iln25a" style="font-size: 12.25px; letter-spacing: 0px; color: rgb(241, 243, 245); flex-shrink: 1; line-height: 12.25px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><a href="/profile/codewithbeto.bsky.social" aria-label="View profile" role="link" data-no-underline="1" class="css-1jxf684 r-1loqt21" style="font-size: 12.25px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 12.25px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); font-weight: 600; line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">Beto</span></a><a href="/profile/codewithbeto.bsky.social" aria-label="View profile" role="link" data-no-underline="1" class="css-1jxf684 r-1loqt21" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"> @codewithbeto.bsky.social</span></a></div>
</div>
</div>
<div dir="auto" class="css-146c3p1" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 14px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">·</div>
<a href="/profile/codewithbeto.bsky.social/post/3lato2jya4c27" dir="auto" aria-label="November 13, 2024 at 4:29 PM" role="link" data-tooltip="November 13, 2024 at 4:29 PM" data-no-underline="1" class="css-146c3p1 r-1loqt21" style="font-size: 14px; letter-spacing: 0px; color: rgb(174, 187, 201); line-height: 18px; white-space: nowrap; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">4h</a>
</div>
</div>
<div dir="auto" data-word-wrap="1" class="css-146c3p1 r-8akbws r-krxsd3 r-dnmrzs r-1udh08x r-1udbk01" style="-webkit-line-clamp: 20; font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">I’m super excited to give you a sneak peek of our upcoming local-first app! We'll be showcasing it on November 21 at the </span><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">@expo.dev</span><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"> launch party! 🚀 </span>expo.dev/launch-party<span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">
Join us to get a taste of what the future of local-first </span><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">@expo.dev</span><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"> apps powered by </span><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">@livestore.dev</span><span class="css-1jxf684" style="font-size: 14px; letter-spacing: 0px; color: rgb(241, 243, 245); line-height: 18px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;"> will look like!
See you there!</span>
</div>
<div class="css-175oi2r">
<div class="css-175oi2r" style="width: 100%; aspect-ratio: 1.33333 / 1; background-color: rgb(0, 0, 0); position: relative; border-radius: 12px; margin-top: 4px;">
<div style="display: flex; flex: 1 1 0%; cursor: default;">
<div class="css-175oi2r" style="flex: 1 1 0%; flex-direction: row;">
<div class="css-175oi2r" style="flex: 1 1 0%; border-radius: 12px; overflow: hidden;">
<div style="height: 100%; width: 100%;">
<figure style="margin: 0px; position: absolute; inset: 0px;">
<video poster="https://video.bsky.app/watch/did%3Aplc%3Arohhw4gvhh4dwgnokjljs3yj/bafkreiafupjl33rpiw6twbnoc2ct3ywlqrlcek4o6xdfoxkjm7rx6m37zq/thumbnail.jpg" playsinline="" preload="none" style="width: 100%; height: 100%; object-fit: contain;" src="blob:https://bsky.app/8239990c-3526-4b8a-88f2-86351087bd65"></video>
</figure>
<div style="position: absolute; inset: 0px; overflow: hidden; display: flex; flex-direction: column;">
<button role="button" tabindex="0" class="css-175oi2r r-1otgn73" type="button" style="flex: 1 1 0%; cursor: pointer;"></button>
<div class="css-175oi2r r-633pao" style="background-color: rgba(0, 0, 0, 0.5); border-radius: 6px; padding: 3px 6px; left: 6px; bottom: 6px; min-height: 21px; position: absolute; justify-content: center;">
<div dir="auto" class="css-146c3p1" style="font-size: 10.5px; letter-spacing: 0px; color: rgb(241, 243, 245); font-variant: no-contextual tabular-nums; font-weight: 600; line-height: 13px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";">0:43</div>
</div>
<div class="css-175oi2r" style="flex-shrink: 0; width: 100%; padding-left: 4px; padding-right: 4px; background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.7)); opacity: 0; transition: opacity 0.2s ease-in-out;">
<div class="css-175oi2r" data-testid="scrubber" style="height: 18px; width: 100%; flex-shrink: 0; padding-left: 4px; padding-right: 4px;">
<div style="flex: 1 1 0%; display: flex; align-items: center; position: relative; cursor: grab; padding: 4px 0px;">
<div class="css-175oi2r" style="width: 100%; border-radius: 999px; overflow: hidden; background-color: rgba(255, 255, 255, 0.4); height: 3px; transition: height 0.1s;">
<div class="css-175oi2r" style="height: 100%; background-color: rgb(241, 243, 245); width: 28.3669%;"></div>
</div>
<div aria-label="Seek slider" role="slider" aria-valuemax="60.07" aria-valuemin="0" aria-valuenow="17.04" aria-valuetext="0:17 of 1:00" tabindex="0" style="position: absolute; height: 16px; width: 16px; border-radius: 8px; pointer-events: none; left: calc(28.3669% - 8px);">
<div class="css-175oi2r" style="width: 100%; height: 100%; border-radius: 999px; background-color: rgb(241, 243, 245); transform: scale(0);"></div>
</div>
</div>
</div>
<div class="css-175oi2r" style="flex: 1 1 0%; padding-left: 4px; padding-right: 4px; padding-bottom: 8px; gap: 8px; flex-direction: row; align-items: center;">
<button role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" type="button" style="padding: 4px; border-radius: 999px; transition: background-color 0.1s;">
<svg fill="none" width="20" viewBox="0 0 24 24" height="20">
<path fill="hsl(211, 20%, 95.3%)" fill-rule="evenodd" clip-rule="evenodd" d="M4 4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V4ZM14 4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1V4Z"></path>
</svg>
</button>
<div class="css-175oi2r" style="flex: 1 1 0%;"></div>
<div dir="auto" class="css-146c3p1" style="font-size: 12.25px; letter-spacing: 0px; color: rgb(241, 243, 245); padding-left: 4px; padding-right: 4px; font-variant: no-contextual tabular-nums; line-height: 12.25px; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";">0:17 / 1:00</div>
<div class="css-175oi2r" style="position: relative;">
<button role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" type="button" style="padding: 4px; border-radius: 999px; transition: background-color 0.1s;">
<svg fill="none" width="20" viewBox="0 0 24 24" height="20">
<path fill="hsl(211, 20%, 95.3%)" fill-rule="evenodd" clip-rule="evenodd" d="M20.707 3.293a1 1 0 0 1 0 1.414l-16 16a1 1 0 0 1-1.414-1.414l2.616-2.616A1.998 1.998 0 0 1 5 15V9a2 2 0 0 1 2-2h2.697l5.748-3.832A1 1 0 0 1 17 4v1.586l2.293-2.293a1 1 0 0 1 1.414 0ZM15 7.586 7.586 15H7V9h2.697a2 2 0 0 0 1.11-.336L15 5.87v1.717Zm2 3.657-2 2v4.888l-2.933-1.955-1.442 1.442 4.82 3.214A1 1 0 0 0 17 20v-8.757Z"></path>
</svg>
</button>
</div>
<button role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" type="button" style="padding: 4px; border-radius: 999px; transition: background-color 0.1s;">
<svg fill="none" width="20" viewBox="0 0 24 24" height="20">
<path fill="hsl(211, 20%, 95.3%)" fill-rule="evenodd" clip-rule="evenodd" d="M14 5a1 1 0 1 1 0-2h6a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V6.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L17.586 5H14ZM4 13a1 1 0 0 1 1 1v3.586l4.293-4.293a1 1 0 0 1 1.414 1.414L6.414 19H10a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1Z"></path>
</svg>
</button>
</div>
</div>
</div>
<div class="css-175oi2r" style="position: absolute; inset: 0px; border-radius: 12px; border-width: 1px; border-color: rgb(74, 97, 121); opacity: 0.6; pointer-events: none;"></div>
</div>
</div>
<div style="top: calc(50% - 50vh); height: 100vh; position: absolute; left: 50%; width: 1px; pointer-events: none;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="css-175oi2r" style="flex-direction: row; justify-content: space-between; align-items: center;">
<div class="css-175oi2r" style="flex: 1 1 0%; align-items: flex-start; margin-left: -6px;">
<button aria-label="Reply (1 reply)" role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" data-testid="replyBtn" type="button" style="gap: 4px; border-radius: 999px; flex-direction: row; justify-content: center; align-items: center; overflow: hidden; padding: 5px;">
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(120, 142, 165); pointer-events: none;">
<path fill="hsl(211, 20%, 56%)" fill-rule="evenodd" clip-rule="evenodd" d="M2.002 6a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2h-1a3 3 0 0 1-3-3V6Zm3-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h7a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-14Z"></path>
</svg>
<div dir="auto" class="css-146c3p1" style="color: rgb(120, 142, 165); font-size: 13.125px; letter-spacing: 0px; font-weight: 400; user-select: none; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">1</div>
</button>
</div>
<div class="css-175oi2r" style="flex: 1 1 0%; align-items: flex-start;">
<div class="css-175oi2r">
<button aria-expanded="false" aria-haspopup="menu" aria-label="Repost or quote post" role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" id="radix-:ra:" type="button" style="border-radius: 999px;">
<div class="css-175oi2r" style="flex-direction: row; align-items: center; gap: 4px; padding: 5px;">
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(120, 142, 165);">
<path fill="hsl(211, 20%, 56%)" fill-rule="evenodd" clip-rule="evenodd" d="M17.957 2.293a1 1 0 1 0-1.414 1.414L17.836 5H6a3 3 0 0 0-3 3v3a1 1 0 1 0 2 0V8a1 1 0 0 1 1-1h11.836l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.47-2.47a1.75 1.75 0 0 0 0-2.474l-2.47-2.47ZM20 12a1 1 0 0 1 1 1v3a3 3 0 0 1-3 3H6.164l1.293 1.293a1 1 0 1 1-1.414 1.414l-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47a1 1 0 0 1 1.414 1.414L6.164 17H18a1 1 0 0 0 1-1v-3a1 1 0 0 1 1-1Z"></path>
</svg>
</div>
</button>
</div>
</div>
<div class="css-175oi2r" style="flex: 1 1 0%; align-items: flex-start;">
<button aria-label="Like (1 like)" role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" data-testid="likeBtn" type="button" style="gap: 4px; border-radius: 999px; flex-direction: row; justify-content: center; align-items: center; overflow: hidden; padding: 5px;">
<div class="css-175oi2r">
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(120, 142, 165); pointer-events: none;">
<path fill="hsl(211, 20%, 56%)" fill-rule="evenodd" clip-rule="evenodd" d="M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z"></path>
</svg>
<div class="css-175oi2r" style="position: absolute; background-color: rgb(236, 72, 153); top: 0px; left: 0px; width: 18px; height: 18px; z-index: -1; pointer-events: none; border-radius: 9px; opacity: 0;"></div>
<div class="css-175oi2r" style="position: absolute; background-color: rgb(22, 30, 39); top: 0px; left: 0px; width: 18px; height: 18px; z-index: -1; pointer-events: none; border-radius: 9px; opacity: 0;"></div>
</div>
<div class="css-175oi2r">
<div class="css-175oi2r">
<div dir="auto" class="css-146c3p1" data-testid="likeCount" style="color: rgb(120, 142, 165); font-size: 13.125px; letter-spacing: 0px; font-weight: 400; user-select: none; font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-variant: no-contextual;">1</div>
</div>
</div>
</button>
</div>
<div class="css-175oi2r" style="flex: 1 1 0%; align-items: flex-start;">
<div class="css-175oi2r">
<button aria-expanded="false" aria-haspopup="menu" aria-label="Open post options menu" role="button" tabindex="0" class="css-175oi2r r-1loqt21 r-1otgn73" id="radix-:rm:" data-testid="postDropdownBtn" type="button" style="padding: 5px; border-radius: 999px;">
<svg fill="none" viewBox="0 0 24 24" width="20" height="20" style="pointer-events: none;">
<path fill="hsl(211, 20%, 56%)" fill-rule="evenodd" clip-rule="evenodd" d="M2 12a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm16 0a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm-6-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Here is the cleaned up version. I have removed all attributes not relevant, and stripped content that was not relevant. Annotated it with a few comments and left some testid
in there so you can find it more easily.
<div role="link" tabindex="0" data-testid="feedItem-by-expo.dev">
<div>
<div></div>
<div>
<div></div>
<div></div>
</div>
<div>
<div>
<div>
<div>
<a href="/profile/expo.dev" aria-label="Expo's avatar" aria-pressed="false" role="link" tabindex="0">
<div>
<div data-testid="userAvatarImage">
<div></div>
<img alt="">
</div>
<div></div>
</div>
</a>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div><a href="/profile/expo.dev" aria-label="View profile" role="link">Expo</span></a><a href="/profile/expo.dev" aria-label="View profile" role="link"> @expo.dev</span></a></div>
</div>
</div>
<div>·</div>
<a href="/profile/expo.dev/post/3lau2tfa3bk2m" aria-label="November 13, 2024 at 8:18 PM" role="link">20m</a>
</div>
<div>
<div>
<div>
<span>😏 This is "Linearlite" built with React Native. Blog post from </span>
<div>
<div><a href="/profile/did:plc:53or656ypxoeh73lwshokzdm" role="link">@schickling.dev</a></div>
</div>
<span> will drop next Thursday during our Launch Party: </span><a href="https://expo.dev/launch-party" role="link">expo.dev/launch-party</a>
</div>
</div>
<div>
<div>
<!-- start of embedded link social image and information -->
<div>
<a href="https://expo.dev/launch-party" aria-label="Launch Party — Expo" aria-pressed="false" role="link" tabindex="0">
<!-- post removed because nothing was interactive, which is good -->
</a>
</div>
<!-- end of embedded link social image and information -->
<div>
<div>
<!-- start of embedded post, entire post is a link -->
<div aria-label="Post by codewithbeto.bsky.social" role="link" tabindex="0">
<div>
<div>
<div>
<div>
<div>
<a href="/profile/codewithbeto.bsky.social" aria-label="Beto's avatar" aria-pressed="false" role="link" tabindex="0">
<!-- profile image -->
</a>
</div>
</div>
</div>
<div>
<div>
<div><a href="/profile/codewithbeto.bsky.social" aria-label="View profile" role="link"><span>Beto</span></a><a href="/profile/codewithbeto.bsky.social" aria-label="View profile" role="link"><span> @codewithbeto.bsky.social</span></a></div>
</div>
</div>
<div>·</div>
<a href="/profile/codewithbeto.bsky.social/post/3lato2jya4c27"aria-label="November 13, 2024 at 4:29 PM" role="link">4h</a>
</div>
</div>
<!-- post removed because nothing was interactive, which is good -->
<!-- start of video player embed -->
<div>
<div>
<div>
<div>
<div>
<div>
<figure>
<video></video>
</figure>
<div>
<button role="button" tabindex="0" type="button"></button>
<div>
<div>
<div>
<div aria-label="Seek slider" role="slider" aria-valuemax="60.07" aria-valuemin="0" aria-valuenow="17.04" aria-valuetext="0:17 of 1:00" tabindex="0">
</div>
</div>
</div>
<div>
<button role="button" tabindex="0" type="button">
</svg>
</button>
<div>
<button role="button" tabindex="0" type="button">
</button>
</div>
<button role="button" tabindex="0" type="button">
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- end of video embed -->
</div>
<!-- end of embedded post -->
</div>
</div>
</div>
</div>
</div>
<!-- end of post contents / start of post actions -->
<div>
<div>
<button aria-label="Reply (1 reply)" role="button" tabindex="0" data-testid="replyBtn" type="button">
</button>
</div>
<div>
<div>
<button aria-expanded="false" aria-haspopup="menu" aria-label="Repost or quote post" role="button" tabindex="0" type="button">
</button>
</div>
</div>
<div>
<button aria-label="Like (1 like)" role="button" tabindex="0" data-testid="likeBtn" type="button">
</button>
</div>
<div>
<div>
<button aria-expanded="false" aria-haspopup="menu" aria-label="Open post options menu" role="button" tabindex="0" data-testid="postDropdownBtn" type="button">
</button>
</div>
</div>
</div>
<!-- end of post actions -->
</div>
</div>
</div>
</div>
What platform(s) does this occur on?
Web (Desktop), Web (Mobile), Android, iOS
Device Info
Not relevant
What version of the app are you using?
Web, Newest Android, Newest iOS
Additional Information
A lot of feeds get this wrong (why? This really is basic stuff). Twitter/X is not necessarily something to copy, but they did get this right.
The post itself is a labelled region:
<article aria-labelledby="..." role="article" tabindex="0" data-testid="tweet">
...
</article>
...which is spoken as:
region
clickable
Poster Name
Verified account graphic
visited link
@poster-handle visited link
36 minutes ago visited link
More menu button collapsed subMenu
[...]
There is no link nesting.