Get it
One JavaScript file plus three.js, with no build step. It's MIT licensed: use it on your own site, change it, ship it. The source, the full setup guide and issues are on GitHub.
<!-- in your <head> -->
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
} }
</script>
<script type="module" src="js/lanyard-wall.js"></script>
<!-- one badge per job, oldest first -->
<lanyard-wall name="Alex Rivera" contact="If found, email: alex@example.com">
<lanyard-badge org="Northwind Labs" role="Product Designer" start="03/2017" end="08/2019"
color="#1d3557" band="#a8dadc" band2="#457b9d" strap="#14243b">
<lanyard-section label="UX design">
<ul data-side="front"><li>Design systems</li></ul>
<ul data-side="back"><li>Built the first shared design system for three product teams</li></ul>
</lanyard-section>
</lanyard-badge>
</lanyard-wall>
Accessibility
The badges are drawn on a WebGL canvas, which assistive technology can't read. So the canvas is hidden from it with aria-hidden, and the real interface sits on top: one native <button> for each badge and pin, moved over it every frame as it swings. Mouse and touch pass through to the physics; keyboard focus and screen readers land on the buttons.
| Key | What it does |
|---|---|
| Tab / Shift Tab | Moves between badges, then pins, in the order they hang. A focus ring follows the badge as it moves. |
| Enter or Space | Flips the focused badge to its other side. |
| ← / → | Gives the focused badge a push to the left or right, so keyboard users can swing it too. |
| Focus on a pin | Shows its note, the same as hovering. |
label attribute (here, “Alex Rivera's work history”).aria-describedby) holds the full text of both sides, so nothing is hidden behind a flip.The wall follows prefers-reduced-motion: flips become a turn in place, a released badge doesn't swing, and the breeze stops. The reduced-motion attribute turns it on or off for a page's own switch, which is how the Reduce motion button at the top of this page works. Without WebGL, the same content renders as a plain list of cards.
A badge does one thing, flip, and that is a button's job. A native button needs no extra ARIA beyond a name and a description, and every screen reader, switch device and voice control tool already knows how to use it. A toggle button (aria-pressed) was the other option, but “pressed” doesn't say which side is showing. Naming the side in the label, and announcing the change when the card lands, says it in plain words. The buttons share one labelled group, so the wall reads as a single region of related controls, and they stay in the normal tab order, because a handful of badges doesn't need a roving tabindex.