Advanced

Accessibility

Accessibility posture of the FanID Widget and gated content overlays — WCAG 2.1 AA targets, ARIA roles, keyboard support, reduced motion.

The FanID Widget targets WCAG 2.1 AA. Every UI surface ships with appropriate ARIA semantics, visible focus, keyboard support, and respects prefers-reduced-motion.

Built-in semantics

Floating widget

ElementSemantics
.fanid-widgetrole="region", aria-label="FanID sign-in"
Close button (×)Native <button type="button"> with aria-label="Close FanID widget". The × glyph is aria-hidden so the label is the only thing announced.
Sign-in CTANative <button type="button"> — visible label Sign In.
Logout button (icon-only)<button type="button" aria-label="Sign out">. Icon SVG is aria-hidden.
"My profile" buttonNative <button type="button"> with visible text.
User avatar (initial)Decorative — not focusable. Display name is rendered separately as text.
Loading state (Loading...)role="status" aria-live="polite" — announced when shown.
Error staterole="alert" so configuration errors are announced immediately. The error title uses <strong> for emphasis without disrupting heading hierarchy on the host page.
Brand logo (<img>)alt={organizationName} — falls back to "WMT Fan ID". Decorative-only would defeat brand recognition for screen readers.

Gated content overlay

ElementSemantics
Overlay containerrole="dialog", aria-modal="false", aria-label={placeholder.title}. The overlay is non-blocking — surrounding page content remains in the tab order — so aria-modal="false" is correct.
Sign-in CTANative <button> with visible label (Sign In by default, configurable via placeholder.buttonText).
Decorative gradient layerNot focusable. pointer-events: none so it can't intercept clicks.

Full-page gate

The full-page variant (FanIDWidget.gateFullPage()) is intended to block interaction. It applies filter / pointer-events: none to document.body, so the only interactive element on the page is the dialog panel itself. Close button (if allowClose: true) is <button aria-label="Close">.

Keyboard support

ActionKey
Open / focus the sign-in CTATab until the button is focused
Trigger sign-inEnter or Space (native button)
Close the floating widgetFocus the close button + Enter/Space
Sign out / open profileFocus respective button + Enter/Space

All interactive elements are native <button>s — there are no custom keyboard handlers to learn and no tabindex hacks. Focus order follows the DOM order: brand → CTA → close.

The widget does not trap focus inside the floating panel — it is intentionally non-modal so keyboard users can move past it to reach the rest of the page. The gated-content overlay is also non-modal for the same reason: the user must be able to inspect surrounding context before signing in.

Reduced motion

When the user has prefers-reduced-motion: reduce set at the OS level, the widget disables:

  • The floating panel's slide-in / fade-in entrance animations
  • The button hover/active transitions
  • The gated content reveal animation
  • Other timed transitions (close button hover, etc.)

The spinner animation is slowed (2s rotation instead of 0.8s) rather than stopped — a stationary spinner would imply the widget is hung. If you want to suppress it entirely, override the fanid-spin animation in your own stylesheet.

@media (prefers-reduced-motion: reduce) {
  .fanid-widget__spinner {
    animation: none;
  }
}

Color contrast

Colors come from your organization's Brand Settings (Primary and Accent). The defaults (#f26522 on white for the button, #002b5c for body text) clear WCAG 2.1 AA at the rendered sizes:

SurfaceForegroundBackgroundRatioWCAG AA
Sign-in buttonwhite#f265223.27 : 1✓ (large text + UI component target ≥ 3:1)
Brand title#002b5cwhite14.5 : 1✓ (normal text ≥ 4.5:1)
Subtitle / body text#002b5cwhite14.5 : 1✓

If you change Primary or Accent in Brand Settings, verify contrast against your widget's background (default white). Tools: WebAIM Contrast Checker, browser DevTools "Inspect Accessibility Properties." When the gated overlay sits on top of host-page content (blur mode), the panel itself uses a white background with a subtle shadow — host content does not affect contrast.

What the SDK does not do

These are deliberate non-features. Implement them at the host-page level if you need them:

  • Focus trapping inside the floating panel — non-modal by design.
  • Auto-focus on mount — the panel is auxiliary UI; auto-focusing it would steal focus from the primary page experience.
  • Custom screen-reader announcements for state changes other than the built-in error / loading roles. If you need to announce a successful sign-in to the host page, listen for the auth:login event and update an aria-live region of your own:
    FanIDWidget.on('auth:login', (profile) => {
      document.getElementById('site-announcer').textContent = `Signed in as ${profile.firstName}.`
    })
    

Testing checklist

When you embed the widget, verify on your own page:

  • All widget buttons are reachable with Tab in a sensible order
  • Visible focus indicator is present (the widget doesn't override your :focus-visible style)
  • Screen reader announces the panel as a region and reads button labels in your locale
  • Gated overlay announces as a dialog with the placeholder title as its label
  • Brand colors clear contrast against the widget background and the host page content
  • The widget shrinks gracefully at 200% zoom (it has no fixed pixel sizes outside the panel width)
  • With OS-level Reduce Motion enabled, no entrance animation plays

Reporting issues

Found an accessibility regression? File it against the FanID widget repo — we treat a11y regressions as P0 bugs.

Copyright © 2026