Sports Data Infrastructure

    Odds Grid Widgets: Complete Technical Specification

    Complete technical reference for implementing odds grid widgets. Performance requirements, accessibility standards, and best practices for production…

    13 min read3,005 words
    Share
    TL;DR

    Odds grid widgets are the dominant mechanism for displaying betting odds on sports websites and in betting applications. A well-designed odds grid widget should be invisible to the end user—it loads instantly, updates seamlessly, and displays information clearly. A poorly designed widget creates frustration (slow loading, jarring layout shifts, unreadable…

    Odds grid widgets are the dominant mechanism for displaying betting odds on sports websites and in betting applications. A well-designed odds grid widget should be invisible to the end user—it loads instantly, updates seamlessly, and displays information clearly. A poorly designed widget creates frustration (slow loading, jarring layout shifts, unreadable data) that damages user experience and directly impacts conversion.

    This specification provides technical requirements for production-grade odds grid widgets used in sports betting and publishing contexts.

    Widget Purpose and Context

    An odds grid widget displays betting odds for a specific match or event in grid format, typically showing:

    • Columns: Betting markets (1X2, Over/Under, Asian Handicap, etc.)
    • Rows: Outcomes within each market (Home Win, Draw, Away Win for 1X2; Over, Under for Totals)
    • Values: Odds in requested format (Decimal, Fractional, American)

    Typical implementation sizes: 300-600px width, 150-400px height depending on market count.

    Core Performance Requirements

    Load Time and Performance Budget

    Primary Requirement: Widget must load and render within 500ms of initialization, including:

    • Script injection and parsing
    • Data fetch from API/data source
    • DOM construction and initial render
    • CSS styling application

    This 500ms requirement assumes widget is embedded on a page that has already loaded. Page rendering may occur before widget loads (recommended pattern).

    Critical rendering path:

    0ms: Widget script execution starts
    50-100ms: API call initiated
    100-150ms: Initial DOM elements created
    150-200ms: Data received from API
    200-300ms: Full DOM construction + CSS application
    300-500ms: Final render + interactive state ready
    

    Measurement and Monitoring

    Implement performance monitoring using:

    • Navigation Timing API: Measure widget load time relative to page load
    • Resource Timing API: Measure API call latency and data transfer size
    • Web Vitals: Track CLS (Cumulative Layout Shift), LCP (Largest Contentful Paint), FID (First Input Delay)

    Target Web Vitals scores:

    • LCP: <1.0s
    • FID: <100ms
    • CLS: <0.1
    • TTFB (Time to First Byte): <400ms

    Missing these targets by >20% indicates optimisation needed.

    Data Transfer Size

    Widget bundle size: <50kb (gzip)

    • Script code: <30kb
    • Initial CSS: <10kb
    • Icons/assets: <10kb

    API response size: <5kb per odds update

    • Use compact JSON format
    • Minimize metadata
    • Separate rate-limiting headers from payload

    Responsive Design and Display Sizes

    Device-Specific Layouts

    Odds grid widgets must render properly across:

    1. Desktop (1200px+): Full grid with all markets visible

      • Layout: 6-8 columns × 2-5 rows
      • Font size: 14-16px
      • Cell padding: 8-12px
    2. Tablet (600-1200px): Condensed grid or scrollable view

      • Layout: 3-4 columns × 2-5 rows (scrollable)
      • Font size: 13-15px
      • Cell padding: 6-10px
    3. Mobile (0-600px): Single-market view or horizontal scroll

      • Layout: 2-3 columns visible at a time, horizontally scrollable
      • Font size: 12-14px
      • Cell padding: 4-8px
      • Touch-friendly minimum tap target: 44px × 44px

    Viewport Meta Configuration

    <meta name="viewport" content="width=device-width, initial-scale=1.0,
    maximum-scale=5.0, user-scalable=yes">
    

    Widget must maintain usability with user zoom (0.5x to 2x).

    Scrolling and Overflow Behavior

    For widgets with more markets than screen space:

    • Desktop: Horizontal scroll within widget boundary (scrollbar visible)
    • Tablet: Horizontal scroll with touch-friendly scroll indicators
    • Mobile: Horizontal scroll, snap-to-position for better UX

    Avoid vertical scrolling in widget (constrains height).

    Data Refresh and Real-Time Updates

    Update Frequency

    Pre-Match Odds (no live event):

    • Update interval: 30-60 seconds
    • Acceptable latency: <5 seconds from odds change to display update

    In-Play / Live Event:

    • Update interval: 10-30 seconds
    • Acceptable latency: <1 second from odds change to display update
    • Critical: Never display stale odds during live events

    Update Mechanism

    Two patterns are common:

    1. Polling Pattern (simpler, more common):

      • Call API every 30 seconds (pre-match) or 10 seconds (in-play)
      • Simple to implement, compatible with all environments
      • Higher server load at scale
    2. WebSocket Pattern (more efficient):

      • Maintain persistent connection
      • Receive odds push updates immediately
      • Lower latency, lower server load, more complex client-side

    Handling Stale Data

    If API latency exceeds thresholds:

    • Display "Last updated: X seconds ago" indicator
    • Reduce confidence level of displayed odds (lighter styling, warning icon)
    • Implement automatic retry on network timeout (3 retries, exponential backoff)

    Display Formatting

    Odds Format Selection

    Support multiple odds formats simultaneously:

    1. Decimal (European standard):

      • Format: 1.25, 2.50, 5.00
      • Precision: 2 decimal places minimum
      • Use cases: Europe, UK, Australia
    2. Fractional (UK traditional):

      • Format: 1/4, 3/2, 4/1
      • Reduce fractions (e.g., 2/4 → 1/2)
      • Use cases: UK betting exchanges
    3. American (Moneyline):

      • Format: -400, +150, -200
      • Use cases: US betting market
      • Interpretation: Negative = amount to win $100; Positive = win on $100

    Implementation: Use ISO 4217 for currency format; implement Intl.NumberFormat API for locale-specific formatting.

    Cell Styling and Highlighting

    Standard cell:

    • Background: White or neutral light gray
    • Border: 1px solid #ccc
    • Text color: #333 (dark gray)
    • Font weight: Normal (400)

    Highlighted cell (best available odds):

    • Background: Light green (#e6f7e6) or light blue (#e6f0f7)
    • Font weight: Bold (600)
    • Border: 1px solid highlight color

    Updated cell animation (on data refresh):

    • Flash highlight for 500-1000ms when odds change
    • Fade smoothly (not jarring color change)
    • Use background-color transition: ease-in-out 200ms

    Disabled/Unavailable cell:

    • Background: Light gray (#f5f5f5)
    • Text: Dimmed (#999)
    • Cursor: Not-allowed
    • Display: "N/A" or "-"

    Decimal Precision

    • Odds 1.01-2.00: Display 2 decimal places (1.25)
    • Odds 2.01-10.00: Display 2 decimal places (5.50)
    • Odds 10.01+: Display 2 decimal places (15.00)

    Don't display more than 2 decimal places (e.g., avoid 1.254).

    Accessibility Requirements

    WCAG 2.1 Level AA Compliance

    Odds grid widgets must meet accessibility standards:

    1. Keyboard Navigation:

      • Tab through cells (implement tabindex)
      • Enter key to select odds
      • Arrow keys to navigate grid
      • Escape to deselect/close
    2. Screen Reader Support:

      • Use semantic HTML (table, thead, tbody, th, td)
      • Provide alt text for icons
      • Use aria-labels for column/row headers
      • Example: "Home Win odds: 1.50"
    3. Color Contrast:

      • Text contrast ratio: 4.5:1 minimum (normal text)
      • 3:1 minimum for large text (18px+)
      • Don't rely solely on color to convey information (use labels)
    4. Focus Indicators:

      • Visible focus outline on all interactive elements
      • Focus color: Contrasting color (e.g., #0066cc)
      • Focus outline width: 2-3px

    Example HTML Structure

    <table role="grid" aria-label="Match odds for Manchester United vs Liverpool">
      <thead>
        <tr>
          <th id="market-1x2">1X2</th>
          <th id="market-totals">Over/Under 2.5</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td headers="market-1x2">
            <button aria-label="Home win at 1.50">1.50</button>
          </td>
          <td headers="market-totals">
            <button aria-label="Over 2.5 goals at 1.90">1.90</button>
          </td>
        </tr>
      </tbody>
    </table>
    

    Mobile Optimisation

    Touch Interactions

    • Tap target size: Minimum 44×44px (Apple guideline)
    • Touch feedback: Visual feedback (opacity change or color) within 100ms
    • Long press: Option to view odds details (historical data, etc.)

    Mobile-Specific Features

    1. Gesture support:

      • Horizontal swipe to scroll markets
      • Pinch-to-zoom support (with reasonable limits)
      • Double-tap to select/deselect
    2. Mobile data optimisation:

      • Lazy-load images (icons, logos)
      • Aggressive compression (gzip + Brotli)
      • Implement data saver mode (reduce update frequency if enabled)
    3. Connectivity handling:

      • Detect offline status (navigator.onLine)
      • Display cached odds if offline (with clear indication)
      • Auto-reconnect with exponential backoff

    Security and Compliance

    Data Validation

    Validate all API responses:

    // Example validation
    const validateOdds = (odds) => {
      if (typeof odds !== 'number') throw new Error('Invalid odds format');
      if (odds < 1.01 || odds > 1000) throw new Error('Odds out of range');
      if (!Number.isFinite(odds)) throw new Error('Non-finite odds');
      return true;
    };
    
    • Validate odds are numeric and within reasonable range (1.01-1000)
    • Validate market identifiers
    • Validate timestamps (ensure freshness)

    Content Security Policy (CSP)

    Implement CSP headers to prevent XSS:

    Content-Security-Policy:
      script-src 'self' https://api.example.com;
      style-src 'self' 'unsafe-inline';
      img-src 'self' https:;
      connect-src 'self' https://api.example.com
    

    GDPR and Privacy

    • No persistent storage of odds without user consent
    • Don't track betting behavior across sites without disclosure
    • Implement Do Not Track (DNT) header respect
    • Provide clear privacy policy for widget data usage

    Responsible Gambling

    Display responsible gambling messaging:

    <div class="rg-warning" role="complementary">
      <p>⚠️ Betting involves risk of loss.
         See www.begambleaware.org</p>
    </div>
    
    • Display problem gambling hotline number
    • Add age verification if required by jurisdiction
    • Implement loss-limit warnings if tracking customer activity

    Testing and Quality Assurance

    Automated Testing

    1. Unit tests: Odds formatting, data validation (Jest/Mocha)
    2. Integration tests: API communication, data updates (Cypress/Playwright)
    3. Performance tests: Load time, update latency (Lighthouse, WebPageTest)
    4. Accessibility tests: WCAG compliance (axe DevTools, WAVE)

    Browser and Device Coverage

    Test across:

    • Browsers: Chrome, Firefox, Safari, Edge (latest + 1 version back)
    • Devices: iPhone, Android, iPad, desktop (1920×1080, 1366×768, mobile viewports)
    • Network conditions: 4G, 3G, offline simulation (Chrome DevTools throttling)

    Performance Benchmarking

    Establish baseline metrics:

    Load time: 350ms p50, 500ms p95
    Update latency: 100ms p50, 300ms p95
    Interaction latency: <100ms
    Memory usage: <15MB
    CPU usage: <5% during updates
    

    Monitor continuously and alert if metrics exceed thresholds.

    Common Implementation Mistakes to Avoid

    1. Blocking page load: Don't load widget synchronously; always async
    2. Layout shift on updates: Use fixed-size cells to prevent CLS
    3. Missing fallback: Display reasonable fallback if API fails
    4. No error handling: Implement try-catch around API calls
    5. Hardcoded styling: Use CSS variables for theming flexibility
    6. No mobile optimisation: Odds grid must be usable on mobile
    7. Stale odds: Always show when odds were last updated
    8. Poor accessibility: Implement keyboard navigation and screen reader support
    9. No performance monitoring: Can't improve what you don't measure
    10. Over-engineering: Simpler is better (avoid unnecessary animations, transitions)

    Widget Size and Layout Options

    Different use cases require different widget dimensions:

    Standard Desktop Widget (600×300px)

    Best for: Publisher sidebar, operator promotions Layout: 6-8 columns × 3-4 rows

    • Width: 600px
    • Height: 300px
    • Market count: 6-12 distinct markets
    • Typical markets: 1X2, Totals, Handicaps, 2-3 props

    CSS example:

    .odds-widget-standard {
      width: 600px;
      height: 300px;
      overflow-y: auto;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    
    .odds-widget-standard .market {
      display: grid;
      grid-template-columns: repeat(6, 1fr);
      gap: 8px;
      padding: 8px;
    }
    

    Compact Widget (300×200px)

    Best for: Inline article embeds, limited space Layout: 3-4 columns × 2-3 rows (with horizontal scroll)

    • Width: 300px
    • Height: 200px
    • Market count: 3-6 distinct markets
    • Typical markets: 1X2, Totals only

    Full-Width Widget (100%×variable)

    Best for: Operator dashboard, comprehensive odds display Layout: All markets visible, multi-row

    • Width: 100% of container
    • Height: Dynamic based on market count
    • Market count: 15+ distinct markets
    • All available markets displayed

    Mobile-Optimised Widget (responsive 0-600px)

    Best for: Mobile operators, publisher mobile experience Layout: 2-column mobile, 4-column tablet, 6+ desktop

    • Width: 100% (responsive)
    • Height: 200px mobile, 250px tablet, 300px desktop
    • Touch-friendly: 44×44px minimum tap targets

    Ticker Widget (narrow, scrolling)

    Best for: Continuous display of odds changes Layout: Single column, wide, horizontal scroll

    • Width: 1200px or 100%
    • Height: 60px
    • Shows: Market name + current odds, scrolls through all matches
    • Auto-scroll: New matches appear from right

    Advanced Implementation Patterns

    Pattern 1: Skinning and Theme Customization

    Enable flexible styling for different contexts (publisher vs. operator site):

    /* CSS Custom Properties for theming */
    :root {
      --odds-bg: #ffffff;
      --odds-text: #333333;
      --odds-border: #cccccc;
      --odds-highlight: #d4edda;
      --odds-font-size: 14px;
      --odds-padding: 8px;
    }
    
    /* Dark mode variant */
    [data-theme="dark"] {
      --odds-bg: #2d2d2d;
      --odds-text: #ffffff;
      --odds-border: #444444;
      --odds-highlight: #1e6622;
    }
    
    /* Compact variant */
    [data-layout="compact"] {
      --odds-font-size: 12px;
      --odds-padding: 4px;
    }
    

    This approach lets same widget display differently based on context without code changes.

    Pattern 2: Virtual Scrolling for Large Tables

    For widgets with hundreds of markets:

    class VirtualOddsGrid {
      constructor(container, allOdds, visibleRows) {
        this.container = container;
        this.allOdds = allOdds;
        this.visibleRows = visibleRows;
        this.scrollTop = 0;
    
        this.render();
        this.container.addEventListener('scroll', () => this.onScroll());
      }
    
      onScroll() {
        const newScrollTop = this.container.scrollTop;
        if (Math.abs(newScrollTop - this.scrollTop) > 30) { // Only re-render on significant scroll
          this.scrollTop = newScrollTop;
          this.render();
        }
      }
    
      render() {
        const startIndex = Math.floor(this.scrollTop / rowHeight);
        const endIndex = startIndex + this.visibleRows;
        const visibleOdds = this.allOdds.slice(startIndex, endIndex);
    
        // Render only visible rows, not entire table
        this.container.innerHTML = this.buildHTML(visibleOdds, startIndex);
      }
    }
    

    Virtual scrolling reduces DOM nodes from 1000+ to 20-30, dramatically improving performance.

    Pattern 3: Optimistic Updates

    Update UI immediately when user interacts, verify with server:

    function selectOdds(oddsId, odds) {
      // Optimistic: Update UI immediately
      updateUIselection(oddsId);
    
      // Pessimistic: Verify with server
      api.validateOdds(oddsId, odds).then(response => {
        if (!response.valid) {
          // Odds have changed, revert UI
          revertSelection();
          showMessage('Odds changed, try again');
        }
      }).catch(() => {
        revertSelection();
        showNetworkError();
      });
    }
    

    This pattern makes UI feel responsive even with network latency.

    Troubleshooting Common Widget Issues

    Issue: Widget loads but odds don't update

    Likely causes:

    1. API endpoint misconfigured (404 errors)
    2. Data refresh interval is too long or not triggered
    3. CORS policy blocking API calls

    Diagnostic steps:

    • Check browser console for JavaScript errors
    • Check Network tab in DevTools for failed API requests
    • Verify API endpoint is correct
    • Check Access-Control-Allow-Origin headers

    Issue: Widget updates create layout shift

    Likely causes:

    1. Odds change cell size (e.g., 1.50 → 12.50 changes width)
    2. Font-size changes on update
    3. Padding/margin changes

    Solution:

    .odds-cell {
      width: 60px;  /* Fixed width prevents shift */
      height: 40px; /* Fixed height prevents shift */
      overflow: hidden;
      text-align: center;
    }
    

    Issue: Mobile widget shows misaligned text

    Likely causes:

    1. Font size too large for small screen
    2. Cell padding creates overflow
    3. Viewport meta tag missing

    Solution:

    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!-- Media query for mobile -->
    @media (max-width: 600px) {
      .odds-cell {
        font-size: 12px;
        padding: 4px;
      }
    }
    

    Testing Checklist

    Before deploying widget to production:

    Functional Testing:

    • Widget loads within 500ms
    • Odds update correctly when data changes
    • All responsive breakpoints work (mobile, tablet, desktop)
    • Clicking odds updates betslip (if applicable)
    • Keyboard navigation works (Tab, Enter, Arrow keys)
    • Screen reader announces odds correctly

    Performance Testing:

    • LCP < 1.0s
    • FID < 100ms
    • CLS < 0.1
    • Widget doesn't block page load
    • Memory usage stays <15MB

    Cross-browser Testing:

    • Chrome/Edge (latest + 1 version back)
    • Firefox (latest + 1 version back)
    • Safari (latest + 1 version back)
    • Mobile Safari (iPhone)
    • Chrome Mobile (Android)

    Accessibility Testing:

    • Color contrast > 4.5:1
    • Keyboard navigation works
    • Screen reader testing (NVDA, JAWS)
    • No flash or seizure triggers
    • Focus indicators visible

    Conclusion

    Odds grid widgets are deceptively complex—simple to look at, challenging to implement well. The core requirements are straightforward: load fast (<500ms), update reliably, display accessibly, work on all devices, and maintain visual stability.

    The difference between a good implementation and a poor one is often invisible to end users until something goes wrong—slow loading, jarring updates, accessibility issues. Invest time in meeting the performance, accessibility, and responsive requirements outlined here. The competitive operators will have the fastest, most reliable widgets.


    CTA: Widget Implementation Resources

    Download the Odds Grid Widget Implementation Checklist for a step-by-step validation against this specification.

    [Download Checklist]

    Or access the widget code samples showing example implementations of critical patterns (responsive layout, real-time updates, accessibility compliance).

    [View Code Samples]


    Last updated: March 2026. Based on WCAG 2.1, Web Vitals Core Standards, and production implementations. © 2026 FairPlay Sports Media.

    Share

    Ready to explore BetTech for your business?

    Talk to the FairPlay team about how our platform can work for your business.

    Get Started

    Related Articles

    Explore More Insights