Accessibility
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
| Element | Semantics |
|---|---|
.fanid-widget | role="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 CTA | Native <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" button | Native <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 state | role="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
| Element | Semantics |
|---|---|
| Overlay container | role="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 CTA | Native <button> with visible label (Sign In by default, configurable via placeholder.buttonText). |
| Decorative gradient layer | Not 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
| Action | Key |
|---|---|
| Open / focus the sign-in CTA | Tab until the button is focused |
| Trigger sign-in | Enter or Space (native button) |
| Close the floating widget | Focus the close button + Enter/Space |
| Sign out / open profile | Focus 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.
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:
| Surface | Foreground | Background | Ratio | WCAG AA |
|---|---|---|---|---|
| Sign-in button | white | #f26522 | 3.27 : 1 | ✓ (large text + UI component target ≥ 3:1) |
| Brand title | #002b5c | white | 14.5 : 1 | ✓ (normal text ≥ 4.5:1) |
| Subtitle / body text | #002b5c | white | 14.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:loginevent and update anaria-liveregion 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-visiblestyle) - 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.