• Design systems
  • Usability testing
  • Built the first shared design system for three product teams
  • React components
  • Storybook
  • Shipped 40 accessible React components with docs
  • Patient journeys
  • Service design
  • Mapped intake from referral to first visit with clinic staff
  • WCAG 2.1 AA
  • Design tokens
  • Took the patient portal to WCAG 2.1 AA
  • Information architecture
  • Workshops
  • Reworked account navigation around how customers named things
  • Prototyping
  • Angular
  • Prototyped the new transfer flow in Angular
  • Research ops
  • Plain language
  • Set up research with residents for three city services
  • Web components
  • Performance
  • Cut form load time in half with web components

    

Get it

Use it on your site

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>

View on GitHub Full setup guide MIT License

Accessibility

Built for keyboards and screen readers from the start

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.

Keyboard

KeyWhat it does
Tab / Shift TabMoves between badges, then pins, in the order they hang. A focus ring follows the badge as it moves.
Enter or SpaceFlips 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 pinShows its note, the same as hovering.

What a screen reader hears

Reduced motion

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.

Why plain buttons

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.